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

Fix middleware next data calls #60785

Closed
wants to merge 2 commits into from

Conversation

valkum
Copy link

@valkum valkum commented Jan 17, 2024

Fixes Bug

When the middleware is involved in rewriting URLs, the nextData handler will not return 404 when the buildId changed. This breaks the Link component which will soft-navigate with empty props.

This is broken since 13.4.13 where the handler logic was refactored. There was a fix applied which did not account for involved middlewares.

@ijjk
Copy link
Member

ijjk commented Jan 18, 2024

Stats from current PR

Default Build
General
vercel/next.js canary valkum/next.js fix-middleware-nextData-calls Change
buildDuration 12.8s 12.9s N/A
buildDurationCached 7.2s 6s N/A
nodeModulesSize 200 MB 200 MB N/A
nextStartRea..uration (ms) 421ms 428ms N/A
Client Bundles (main, webpack)
vercel/next.js canary valkum/next.js fix-middleware-nextData-calls Change
193.HASH.js gzip 181 B 182 B N/A
3f784ff6-HASH.js gzip 53.4 kB 53.4 kB
433-HASH.js gzip 29 kB 29 kB N/A
framework-HASH.js gzip 45.2 kB 45.2 kB
main-app-HASH.js gzip 239 B 242 B N/A
main-HASH.js gzip 31.8 kB 31.8 kB N/A
webpack-HASH.js gzip 1.7 kB 1.7 kB N/A
Overall change 98.6 kB 98.6 kB
Legacy Client Bundles (polyfills)
vercel/next.js canary valkum/next.js fix-middleware-nextData-calls Change
polyfills-HASH.js gzip 31 kB 31 kB
Overall change 31 kB 31 kB
Client Pages
vercel/next.js canary valkum/next.js fix-middleware-nextData-calls Change
_app-HASH.js gzip 194 B 195 B N/A
_error-HASH.js gzip 183 B 181 B N/A
amp-HASH.js gzip 504 B 502 B N/A
css-HASH.js gzip 321 B 321 B
dynamic-HASH.js gzip 2.5 kB 2.5 kB N/A
edge-ssr-HASH.js gzip 255 B 253 B N/A
head-HASH.js gzip 350 B 349 B N/A
hooks-HASH.js gzip 369 B 369 B
image-HASH.js gzip 4.18 kB 4.18 kB N/A
index-HASH.js gzip 255 B 256 B N/A
link-HASH.js gzip 2.61 kB 2.61 kB
routerDirect..HASH.js gzip 312 B 311 B N/A
script-HASH.js gzip 385 B 383 B N/A
withRouter-HASH.js gzip 307 B 308 B N/A
1afbb74e6ecf..834.css gzip 106 B 106 B
Overall change 3.4 kB 3.4 kB
Client Build Manifests
vercel/next.js canary valkum/next.js fix-middleware-nextData-calls Change
_buildManifest.js gzip 484 B 485 B N/A
Overall change 0 B 0 B
Rendered Page Sizes
vercel/next.js canary valkum/next.js fix-middleware-nextData-calls Change
index.html gzip 529 B 528 B N/A
link.html gzip 541 B 541 B
withRouter.html gzip 524 B 523 B N/A
Overall change 541 B 541 B
Edge SSR bundle Size
vercel/next.js canary valkum/next.js fix-middleware-nextData-calls Change
edge-ssr.js gzip 93.9 kB 93.9 kB N/A
page.js gzip 148 kB 148 kB N/A
Overall change 0 B 0 B
Middleware size
vercel/next.js canary valkum/next.js fix-middleware-nextData-calls Change
middleware-b..fest.js gzip 624 B 627 B N/A
middleware-r..fest.js gzip 151 B 151 B
middleware.js gzip 37.5 kB 37.5 kB N/A
edge-runtime..pack.js gzip 1.92 kB 1.92 kB
Overall change 2.07 kB 2.07 kB
Next Runtimes
vercel/next.js canary valkum/next.js fix-middleware-nextData-calls Change
app-page-exp...dev.js gzip 169 kB 169 kB
app-page-exp..prod.js gzip 95.6 kB 95.6 kB
app-page-tur..prod.js gzip 96.3 kB 96.3 kB
app-page-tur..prod.js gzip 90.8 kB 90.8 kB
app-page.run...dev.js gzip 142 kB 142 kB
app-page.run..prod.js gzip 90.2 kB 90.2 kB
app-route-ex...dev.js gzip 24.2 kB 24.2 kB
app-route-ex..prod.js gzip 16.8 kB 16.8 kB
app-route-tu..prod.js gzip 16.8 kB 16.8 kB
app-route-tu..prod.js gzip 16.4 kB 16.4 kB
app-route.ru...dev.js gzip 23.6 kB 23.6 kB
app-route.ru..prod.js gzip 16.4 kB 16.4 kB
pages-api-tu..prod.js gzip 9.39 kB 9.39 kB
pages-api.ru...dev.js gzip 9.67 kB 9.67 kB
pages-api.ru..prod.js gzip 9.39 kB 9.39 kB
pages-turbo...prod.js gzip 22 kB 22 kB
pages.runtim...dev.js gzip 22.6 kB 22.6 kB
pages.runtim..prod.js gzip 22 kB 22 kB
server.runti..prod.js gzip 49.7 kB 49.7 kB N/A
Overall change 894 kB 894 kB
Diff details
Diff for page.js

