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
feat(lambda-at-edge): support trailing slash / non-trailing slash redirects #556
Conversation
...ge/tests/integration/with-trailing-slash-next-config/with-trailing-slash-next-config.test.ts
Outdated
Show resolved
Hide resolved
packages/libs/lambda-at-edge/tests/default-handler/default-handler.test.ts
Show resolved
Hide resolved
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); |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
@dphang Looks really good. Thanks for detailing every step. I'm happy to merge once handling the different types of |
Thanks for spending the time to review! |
When can we expect it in alpha version? :) |
Just published it as |
Thanks! Yes, please try it out and let me know if there are issues. I've tested it with |
I did find a bug in this PR that breaks the entire deployment for apps using Fixed it in this PR: #569 |
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. |
Description
This adds support for server-side redirects (via Lambda origin request handler) based on value of
trailingSlash
innext.config.js
, or by defaulttrailingSlash = 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!
SSR/SSG pages: if
trailingSlash
is set totrue
, then URLs likeexample.com/mypage
get redirected toexample.com/mypage/
.SSR/SSG pages: if
trailingSlash
is set tofalse
or not set (i.e default behavior), then URLs likeexample.com/mypage/
get redirected toexample.com/mypage
.Query string is also passed along: e.g if
trailingSlash
istrue
thenexample.com/mypage?abc=123
is redirected toexample.com/mypage/?abc=123
. Vice-versa iftrailingSlash
isfalse
.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
I've updated unit tests in
build.test.ts
,default-handler.test.ts
anddefault-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'sdescribe.each
to run the tests wheretrailingSlash = true
andtrailingSlash = false
. This doubles the number of unit tests indefault-handler
test files as a result (but still runs fairly quickly). Please me know if there is a better/preferred way to do this.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