Dynamic Links from a CMS - Error: "redirect" can not be returned from getStaticProps during prerendering - redirect

I have a Next JS app connected to a CMS and hosted on Vercel - all links are dynamic and the pages are created by the content authors.
I am trying to create dynamic redirects that will force URLs to adhere to formats that are better for SEO. For example:
Enforce lowercase URLs
Replace spaces with dashes
Remove trailing slashes
For example, /test/Author Name/ would redirect to /test/author-name
Since I need to trigger a 301 redirect for these wrong URLs, the only way to do this with Next JS from what I have found is to return a Redirect from getStaticProps, this is what I have so far:
export const getStaticProps: GetStaticProps = async (context) => {
let requestedUrl = '/';
if (context?.params?.path) {
requestedUrl = '/' + (context?.params?.path as string[]).join('/');
}
//check for URLs with uppercases, spaces, etc. and clean them up
let modifiedUrl = requestedUrl;
modifiedUrl = modifiedUrl.trim().toLowerCase().replace(/\s\s+/g, ' ').replace(/\s/g, '-');
if (modifiedUrl != requestedUrl) {
return {
redirect: {
destination: modifiedUrl,
permanent: true,
},
};
}
This works wonderfully well running locally and connected to the CMS - everything is working as it should and all "faulty" URLs are corrected with the correct response code.
Sadly, this does not work on build, I have spent so much time so far trying to find an alternative, but no matter what I do, the build on Vercel fails with the error:
"redirect" can not be returned from getStaticProps during prerendering
The next best potential solution is to use Middleware, but that requires v.12 at least. Due to limitations from the CMS connector, we are forced to use Node v.11 :(
The alternative that I have built is to use router.push on the client side, but this... just looks terrible. The page loads, returns a 200, and then loads again with the corrected URL. Not good for the user's experience.
Any advice or suggestions? I am baffled that something this simple is this complicated with Next JS!

I resolved the issue... it looks like redirects on statically generated pages are not possible unfortunately. I removed getStaticProps and getStaticPaths, and added getServerSideProps instead. The redirects are now working correctly, but the site is not as fast as we are losing out on SSG.

Related

Backend Returns "Access-Control-Allow-Origin" Header Twice, Each With Different Value

I'm using Python's FastAPI to manage the server's API and Axios hooks on my Frontend.
Here's my code snippet that handles details of the CORS policy on the server:
origins = ["http://localhost:3000"]
*****some code here*****
app = FastAPI(
title=settings.PROJECT_NAME,
version="1.0",
docs_url=f"{settings.API_V1_STR}/docs",
openapi_url=f"{settings.API_V1_STR}/openapi.json",
)
app.container = app
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
app.include_router(api_router, prefix=settings.API_V1_STR)
Here's the relevant hook that I'm using on the Frontend via Axios-hooks axios-hooks docs:
const [
{
response: marketResponse,
loading: marketLoading,
error: marketError,
},
] = useAxios({
url: serverURL("market/list"),
method: "GET",
});
It's important to note that I've double checked the allowed origin.
The issue:
As my web app requests the list via above mentioned axios hoook, the following error appears:
Frontend HTTP Error
Here's the Network Tab's Header Info:
Network's Header Info
As you'll notice Access-Control-Allow-Origin appears there 2x! Once in capped init letter and 2nd time in all lower case. I figured the issue somehow stems from this headers. Unfortunatelly can't find a particular way to fix it.
Thanks for any kind of help!
Googled multiple similiar issues and studied both Axios & Fast Api docs. Unfortunately couldn't find even a slight hint of solution.
The issue
This is actually a known issue with the Starlette CORSMiddleware.
So the issue is that Starlette CORSMiddleware adds the origin header without checking if it already exists. That is by design.
FastAPI is wrapping this module.
If you take a look at the actual Starlette code you will see that it is going to this line.
This explains the upper case version of Access-Control-Allow-Origin.
So why is this happening?
Multiple servers are running. A known issue is if you are using python-socketio then the socketio will add its own version of the header.
I noticed this line:
app.container = app
What is the reason for this? Can you try to comment it out and rerun the code?
Else check if your NGINX is setting CORS.

TYPO3 v10: How to access TSFE in Backend/Scheduler Task?

The current situation:
I am trying to access the TypoScript configuration of the frontend from within the backend (or rather a scheduler task). Previously with Typo3 v8 and v9, I initialized entire $GLOBALS["TSFE"] object, however this was already hack the last time around (using mostly deprecated calls) and now it has all been removed with the v10 release.
My goal:
Access the TypoScript configuration of the frontend of a certain page (root page of a site would be fine) from within a scheduler job.
Background of the whole project:
I have a periodic scheduler job that sends emails to various users (fe_users). The email contains links to certain pages (configured UIDs in typoscript) as well as file attachments and the likes (generated by other extensions, which are also fully configured via typoscript). Currently, I basically initialize the entire frontend from within the backend, but as I said before, its inefficient, super hacky and I doubt it was the intended way to solve this problem.
Getting TypoScript settings in the backend is ugly, but possible.
You need a page ID and a rootline which you can pass to \TYPO3\CMS\Core\TypoScript\TemplateService::runThroughTemplates().
Something along these lines:
$template = GeneralUtility::makeInstance(TemplateService::class);
$template->tt_track = false;
$rootline = GeneralUtility::makeInstance(
RootlineUtility::class, $pageId
)->get();
$template->runThroughTemplates($rootline, 0);
$template->generateConfig();
$typoScriptSetup = $template->setup;
You can get inspiration from \TYPO3\CMS\Extbase\Configuration\BackendConfigurationManager::getTypoScriptSetup and \TYPO3\CMS\Tstemplate\Controller\TypoScriptTemplateObjectBrowserModuleFunctionController
This won't get any better and is not intended to be done such way. I would use as configuration:
plain PHP, e.g. in $GLOBALS['TYPO3_CONF_VARS]`
YAML site config if depending on various sites
You can build links by using e.g. something like that
protected function generateUrl(int $pageId, int $recordId)
{
$additionalParams = 'tx_xxxx[action]=show&tx_ixxxx[controller]=Job&tx_xxxx[job]=' . $recordId;
return BackendUtility::getPreviewUrl($pageId, '', null, '', '', $additionalParams);
}

Authentication That Doesn't Require Javascript?

I have a Web API app, initialized thusly:
app.UseCookieAuthentication();
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
app.UseOAuthBearerTokens(OAuthOptions);
app.UseGoogleAuthentication();
For calls to most controllers, it works great. However, it also requires a bit of javascript before client-side service calls are made:
function getSecurityHeaders() {
var accessToken = sessionStorage["accessToken"] || localStorage["accessToken"];
if (accessToken) {
return { "Authorization": "Bearer " + accessToken };
}
return {};
}
The problem is that we have a certain type of controller (one that accesses files) where no javascript can be run during the call. For example, the call might be to:
http://mysite/mycontroller/file/filename.jpg
...where the value is assigned as the src attribute of an img tag. The call works, but Thread.CurrentPrincipal.Identity is unauthenticated with a null name, so there's currently not a way to enforce security.
I'm new to Web API, so it may be a dumb question, but what's the way around this? What switches do I need to flip to not require javascript to add security headers? I was considering trying to find a way to force an authorization header in an IAuthorizationFilter or something, but I'm not even sure that would work.
So I figured out the solution to my problem.
First, I needed to configure the app to use an authentication type of external cookies thusly:
//the line below is the one I needed to change
app.UseCookieAuthentication(AuthenticationType = DefaultAuthenticationTypes.ExternalCookie);
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
app.UseOAuthBearerTokens(OAuthOptions);
app.UseGoogleAuthentication();
Second, it turned out there was a line of code in my WebApiConfig file that was disabling reading the external cookie:
//this line needed to be removed
//config.SuppressDefaultHostAuthentication();
After that, I could see the external cookie from Google, which passed along an email address I could identify the user with.

Soundmanager2 won't load sound from google translate

I want to speak some text; I can get the audio-file(mp3) from google translate tts if I enter a properly formatted url in the browser.
But if I try to createSound it, I only see a 404-error in firebug.
I use this, but it fails:
soundManager.createSound(
{id:'testsound',
autoLoad:true,
url:'http://translate.google.com/translate_tts?ie=UTF-8&tl=da&q=testing'}
);
I have pre-fetched the fixed voiceprompts with wget, so they are as local mp3-files on the same webserver as the page. But I would like to say a dynamic prompt.
I see this was asked long time ago, but I have come to a similar issue, and I was able to make it work for Chrome and Firefox, but with the Audio Tag.
Here is the demo page I have made
http://jsfiddle.net/royriojas/SE6ET/
here is the code that made the trick for me...
var sayIt;
function createSayIt() {
// Tiny trick to make the request to google actually work!, they deny the request if it comes from a page but somehow it works when the function is inside this iframe!
//create an iframe without setting the src attribute
var iframe = document.createElement('iframe');
// don't know if the attribute is required, but it was on the codepen page where this code worked, so I just put this here. Might be not needed.
iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-pointer-lock');
// hide the iframe... cause you know, it is ugly letting iframes be visible around...
iframe.setAttribute('class', 'hidden-iframe')
// append it to the body
document.body.appendChild(iframe);
// obtain a reference to the contentWindow
var v = iframe.contentWindow;
// parse the sayIt function in this contentWindow scope
// Yeah, I know eval is evil, but its evilness fixed this issue...
v.eval("function sayIt(query, language, cb) { var audio = new Audio(); audio.src = 'http://translate.google.com/translate_tts?ie=utf-8&tl=' + language + '&q=' + encodeURIComponent(query); cb && audio.addEventListener('ended', cb); audio.play();}");
// export it under sayIt variable
sayIt = v.sayIt;
}
I guess that I was able to byPass that restriction. They could potentially fix this hack in the future I don't know. I actually hope they don't...
You can also try to use the Text2Speech HTML5 api, but it is still very young...
IE 11 is not working with this hack, some time in the future I might try to fix it
Even though you see this as a 404 error, you're actually running into a cross-domain restriction.
Some of the response headers from that 404 will also give you a clue of what's going on:
X-Content-Type-Options:nosniff
X-XSS-Protection:1; mode=block
So, you won't be able to do this client-side, as Google does not (and probably will never) allow you to do so.
In order to do this dynamic loading of audio, you need to work around this x-domain restriction by setting up a proxy on your own server, which would download whatever file requested by the end-user from Google's servers (via wget or whatever) and spitting whatever data comes from google.
Code I used to reproduce the issue:
soundManager.setup({
url: 'swf',
onready: function() {
soundManager.createSound({
id:'testsound',
autoLoad:true,
url:'http://translate.google.com/translate_tts?ie=UTF-8&tl=da&q=testing'
});
}
});
Your code should look like this:
soundManager.createSound({
id:'testsound',
autoLoad:true,
url:'/audioproxy.php?ie=UTF-8&tl=da&q=testing' // Same domain!
});
Regards and good luck!

OAuth & facebook access_token not working, need OAuth expert

I have read the 1000+ blogs about how the redirect_uri has to be the same in both calls to OAuth in order to get a user token, but 100% of the time, regardless of how I format the URL, it fails with:
{
"error": {
"message": "Error validating verification code. Please make sure your redirect_uri is identical to the one you used in the OAuth dialog request",
"type": "OAuthException",
"code": 100
}
}
I have been meticulous in making sure that the URLs in both calls were the exact same. My URL has to have a ? in it and I have tried replacing it with %3f but that didn't help. There has to be something else that can cause this error, I need to learn what that might be?
This seemed to break for me over the past month sometime. We did a show in late July and things worked fine (had a different base URL for that show since it was a different server). Could it be that the URL is of this format:
someprestuff.morestuff.mainurl.com?prm=value
Are there too many "parts" to the URL for Facebook to accept it?
I'm looking for alternate things to look for.
The url should be the same and it has to be escaped. In the url it has to look like this:
http%3A//someprestuff.morestuff.mainurl.com%3Fprm%3Dvalue
Jim's comment above worked, but to clarify, it was a forward slash that fixed it for us.
Had the same problem today, the problem turned out to be that the redirect_uri used a http:// URL Schema, and Facebook only accepts https://
I just finished tracing this issue on a server that was behind a load balancer. It turns out that while the load balancer was passing the HTTP_X_FORWARDED_PROTO header, somehow between the V3 PHP Facebook library, PHP and Apache, the header was not being recognized.
Here is the relevant code from the library:
protected function getHttpProtocol() {
if ($this->trustForwarded && isset($_SERVER['HTTP_X_FORWARDED_PROTO'])) {
if ($_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') {
return 'https';
}
return 'http';
}
/*apache + variants specific way of checking for https*/
if (isset($_SERVER['HTTPS']) &&
($_SERVER['HTTPS'] === 'on' || $_SERVER['HTTPS'] == 1)) {
return 'https';
}
/*nginx way of checking for https*/
if (isset($_SERVER['SERVER_PORT']) &&
($_SERVER['SERVER_PORT'] === '443')) {
return 'https';
}
return 'http';
}
As you can see, there are a few scenarios being accommodated here, so you'll have to ensure that whatever situation applies to you is properly configured for this block of code to succeed.
The most likely solution will be to set trustForwarded to true in your facebook config array.