Diff too large to display

Diff for edge-ssr.js

Diff too large to display

Diff for server.runtime.prod.js

Diff too large to display

Commit: e5411e1

@ijjk
Copy link
Member

ijjk commented Jan 18, 2024

Failing test suites

Commit: e5411e1

pnpm test-dev test/e2e/skip-trailing-slash-redirect/index.test.ts (PPR)

  • skip-trailing-slash-redirect > should handle external rewrite correctly /chained-rewrite-ssg
  • skip-trailing-slash-redirect > should handle external rewrite correctly /chained-rewrite-static
  • skip-trailing-slash-redirect > should handle external rewrite correctly /chained-rewrite-ssr
  • skip-trailing-slash-redirect > should handle external rewrite correctly /docs/first
  • skip-trailing-slash-redirect > should handle external rewrite correctly /docs-ssr/first
  • skip-trailing-slash-redirect > should allow rewriting invalid buildId correctly
Expand output

● skip-trailing-slash-redirect › should handle external rewrite correctly /chained-rewrite-ssg

expect(received).toBe(expected) // Object.is equality

Expected: 1
Received: undefined

  205 |       }, 'success')
  206 |
> 207 |       expect(await browser.eval('window.beforeNav')).toBe(1)
      |                                                      ^
  208 |     }
  209 |   )
  210 |

  at toBe (e2e/skip-trailing-slash-redirect/index.test.ts:207:54)

● skip-trailing-slash-redirect › should handle external rewrite correctly /chained-rewrite-static

TIMED OUT: success

undefined

Error: expect(received).toEqual(expected) // deep equality

- Expected  - 3
+ Received  + 1

- Object {
-   "slug": "first",
- }
+ Object {}

  636 |
  637 |   if (hardError) {
> 638 |     throw new Error('TIMED OUT: ' + regex + '\n\n' + content + '\n\n' + lastErr)
      |           ^
  639 |   }
  640 |   return false
  641 | }

  at check (lib/next-test-utils.ts:638:11)
  at e2e/skip-trailing-slash-redirect/index.test.ts:201:7

● skip-trailing-slash-redirect › should handle external rewrite correctly /chained-rewrite-ssr

expect(received).toBe(expected) // Object.is equality

Expected: 1
Received: undefined

  205 |       }, 'success')
  206 |
> 207 |       expect(await browser.eval('window.beforeNav')).toBe(1)
      |                                                      ^
  208 |     }
  209 |   )
  210 |

  at toBe (e2e/skip-trailing-slash-redirect/index.test.ts:207:54)

● skip-trailing-slash-redirect › should handle external rewrite correctly /docs/first

expect(received).toBe(expected) // Object.is equality

Expected: 1
Received: undefined

  205 |       }, 'success')
  206 |
> 207 |       expect(await browser.eval('window.beforeNav')).toBe(1)
      |                                                      ^
  208 |     }
  209 |   )
  210 |

  at toBe (e2e/skip-trailing-slash-redirect/index.test.ts:207:54)

● skip-trailing-slash-redirect › should handle external rewrite correctly /docs-ssr/first

expect(received).toBe(expected) // Object.is equality

Expected: 1
Received: undefined

  205 |       }, 'success')
  206 |
> 207 |       expect(await browser.eval('window.beforeNav')).toBe(1)
      |                                                      ^
  208 |     }
  209 |   )
  210 |

  at toBe (e2e/skip-trailing-slash-redirect/index.test.ts:207:54)

● skip-trailing-slash-redirect › should allow rewriting invalid buildId correctly

expect(received).toBe(expected) // Object.is equality

