Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(lambda-at-edge): support trailing slash / non-trailing slash redirects #556

Merged
merged 7 commits into from Aug 26, 2020
Merged

Conversation

dphang
Copy link
Collaborator

@dphang dphang commented Aug 22, 2020

Description

This adds support for server-side redirects (via Lambda origin request handler) based on value of trailingSlash in next.config.js, or by default trailingSlash = false if not set.

By putting it in origin request handler (instead of viewer request handler), we ensure this code is not called on every request, only for redirection requests / requests that were not cached by CloudFront. Also, we do not do regex matching for perf/security reasons, although I believe this is how Next.js does it (e.g for files: vercel/next.js#14681).

Here's the feature from Next.js: https://nextjs.org/docs/api-reference/next.config.js/trailing-slash. I saw a few issues about this, such as #479 but didn't see anyone was working on this for a while, and I had some free time, so I thought I'd take a stab at it. Hopefully no one else has already been working on it!

  1. SSR/SSG pages: if trailingSlash is set to true, then URLs like example.com/mypage get redirected to example.com/mypage/.

  2. SSR/SSG pages: if trailingSlash is set to false or not set (i.e default behavior), then URLs like example.com/mypage/ get redirected to example.com/mypage.

  3. Query string is also passed along: e.g if trailingSlash is true then example.com/mypage?abc=123 is redirected to example.com/mypage/?abc=123. Vice-versa if trailingSlash is false.

  4. In addition, data requests (JSON) and public files always get redirected to non-trailing slash URL.

Note: all the above is the same behavior as in Next/Vercel. The redirect uses a 308 status code (Permanent Redirect, same as Next/Vercel. See https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/308. It's supported by all browsers except IE11 pre-windows 10. For full IE11 support, we also always add a refresh header e.g 0;url=/ as per https://nextjs.org/docs/api-reference/next.config.js/redirects / https://github.com/vercel/next.js/pull/10042/files)

The one exception: files in /_next/static are not redirected if they have trailing slash in URL, they will get 403 response code instead. See notes below, this seems due to the separate CloudFront/S3 behavior (Vercel/Next handles this).

Tests

  1. I've updated unit tests in build.test.ts, default-handler.test.ts and default-handler-with-basepath.test.ts. I didn't want to duplicate a bunch of test code by making new files, so I reorganized some test structure and used Jest's describe.each to run the tests where trailingSlash = true and trailingSlash = false. This doubles the number of unit tests in default-handler test files as a result (but still runs fairly quickly). Please me know if there is a better/preferred way to do this.

  2. AWS testing: deployed both with and without trailingSlash=true and tested all cases (SSG, SSR, data files, public files). Note that files in /_next/static get an access denied when using a trailing slash, but it should because they have a separate CloudFront behavior for those S3 files. (Note: Vercel actually handles these redirects as well. Not sure if S3 supports removing trailing slash? But probably not very important, as no one will likely access these files directly.)

Test App

You can try it out here. I set trailingSlash = true, so all page URLs will redirect to the trailing slash version.

App link: https://dpjozqtv88tg7.cloudfront.net (this is my own AWS account, so I will take this down shortly after the PR is done)

SSR:

SSG:

Public File (always redirect to non-trailing slash URL, no matter value of trailingSlash):

Data File (always redirect to non-trailing slash URL, no matter value of trailingSlash):

Passing query strings:

I'll try to test other types of pages as well when I have some time.

Client (Static) files do not redirect, but will throw a 403 if you try to access with trailing slash (Next/Vercel handles this). This will return a 403:

You can compare the behavior to a Vercel app here: https://nextjs-repros.vercel.app/

The app code is here: https://github.com/dphang/nextjs-repros/tree/343454e1e1c38fc773d3ba4c45dd2d2bac4200dd

@danielcondemarin
Copy link
Contributor

Cheers for PR'ing @dphang 👍 I'll take a look at this tonight.

const nextConfigPath = path.join(this.nextConfigDir, "next.config.js");

if (await fse.pathExists(nextConfigPath)) {
const nextConfig = await import(nextConfigPath);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dphang Not all next.js configs would work with this. next.config.js can export an object { ... } , a Promise, or a function. Take a look at https://github.com/serverless-nextjs/serverless-next.js/blob/master/packages/libs/lambda-at-edge/src/lib/createServerlessConfig.ts#L10

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I see, thanks! I'll update this.

Copy link
Collaborator Author

@dphang dphang Aug 26, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I updated the code to handle next.config.js exporting a function. However I could not export a Promise as when doing Next.js build as part of the integ test, it gets an error: https://github.com/vercel/next.js/blob/master/errors/promise-in-next-config.md. Here's their related PR from Feb 2019: vercel/next.js#6476. I don't think a Promise export is supported by Next.js?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also did not see any existing tests where next.config.js exported a Promise, so not sure if this was ever actually supported/tested. @danielcondemarin do you have any example?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code example I sent I actually took from https://github.com/vercel/vercel/blob/master/packages/now-next/src/create-serverless-config.ts. Maybe it used to be supported at some point and can be removed.

Since I took the code from next's repo I only covered a few base cases (https://github.com/serverless-nextjs/serverless-next.js/tree/master/packages/libs/lambda-at-edge/tests/integration) but not a Promise.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, makes sense! Thanks.

@danielcondemarin
Copy link
Contributor

@dphang Looks really good. Thanks for detailing every step. I'm happy to merge once handling the different types of next.config is resolved 👍

@dphang
Copy link
Collaborator Author

dphang commented Aug 26, 2020

@dphang Looks really good. Thanks for detailing every step. I'm happy to merge once handling the different types of next.config is resolved 👍

Thanks for spending the time to review!

@danielcondemarin danielcondemarin merged commit ca63b80 into serverless-nextjs:master Aug 26, 2020
@marcin-piela-sf
Copy link

When can we expect it in alpha version? :)

@dphang dphang deleted the redirects-support branch August 27, 2020 17:26
@danielcondemarin
Copy link
Contributor

When can we expect it in alpha version? :)

Just published it as @sls-next/serverless-component@1.17.0-alpha.7. I've not tested it myself, please feedback if you find any issues 🙏

@dphang
Copy link
Collaborator Author

dphang commented Aug 27, 2020

When can we expect it in alpha version? :)

Just published it as @sls-next/serverless-component@1.17.0-alpha.7. I've not tested it myself, please feedback if you find any issues 🙏

Thanks! Yes, please try it out and let me know if there are issues. I've tested it with trailingSlash true on a test app (in the PR) and trailingSlash false (on my own app) and had no issues so far. Though I don't have a machine to test redirects with IE11 on Windows 7/8, but it's using the same refresh header as used by Vercel/Next so I assume it works. If someone can help test, that'd be great :)

@dphang
Copy link
Collaborator Author

dphang commented Aug 28, 2020

I did find a bug in this PR that breaks the entire deployment for apps using next-compose-plugins (and other plugins that does not handle having undefined default config) no matter if you are using trailingSlash config or not. Sorry that I did not catch this earlier.

Fixed it in this PR: #569

@marcin-piela-sf
Copy link

I see one problem after deploy, I have a subPath "test/dwa" and app returns 404 on:

I have "trailingSlash:false" in next.js config.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants