-
Notifications
You must be signed in to change notification settings - Fork 27k
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
Enforce absolute URLs in Edge Functions runtime #33410
Conversation
This comment has been minimized.
This comment has been minimized.
Does this mean that this breaking change will be introduced in 12.0.9 or 12.1.0? |
8cbd298
to
1d7549f
Compare
This comment has been minimized.
This comment has been minimized.
@matamatanot I corrected the PR to include a warning for now. We will land the change after some time but it will probably happen in a minor bump since we are warning the breaking changes are not covered by semver yet for Middleware. |
bb25df1
to
1b86aca
Compare
Stats from current PRDefault Build (Decrease detected ✓)General Overall increase
|
vercel/next.js canary | javivelasco/next.js absolute-urls | Change | |
---|---|---|---|
buildDuration | 18.7s | 19s | |
buildDurationCached | 4.2s | 4.1s | -96ms |
nodeModulesSize | 354 MB | 354 MB |
Page Load Tests Overall decrease ⚠️
vercel/next.js canary | javivelasco/next.js absolute-urls | Change | |
---|---|---|---|
/ failed reqs | 0 | 0 | ✓ |
/ total time (seconds) | 4.123 | 4.034 | -0.09 |
/ avg req/sec | 606.4 | 619.77 | +13.37 |
/error-in-render failed reqs | 0 | 0 | ✓ |
/error-in-render total time (seconds) | 2.134 | 2.185 | |
/error-in-render avg req/sec | 1171.27 | 1144.22 |
Client Bundles (main, webpack, commons)
vercel/next.js canary | javivelasco/next.js absolute-urls | Change | |
---|---|---|---|
450.HASH.js gzip | 179 B | 179 B | ✓ |
framework-HASH.js gzip | 42.2 kB | 42.2 kB | ✓ |
main-HASH.js gzip | 27.2 kB | 27.2 kB | ✓ |
webpack-HASH.js gzip | 1.44 kB | 1.44 kB | ✓ |
Overall change | 71 kB | 71 kB | ✓ |
Legacy Client Bundles (polyfills)
vercel/next.js canary | javivelasco/next.js absolute-urls | Change | |
---|---|---|---|
polyfills-HASH.js gzip | 31 kB | 31 kB | ✓ |
Overall change | 31 kB | 31 kB | ✓ |
Client Pages
vercel/next.js canary | javivelasco/next.js absolute-urls | Change | |
---|---|---|---|
_app-HASH.js gzip | 1.37 kB | 1.37 kB | ✓ |
_error-HASH.js gzip | 194 B | 194 B | ✓ |
amp-HASH.js gzip | 312 B | 312 B | ✓ |
css-HASH.js gzip | 326 B | 326 B | ✓ |
dynamic-HASH.js gzip | 2.37 kB | 2.37 kB | ✓ |
head-HASH.js gzip | 350 B | 350 B | ✓ |
hooks-HASH.js gzip | 919 B | 919 B | ✓ |
image-HASH.js gzip | 4.7 kB | 4.7 kB | ✓ |
index-HASH.js gzip | 263 B | 263 B | ✓ |
link-HASH.js gzip | 2.13 kB | 2.13 kB | ✓ |
routerDirect..HASH.js gzip | 321 B | 321 B | ✓ |
script-HASH.js gzip | 383 B | 383 B | ✓ |
withRouter-HASH.js gzip | 318 B | 318 B | ✓ |
85e02e95b279..7e3.css gzip | 107 B | 107 B | ✓ |
Overall change | 14.1 kB | 14.1 kB | ✓ |
Client Build Manifests
vercel/next.js canary | javivelasco/next.js absolute-urls | Change | |
---|---|---|---|
_buildManifest.js gzip | 459 B | 459 B | ✓ |
Overall change | 459 B | 459 B | ✓ |
Rendered Page Sizes
vercel/next.js canary | javivelasco/next.js absolute-urls | Change | |
---|---|---|---|
index.html gzip | 530 B | 530 B | ✓ |
link.html gzip | 544 B | 544 B | ✓ |
withRouter.html gzip | 525 B | 525 B | ✓ |
Overall change | 1.6 kB | 1.6 kB | ✓ |
Default Build with SWC (Increase detected ⚠️ )
General Overall increase ⚠️
vercel/next.js canary | javivelasco/next.js absolute-urls | Change | |
---|---|---|---|
buildDuration | 22.4s | 22.4s | -69ms |
buildDurationCached | 4.2s | 4.2s | -26ms |
nodeModulesSize | 354 MB | 354 MB |
Page Load Tests Overall increase ✓
vercel/next.js canary | javivelasco/next.js absolute-urls | Change | |
---|---|---|---|
/ failed reqs | 0 | 0 | ✓ |
/ total time (seconds) | 4.086 | 4.051 | -0.04 |
/ avg req/sec | 611.81 | 617.14 | +5.33 |
/error-in-render failed reqs | 0 | 0 | ✓ |
/error-in-render total time (seconds) | 2.184 | 2.178 | -0.01 |
/error-in-render avg req/sec | 1144.79 | 1147.81 | +3.02 |
Client Bundles (main, webpack, commons)
vercel/next.js canary | javivelasco/next.js absolute-urls | Change | |
---|---|---|---|
450.HASH.js gzip | 179 B | 179 B | ✓ |
framework-HASH.js gzip | 42.3 kB | 42.3 kB | ✓ |
main-HASH.js gzip | 27.3 kB | 27.3 kB | ✓ |
webpack-HASH.js gzip | 1.44 kB | 1.44 kB | ✓ |
Overall change | 71.3 kB | 71.3 kB | ✓ |
Legacy Client Bundles (polyfills)
vercel/next.js canary | javivelasco/next.js absolute-urls | Change | |
---|---|---|---|
polyfills-HASH.js gzip | 31 kB | 31 kB | ✓ |
Overall change | 31 kB | 31 kB | ✓ |
Client Pages
vercel/next.js canary | javivelasco/next.js absolute-urls | Change | |
---|---|---|---|
_app-HASH.js gzip | 1.35 kB | 1.35 kB | ✓ |
_error-HASH.js gzip | 180 B | 180 B | ✓ |
amp-HASH.js gzip | 305 B | 305 B | ✓ |
css-HASH.js gzip | 321 B | 321 B | ✓ |
dynamic-HASH.js gzip | 2.36 kB | 2.36 kB | ✓ |
head-HASH.js gzip | 342 B | 342 B | ✓ |
hooks-HASH.js gzip | 911 B | 911 B | ✓ |
image-HASH.js gzip | 4.73 kB | 4.73 kB | ✓ |
index-HASH.js gzip | 256 B | 256 B | ✓ |
link-HASH.js gzip | 2.19 kB | 2.19 kB | ✓ |
routerDirect..HASH.js gzip | 314 B | 314 B | ✓ |
script-HASH.js gzip | 375 B | 375 B | ✓ |
withRouter-HASH.js gzip | 309 B | 309 B | ✓ |
85e02e95b279..7e3.css gzip | 107 B | 107 B | ✓ |
Overall change | 14.1 kB | 14.1 kB | ✓ |
Client Build Manifests
vercel/next.js canary | javivelasco/next.js absolute-urls | Change | |
---|---|---|---|
_buildManifest.js gzip | 459 B | 459 B | ✓ |
Overall change | 459 B | 459 B | ✓ |
Rendered Page Sizes
vercel/next.js canary | javivelasco/next.js absolute-urls | Change | |
---|---|---|---|
index.html gzip | 533 B | 533 B | ✓ |
link.html gzip | 546 B | 546 B | ✓ |
withRouter.html gzip | 527 B | 527 B | ✓ |
Overall change | 1.61 kB | 1.61 kB | ✓ |
Thanks! Middleware is clearly marked as Beta, so I think it should be fine. |
We had a test where we expected the result from In our case in particular we read this from the headers Leaving this here, just in case someone bumps into a similar issue. |
@icyJoseph I think you should open an issue for that situation. |
Follow up from #33410. Adds version history for Middleware so it's more clear it launched in beta, and that we have a place to track changes.
We currently have inconsistencies when working with URLs in the Edge Functions runtime, this PR addresses them introducing a warning for inconsistent usage that will break in the future. Here is the reasoning. ### The Browser When we are in a browser environment there is a fixed location stored at `globalThis.location`. Then, if one tries to build a request with a relative URL it will work using that location global hostname as _base_ to construct its URL. For example: ```typescript // https://nextjs.org new Request('/test').url; // https://nextjs.org/test Response.redirect('/test').headers.get('Location'); // https://nextjs.org/test ``` However, if we attempt to run the same code from `about:blank` it would not work because the global to use as a base `String(globalThis.location)` is not a valid URL. Therefore a call to `Response.redirect('/test')` or `new Response('/test')` would fail. ### Edge Functions Runtime In Next.js Edge Functions runtime the situation is slightly different from a browser. Say that we have a root middleware (`pages/_middleware`) that gets invoked for every page. In the middleware file we expose the handler function and also define a global variable that we mutate on every request: ```typescript // pages/_middleware let count = 0; export function middleware(req: NextRequest) { console.log(req.url); count += 1; } ``` Currently we cache the module scope in the runtime so subsequent invocations would hold the same globals and the module would not be evaluated again. This would make the counter to increment for each request that the middleware handles. It is for this reason that we **can't have a global location** that changes across different invocations. Each invocation of the same function uses the same global which also holds primitives like `URL` or `Request` so changing an hypothetical `globalThis.location` per request would affect concurrent requests being handled. Then, it is not possible to use relative URLs in the same way the browser does because we don't have a global to rely on to use its host to compose a URL from a relative path. ### Why it works today We are **not** validating what is provided to, for example, `NextResponse.rewrite()` nor `NextResponse.redirect()`. We simply create a `Response` instance that adds the corresponding header for the rewrite or the redirect. Then it is **the consumer** the one that composes the final destination based on the request. Theoretically you can pass any value and it would fail on redirect but won't validate the input. Of course this is inconsistent because it doesn't make sense that `NextResponse.rewrite('/test')` works but `fetch(new NextRequest('/test'))` does not. Also we should validate what is provided. Finally, we want to be consistent with the way a browser behaves so `new Request('/test')` _should_ not work if there is no global location which we lack. ### What this PR does We will have to deprecate the usage of relative URLs in the previously mentioned scenarios. In preparation for it, this PR adds a validation function in those places where it will break in the future, printing a warning with a link that points to a Next.js page with an explanation of the issue and ways to fix it. Although middleware changes are not covered by semver, we will roll this for some time to make people aware that this change is coming. Then after a reasonable period of time we can remove the warning and make the code fail when using relative URLs in the previously exposed scenarios.
We currently have inconsistencies when working with URLs in the Edge Functions runtime, this PR addresses them introducing a warning for inconsistent usage that will break in the future. Here is the reasoning.
The Browser
When we are in a browser environment there is a fixed location stored at
globalThis.location
. Then, if one tries to build a request with a relative URL it will work using that location global hostname as base to construct its URL. For example:However, if we attempt to run the same code from
about:blank
it would not work because the global to use as a baseString(globalThis.location)
is not a valid URL. Therefore a call toResponse.redirect('/test')
ornew Response('/test')
would fail.Edge Functions Runtime
In Next.js Edge Functions runtime the situation is slightly different from a browser. Say that we have a root middleware (
pages/_middleware
) that gets invoked for every page. In the middleware file we expose the handler function and also define a global variable that we mutate on every request:Currently we cache the module scope in the runtime so subsequent invocations would hold the same globals and the module would not be evaluated again. This would make the counter to increment for each request that the middleware handles. It is for this reason that we can't have a global location that changes across different invocations. Each invocation of the same function uses the same global which also holds primitives like
URL
orRequest
so changing an hypotheticalglobalThis.location
per request would affect concurrent requests being handled.Then, it is not possible to use relative URLs in the same way the browser does because we don't have a global to rely on to use its host to compose a URL from a relative path.
Why it works today
We are not validating what is provided to, for example,
NextResponse.rewrite()
norNextResponse.redirect()
. We simply create aResponse
instance that adds the corresponding header for the rewrite or the redirect. Then it is the consumer the one that composes the final destination based on the request. Theoretically you can pass any value and it would fail on redirect but won't validate the input.Of course this is inconsistent because it doesn't make sense that
NextResponse.rewrite('/test')
works butfetch(new NextRequest('/test'))
does not. Also we should validate what is provided. Finally, we want to be consistent with the way a browser behaves sonew Request('/test')
should not work if there is no global location which we lack.What this PR does
We will have to deprecate the usage of relative URLs in the previously mentioned scenarios. In preparation for it, this PR adds a validation function in those places where it will break in the future, printing a warning with a link that points to a Next.js page with an explanation of the issue and ways to fix it.
Although middleware changes are not covered by semver, we will roll this for some time to make people aware that this change is coming. Then after a reasonable period of time we can remove the warning and make the code fail when using relative URLs in the previously exposed scenarios.