Expected: 200
Received: 404

  220 |       }
  221 |     )
> 222 |     expect(res.status).toBe(200)
      |                        ^
  223 |     expect(await res.text()).toContain('Example Domain')
  224 |
  225 |     if (!(global as any).isNextDeploy) {

  at Object.toBe (e2e/skip-trailing-slash-redirect/index.test.ts:222:24)

Read more about building and testing Next.js in contributing.md.

process.env.NEXT_RUNTIME !== 'edge' &&
req.headers['x-middleware-invoke']
) {
return false
Copy link
Member

Choose a reason for hiding this comment

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

Removing this breaks the functionality that allows middleware to rewrite _next/data paths for a different project/deployment.

Instead of changing this, we should ensure we 404 properly after middleware is invoked, we didn't rewrite/redirect, and the buildId doesn't match at that point.

Copy link
Author

@valkum valkum Jan 18, 2024

Choose a reason for hiding this comment

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

First, I am sorry about the failing test cases. I tried to get pnpm build and pnpm test-start working in 3 different envs (macOS (with volta installed), the repo's devcontainer, and a ubuntu VM) with pnpm build complaining about missing dependencies after pnpm i).

Instead of changing this, we should ensure we 404 properly after middleware is invoked, we didn't rewrite/redirect, and the buildId doesn't match at that point.

But the case of a successful rewrite is the culprit here. next.js needs to properly 404 after middleware is invoked, the buildId is different and a rewrite happened.

Clicking Link after a deployment with a different buildId will do the following:
Request to _next/data/....json is made with:

'x-middleware-prefetch': '1'
'x-nextjs-data': '1'

Middleware will rewrite that and will add

'x-invoke-path': '',
'x-invoke-query': '',
'x-invoke-output': '',
'x-middleware-invoke': '1'

before normalizeAndAttachMetadata and thus handleNextDataRequest is called.

Based on these headers the issue is in the branch where handleCatchallRenderRequest is called.
I am not sure if this is the only branch that is affected, though, as I am not very familiar with "middleware to rewrite _next/data paths for a different project/deployment."

What about letting normalizeAndAttachMetadata return [complete, buildIdMismatch], and before handleCatchallRenderRequest check if buildIdMismatch is true and render 404 in that case before executing handleCatchallRenderRequest?

I can provide a reproducible case if you want.

@valkum valkum force-pushed the fix-middleware-nextData-calls branch from e5411e1 to 1b6e0f4 Compare January 20, 2024 19:26
ijjk added a commit that referenced this pull request Feb 5, 2024
### What?
Currently, when a middleware is active and the path results in a 404,
the server responds with a 200 status code to data requests. We propose
a change to ensure that a 404 status code is returned in these
situations, as expected, solving production issues for the navigation
router.

### Why?
The client data requests (identified with `_next/data` path &
`x-nextjs-data` header) get answered the status code 200. The code below
is executed when there is a version skew for the data requests (e.g
prefetch in production).


https://github.com/vercel/next.js/blob/4125069840ca98981f0e7796f55265af04f3e903/packages/next/src/server/lib/router-server.ts#L217-L227

The version skew consists in the client side version identified with the
buildId in `__NEXT_DATA__ ` tag to be obsolete compared to the server
version (`BUILD_ID` file).

In the case of prefetching, this leads to the code above being executed,
therefore the `prefetch-reducer.ts` handles the response as valid and
sets it in its cache. Which ultimately triggers a navigation with empty
prop, resulting in erroneous behaviours reported in issues and in our
production websites:

https://github.com/vercel/next.js/blob/4125069840ca98981f0e7796f55265af04f3e903/packages/next/src/client/components/router-reducer/reducers/prefetch-reducer.ts#L54-L74

By switching the response to a 404, we trigger this code in the fetch
(`fetchServerResponse`). This change prompts a hard navigation
(mpaNavigation), effectively refreshing the client version and
resynching it with the server version.

https://github.com/vercel/next.js/blob/4125069840ca98981f0e7796f55265af04f3e903/packages/next/src/client/components/router-reducer/fetch-server-response.ts#L125-L134

### How?
We simply update the status code to 404 here:

https://github.com/vercel/next.js/blob/4125069840ca98981f0e7796f55265af04f3e903/packages/next/src/server/lib/router-server.ts#L223

Closes: #60785
Closes: #59295
Fixes: #47516

---------

Co-authored-by: JJ Kasper <jj@jjsweb.site>
@ijjk ijjk closed this in #60968 Feb 5, 2024
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Feb 20, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants