fix(pages): pass req res to document initial props - #2402
Conversation
|
/bigbonk review for issues |
commit: |
|
6015563 to
777087f
Compare
|
/bigbonk review for issues |
There was a problem hiding this comment.
Review: fix(pages): pass req res to document initial props
Reviewed the changes across dev-server.ts, pages-document-initial-props.ts, pages-page-data.ts, pages-page-handler.ts, pages-page-response.ts, and the two test files. The core change is correct and well-scoped: threading the shared Pages req/res into _document.getInitialProps, falling back to a freshly-created req/res for plain routes, and folding the document res into header/status application.
I verified:
gsspResanddocumentReqRes.resare the same object on gSSP/gIP routes (gsspRes = resatpages-page-data.ts:815, both sourced fromgetSharedReqRes()), sogsspRes ?? documentReqRes?.resinapplyGsspHeadersnever drops headers set by_documenton those routes.- The
statusCode = 200->statusCode ?? res.statusCodechange indev-server.tsis sound: Node'sServerResponse.statusCodedefaults to 200, so no regression, and it now lets_document.getInitialPropsinfluence status in dev to match prod. vp checkis clean and the cited tests pass in both dev and prod.
A few issues worth addressing:
1. Dev/prod parity gap: dev does not honor a response sent from _document.getInitialProps
In prod, pages-page-response.ts:531-533 returns early if _document.getInitialProps sent a response via res:
if (options.documentReqRes?.res.headersSent && options.documentReqRes.responsePromise) {
return options.documentReqRes.responsePromise;
}The dev path (dev-server.ts streamPageToResponse) has no equivalent guard. In dev the res passed in documentContext (dev-server.ts:1734-1735) is the real Node ServerResponse. If a user's _document.getInitialProps calls res.end()/res.json()/res.writeHead(), dev will still fall through to res.writeHead(statusCode ?? res.statusCode, headers) at dev-server.ts:359 and throw ERR_HTTP_HEADERS_SENT, while prod returns the already-sent response.
This is an edge case (sending a response from _document.getInitialProps is unusual, and the new tests only exercise setHeader/statusCode, not res.end()), but per the repo's dev/prod parity rule it should either be handled in dev too or explicitly documented as a known divergence. Worth confirming Next.js behavior here first.
2. Missing integration coverage for the headline "plain Pages route" path
The PR summary's main new behavior is "create a document req/res for plain Pages routes that do not already run gSSP, _app.getInitialProps, or page getInitialProps" — i.e. the pageDataResult.documentReqRes ?? createPageReqRes() fallback at pages-page-handler.ts:647.
Neither new test exercises that fallback:
tests/pages-router.test.tsuses anindex.tsxfixture that definesgetServerSideProps(tests/pages-router.test.ts:5715), sodocumentReqResis always the shared gSSP req/res —createPageReqRes()is never hit.tests/pages-page-response.test.tspassesdocumentReqResdirectly, bypassing the handler entirely.
Consider adding a plain static page (no gSSP/gIP/_app.getInitialProps) whose _document.getInitialProps reads ctx.req.cookies and sets ctx.res.setHeader/statusCode, to lock in the new fallback path end-to-end.
3. Stale comment wording
pages-document-initial-props.ts:202-203 now says req/res are provided "in SSR paths". With this PR, req/res are passed on all Pages render paths (including plain static pages via the createPageReqRes() fallback), not just SSR. Suggest tweaking the comment to avoid implying the fallback path is excluded.
Minor
- The
responsePromisefield onPagesDocumentReqResis optional, so the guard atpages-page-response.ts:531isheadersSent && responsePromise. AheadersSent-but-no-responsePromiseres silently re-renders. This is only reachable via a test mock today (real producers always setresponsePromise), so it's not a bug — just noting the guard's behavior.
Overall the implementation logic is correct; the main actionable items are the dev-path parity check (#1) and integration coverage for the plain-route fallback (#2).
|
Review posted to PR #2402. Summary of what I found: Review summaryThe core change is correct and well-scoped. I verified the key invariant that makes it safe: on gSSP/gIP routes, Issues flagged:
Plus a minor note about the optional No blocking correctness bugs in the logic itself — the actionable items are the dev-path parity check and the integration test gap. |
Performance benchmarksCompared 0 improved · 0 regressed · 6 within ±1.5%
View detailed results and traces 🟢 improvement · 🔴 regression · ⚫ change below 1.5% · paired base/head |
777087f to
00dc418
Compare
|
/bigbonk review for issues |
|
@james-elicx Bonk workflow was cancelled. View workflow run · To retry, trigger Bonk again. |
00dc418 to
8e637ad
Compare
|
/bigbonk review for issues |
There was a problem hiding this comment.
Review: fix(pages): pass req res to document initial props
Re-reviewed at HEAD 8e637ad. This commit is more complete than the version the earlier bot review saw — it already resolves all three substantive items that prior review flagged. Verified by reading every changed file, tracing the data flow, and running the cited tests + vp check.
Verified correct
-
Dev/prod parity for early responses (prior issue #1 — now fixed).
dev-server.ts:251addsif (res.headersSent || res.writableEnded) return;right afterrunDocumentRenderPage, mirroring the prod early-return atpages-page-response.ts:531-533. A_document.getInitialPropsthat callsres.end()now leaves the already-sent response intact in both dev and prod instead of throwingERR_HTTP_HEADERS_SENTin dev. The new test"honors _document.getInitialProps responses that end early in %s"exercisesres.end("DOCUMENT ENDED")+statusCode = 203in both modes and passes. -
Integration coverage for the new paths (prior issue #2 — now fixed). Added fixtures
static.tsx(auto-export) andstatic-gsp.tsx(getStaticProps), plus dev+prod tests:"passes req/res into _document.getInitialProps in %s"(gSSP route)"passes req/res into _document.getInitialProps for getStaticProps pages in %s"— this is the path that actually hitscreatePageReqRes()(pages-page-handler.ts:650), since a gSP page served from ageneratedFromDataRequestcache HIT never callsgetSharedReqRes(), sopageDataResult.documentReqResisnull."omits req/res from _document.getInitialProps for auto-export pages in %s"— locks in theautoExport === true → nullgate (has-resvsmissing-resassertion).
-
Stale comment (prior issue #3 — now fixed).
pages-document-initial-props.tscomment rewritten to say renderers pass "the request-scoped context they can provide (pathname/query/asPath plus req/res when available)". -
Header/status fallback is safe. On gSSP/gIP routes
gsspResanddocumentReqRes.resare the same object (gsspRes = resatpages-page-data.ts:815, both fromgetSharedReqRes()), sooptions.gsspRes ?? options.documentReqRes?.resinapplyGsspHeaders(pages-page-response.ts:604-608) never drops document-set headers. ThefinalStatuscorrectly feeds the ISR cache write. -
statusCode ?? res.statusCodeis sound. Node'sServerResponse.statusCodedefaults to 200, so dropping thestatusCode = 200default preserves behavior while letting_documentinfluence status in dev (matching prod). -
autoExportgating is consistent across dev (dev-server.ts:1735) and prod (pages-page-handler.ts:648). Both pass req/res only when the page has a data method (!autoExport), matching Next.js, which renders auto-export documents without request-scoped req/res. -
vp checkclean on all five source files; all 22_documenttests intests/pages-router.test.tsand the targetedpages-page-response.tstest pass.
Non-blocking notes
-
PR summary wording is slightly inaccurate. It says the fallback covers "plain Pages routes that do not already run gSSP,
_app.getInitialProps, or pagegetInitialProps". But such pure-static pages areautoExport === true, which short-circuitsdocumentReqRestonullbefore the?? createPageReqRes()fallback. The fallback is actually reached for gSP pages served from a data-request-generated cache HIT (wheregetSharedReqRes()was never invoked). Behavior is correct; only the description is misleading. Worth tightening for the changelog. -
Minor (carried from prior review, still accurate): the prod guard at
pages-page-response.ts:531isheadersSent && responsePromise. AheadersSent-but-no-responsePromiseres would silently re-render, butcreatePagesReqRes()always setsresponsePromise(pages-node-compat.ts:455), so this is only reachable via test mocks — not a real-world bug.
Verdict
No blocking issues. Logic is correct, dev/prod parity holds, and coverage now exercises gSSP, gSP, auto-export, and early-res.end() paths in both runtimes. Recommend tightening the PR/commit description's "plain Pages route" wording before merge.
|
Review posted to PR #2402. Summary of my findings: Review summaryI re-reviewed at HEAD
I verified the header/status fallback is safe ( Non-blocking note: The PR summary's "plain Pages routes" description is slightly inaccurate — pure-static pages are Verdict: No blocking issues. |
Summary
_document.getInitialPropsduring production HTML renders_app.getInitialProps, or pagegetInitialProps_documentValidation
vp test run tests/pages-page-response.test.ts -t "passes req/res into _document"vp test run tests/pages-router.test.ts -t "passes req/res into _document"vp test run tests/pages-router.test.ts -t "Pages _document renderPage enhancers"vp test run tests/pages-page-response.test.tsvp check packages/vinext/src/server/dev-server.ts packages/vinext/src/server/pages-document-initial-props.ts packages/vinext/src/server/pages-page-data.ts packages/vinext/src/server/pages-page-handler.ts packages/vinext/src/server/pages-page-response.ts tests/pages-page-response.test.ts tests/pages-router.test.ts