fix(pages-router): allow server-only in API routes#2234
Conversation
|
/bigbonk review for issues |
|
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 |
|
@james-elicx Bonk workflow was cancelled. View workflow run · To retry, trigger Bonk again. |
|
/bigbonk review for issues |
2 similar comments
|
/bigbonk review for issues |
|
/bigbonk review for issues |
There was a problem hiding this comment.
Review: fix(pages-router): allow server-only in API routes
I verified this change by reading the diff, tracing the taint-tracking logic, and running the test suite both with and without the fix wired up.
Verdict: Looks correct — ship it. Two minor (non-blocking) notes below.
What I verified
1. The fix is real and the test actually catches the regression. I temporarily stubbed isNeutralServerModule to always return false and re-ran tests/middleware-server-only.test.ts. The build failed with exactly the error this PR targets, for both the direct and transitive cases:
'server-only' cannot be imported in client build ('ssr' environment):
imported by lib/api-server-only.ts
imported by pages/api/server-only-edge.ts
imported by virtual:vinext-server-entry
imported by virtual:vinext-app-ssr-entry
Restoring the fix makes all 4 tests pass. So the new fixture genuinely exercises the new code path (not a trivially-passing test).
2. Taint propagation is correct for deep chains. The depth-1 case is seeded by isNeutralServerModule at the resolveId guard (line 118). Module X imported by the API route gets added to tainted via the this.resolve(...) + addTainted(resolved.id) path (lines 134-137). Beyond depth-1, the existing isTainted(importer) mechanism takes over, so transitive chains of arbitrary depth work. Confirmed by lib/api-server-only.ts (a transitive import) succeeding.
3. No weakening of the client-side server-only guard. I checked entries/pages-client-entry.ts:52-61 — Pages API routes are referenced in the client entry only as prefetch metadata (documentOnly: true), never imported as modules. API route code never enters the client graph, so vinext:validate-server-only-client-imports (index.ts:4392, client-environment-only) is untouched. The fix is correctly scoped to the SSR (non-RSC) environment only. No security regression.
4. isNeutralServerModule canonicalization is consistent. It calls canonicalizePageTransformPath(id) before isApiPage, which slices against canonicalPagesDir.length — matching the pattern already used at index.ts:4339-4343. Correct.
5. vp check passes on the changed files (format, lint, types clean).
Minor notes (non-blocking)
a. Stale doc comment. middleware-server-only.ts:38-42 still reads "The taint set is scoped to the middleware chain only…". After this change it also covers Pages API route chains. The summary paragraphs were updated to mention API routes, but this one wasn't. Worth a one-line tweak for future readers.
b. Test coverage only exercises runtime = "edge". The new fixture's API route sets export const runtime = "edge". Since isNeutralServerModule is runtime-agnostic (it only checks the pages/api path), a default Node-runtime API route takes the identical code path and would also pass — but it isn't covered explicitly. Not a correctness gap, just a coverage observation if you want belt-and-suspenders.
Parity note
I could not verify the Next.js v16.2.6 reference locally (.nextjs-ref is not present in this environment), so I'm taking the PR's parity claim at face value. The cited WEBPACK_LAYERS neutral/server-layer treatment of API routes is consistent with how Next.js handles server-only in non-client layers, and the behavior matches the documented intent.
|
Review posted to PR #2234. SummaryI reviewed the PR by reading the diff, tracing the taint-tracking logic, and empirically verifying the fix and test by toggling the change on/off. Verdict: Correct — ship it. No blocking issues found. Key findings (verified, not assumed)
Two minor, non-blocking notes
I flagged that I couldn't verify the Next.js v16.2.6 reference locally (no |
Summary
server-onlyimports without weakening validation for ordinary Pages client modulesNext.js parity
The pinned Next.js
v16.2.6reference (ee6e79b1792a4d401ddf2480f40a83549fe8e722) permitsserver-onlyin Pages API routes and modules imported by them.This fixes the 13 module-layer failures in deploy-suite run
27858251903, which all came from the same build abort while processingpages/api/server-only-edge.js.Validation
vp test run tests/middleware-server-only.test.ts— 4/4 passed with one workerNo broad local suite or raw Next.js E2E was run.