fix(pages): apply fallback rewrites after API misses - #2827
Conversation
commit: |
|
Performance benchmarksCompared 0 improved · 0 regressed · 6 within ±1.5%
View detailed results and traces 🟢 improvement · 🔴 regression · ⚫ change below 1.5% · paired base/head |
|
/bigbonk review for issues |
Review: fix(pages): apply fallback rewrites after API missesReviewed the full diff against Next.js execution-order semantics, dev/prod parity, and the middleware-context handoff protocol. The core fix is correct and well-tested. One latent correctness concern in the dev handoff guard, plus a couple of smaller notes. The fix is soundThe central change — gating The Test coverage is strong: real dev+prod Pages fixture (basePath, has/missing, local API precedence, merged query, headers, JSON body, external proxy method/body), and a hybrid App fixture whose fallback Concern: URL-equality guard conflates i18n locale stripping with a config rewrite (dev)
if (pipelineResult.apiUrl !== routeUrl) {
forwardInternalApiRewriteToApp(pipelineResult.apiUrl);
}
The URL-equality check is being used as a proxy for "did a config rewrite change the request?", but it also trips on i18n locale normalization (and potentially query-order/encoding normalization between Minor notes
Everything I ran is green: |
|
Review posted to PR #2827. SummaryI reviewed PR #2827 ( The fix is sound. The core change — gating One latent concern I flagged ( Minor notes: a duplicated All tests I ran are green: |
|
@james-elicx Bonk workflow failed. Check the logs for details. View workflow run · To retry, trigger Bonk again. |
|
/bigbonk review for issues |
|
UnknownError: ProviderInitError |
|
/bigbonk review for issues |
There was a problem hiding this comment.
Review: fix(pages): apply fallback rewrites after API misses
Re-reviewed after the second commit (64e3fba). The core fix is correct and well-tested, and the follow-up commit already resolves the i18n concern raised in the previous review by switching from the apiUrl !== routeUrl guard to pipelineResult.configRewriteFired. I ran the touched test suites and vp check; all green.
The fix is sound
Gating handleResolvedApiRoute() on deps.matchApiRoute (pages-request-pipeline.ts:635) so an /api/* filesystem miss continues through afterFiles/fallback rewrites instead of committing to the API 404 correctly mirrors Next.js's documented order (the API filesystem check runs before afterFiles/fallback rewrites; only a real match owns the request). Threading matchApiRoute through the pipeline deps, the generated pages-server-entry, pages-router-entry.ts, and prod-server.ts keeps dev / built-Node / Worker in parity.
The previous-review concern — that the dev handoff guard conflated i18n locale stripping with a config rewrite — is fixed in 64e3fba. index.ts:5666 now gates forwardInternalApiRewriteToApp on pipelineResult.configRewriteFired, and configRewriteFired is set to true only at actual rewrite sites (pipeline lines 602/702/756/816/902), never by stripI18nLocaleForApiRoute. So a locale-prefixed API request with no config rewrite no longer injects a phantom r: rewrite into the App RSC handoff. The new hybrid-i18n-api-handoff fixture asserts exactly this (/fr/api/direct → 404, /api/direct → 200) in both dev and production, and it's ported with a link to Next.js's i18n-api-support test.
The app-middleware.ts refactor is behaviorally equivalent on the normal path: applyMiddlewareRequestHeaders only reads the x-middleware-* protocol keys from its argument and mutates the global headers context, so passing context.requestHeaders (a copy of context.headers, which still carries those keys) produces the same result as the old applyMiddlewareRequestHeaders(context.headers). The new q branch correctly enables the request-header-only forwarded case (data.q carries the encoded protocol keys via encodeMiddlewareRequestHeaders), which the old if (context.headers) gate would have skipped.
Test coverage is strong: a real dev+prod Pages fixture (basePath, has/missing, local-API precedence, merged query, preserved method/headers/JSON body via an external upstream) and a hybrid App fixture whose fallback has condition keys on a cookie set by middleware — proving the rule is evaluated against post-middleware request-header overrides and the App handoff carries them.
Non-blocking: pre-existing i18n locale-prefixed-API divergence
Not introduced by this PR, but surfaced by the new matchApiRoute gate and worth tracking. Next.js's resolve-routes.ts (checkLocaleApi + the check_fs guard) explicitly skips the filesystem API check when the request carries an explicit locale prefix:
const checkLocaleApi = (pathname) =>
config.i18n && pathname === urlNoQuery &&
initialLocaleResult?.detectedLocale &&
pathHasPrefix(initialLocaleResult.pathname, '/api')That is exactly why Next.js's i18n-api-support e2e test expects /fr/api/hello (with pages/api/hello present) to fall through to the fallback rewrite (returns Example Domain), not to be served by the pages/api/hello handler. vinext's stripI18nLocaleForApiRoute instead strips the prefix whenever hadPrefix is true, so /fr/api/hello matches pages/api/hello and is served directly. The PR's new fixture only covers the no-rewrite case (/fr/api/direct → 404), which matches Next.js by coincidence (nothing claims it and nothing rewrites it), so the divergence isn't exercised.
This is a pre-existing behavior of stripI18nLocaleForApiRoute (unchanged by this PR), so it's not a blocker here — but consider a follow-up to port the "locale-prefixed API + fallback rewrite" case from Next.js's i18n-api-support test and align stripI18nLocaleForApiRoute with the detectedLocale-skips-filesystem-API semantics.
Minor notes
index.ts:5461(devmatchApiRouteclosure) andindex.ts:5626-5636(api-intent precedence branch) both do a pages→app precedence check but with intentionally different semantics: the closure returns any App match to keep the request flowing, while the branch appliespagesRouteHasPriorityOverAppRoute. Correct as-is; a one-line comment noting the two serve different purposes would help future readers.- The generated
matchApiRoute(pages-server-entry.ts:368) re-runsresolvePagesI18nRequeston aurlthe pipeline has already passed asapiLookupUrl(locale-stripped). Harmless (near no-op), just redundant.
Validation
tests/pages-request-pipeline.test.ts(99),tests/app-rsc-handler.test.ts+tests/app-router-next-config-dev.test.ts(187),tests/hybrid-i18n-api-handoff.test.ts(2, dev+prod),tests/deploy.test.ts(312) — all greenvp checkon all five touched source files — clean
|
Review posted to PR #2827. SummaryI reviewed PR #2827 ( The fix is sound. Gating The previous review's i18n concern is already fixed. The The One non-blocking finding I flagged: a pre-existing i18n divergence (not introduced by this PR). Next.js's Minor notes: two intentionally-different All tests I ran are green: |
Summary
Regression coverage
Validation
vp check