Skip to content

fix(pages-router): pass revalidateReason to gssp/getStaticProps context#1573

Draft
james-elicx wants to merge 1 commit into
mainfrom
fix/gssp-revalidate-reason
Draft

fix(pages-router): pass revalidateReason to gssp/getStaticProps context#1573
james-elicx wants to merge 1 commit into
mainfrom
fix/gssp-revalidate-reason

Conversation

@james-elicx
Copy link
Copy Markdown
Member

Closes #1462

Summary

  • getServerSideProps and getStaticProps context now includes revalidateReason ('build', 'on-demand', or 'stale') matching Next.js.

Test plan

  • Test asserts the three reason values on respective triggers
  • vp check passes

Pages Router `getStaticProps` now receives `context.revalidateReason`
matching Next.js: `'build'` during build-time prerendering,
`'on-demand'` when the dispatch is signalled as an explicit revalidation,
and `'stale'` for stale-while-revalidate background regeneration and
runtime cache-miss requests.

Closes #1462
@james-elicx
Copy link
Copy Markdown
Member Author

/bigbonk review

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 22, 2026

Open in StackBlitz

npm i https://pkg.pr.new/vinext@1573

commit: 055a80a

@github-actions
Copy link
Copy Markdown

Example Preview Production Original
app-router-cloudflare preview production
pages-router-cloudflare preview production
app-router-playground preview production original
realworld-api-rest preview production
nextra-docs-template preview production
benchmarks preview production
hackernews preview production original
web preview production

Copy link
Copy Markdown
Contributor

@ask-bonk ask-bonk Bot left a comment

Choose a reason for hiding this comment

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

Review — revalidateReason + gSSP Cache-Control

revalidateReason implementation

The core revalidateReason implementation in pages-page-data.ts is correct. The tri-state resolution logic mirrors Next.js render.tsx faithfully:

  • "on-demand" when isOnDemandRevalidate is set
  • "build" when isBuildTimePrerendering is set (via VINEXT_PRERENDER=1)
  • "stale" as the runtime default (cache-miss and SWR background regeneration)

The type narrowing on the getStaticProps context, the isBuildTimePrerendering wiring in the generated entry, and the hardcoded "stale" for SWR background regen paths are all correct. Good tests covering all three branches plus the SWR path.

Two issues to address

1. PR title says "gssp" but the fix is for getStaticProps

The PR title is fix(pages-router): pass revalidateReason to gssp/getStaticProps context, but revalidateReason is only added to getStaticProps, not getServerSideProps. This is correct behavior (Next.js doesn't put revalidateReason on the gSSP context — it's an ISR concept), but the title is misleading. The /getStaticProps part is accurate; the gssp part should be dropped.

2. The gSSP default Cache-Control fix creates a dev/prod parity gap

The diff includes an unrelated fix (referencing #1461) that adds Cache-Control: private, no-cache, no-store, max-age=0, must-revalidate as the default for getServerSideProps responses in the dev server. This is correct behavior (matches Next.js pages-handler.ts), but the corresponding production path does not get the same treatment.

The production flow is: pages-server-entry.tsresolvePagesPageData()renderPagesPageResponse()applyGsspHeaders(). Neither renderPagesPageResponse nor the isDataReq JSON short-circuit path add a default Cache-Control for gSSP responses when the user didn't set one. This means:

  • Dev: gSSP responses get private, no-cache, no-store, max-age=0, must-revalidate
  • Prod: gSSP responses get no Cache-Control header at all ✗

Per AGENTS.md: "When fixing a bug in any of these files, check whether the same bug exists in the others. Do not leave known bugs as follow-ups — fix them in the same PR."

The fix belongs in pages-page-response.ts's renderPagesPageResponse — add an else branch after the scriptNonce and isrRevalidateSeconds Cache-Control cases for when gsspRes is present and no Cache-Control has been set yet. The isDataReq path in pages-server-entry.ts (lines 645–655) should also get the default.

Minor notes

  • isOnDemandRevalidate is forward-looking plumbing — no caller sets it today (res.revalidate() is not implemented yet). That's fine as typed infrastructure, but worth noting in a comment so future readers know it's intentional dead code.
  • The tests are well-structured. The SWR regeneration test that passes isBuildTimePrerendering: true alongside a stale cache hit is a particularly good edge case — it verifies the SWR path always uses "stale" regardless of the outer dispatch context.

if (!hasUserCacheControl) {
gsspExtraHeaders["Cache-Control"] =
"private, no-cache, no-store, max-age=0, must-revalidate";
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This default Cache-Control fix is correct for dev, but the production path (renderPagesPageResponse in pages-page-response.ts) doesn't set any default Cache-Control for gSSP responses. This creates a dev/prod parity gap — dev returns private, no-cache, no-store, ... while prod returns no Cache-Control at all.

The fix should also go in pages-page-response.ts (after the scriptNonce and isrRevalidateSeconds branches) and the isDataReq JSON short-circuit in pages-server-entry.ts.

Also, this is an unrelated fix (referencing #1461) bundled into a PR whose title and description only mention revalidateReason (#1462). Consider either splitting it into its own PR or updating the PR description to mention both fixes.

// Mirrors Next.js's \`renderOpts.isBuildTimePrerendering\`. See
// \`.nextjs-ref/packages/next/src/server/render.tsx\` and
// \`packages/vinext/src/build/prerender.ts\`.
isBuildTimePrerendering: typeof process !== "undefined" && process.env && process.env.VINEXT_PRERENDER === "1",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

isOnDemandRevalidate is not passed here, so the "on-demand" branch in resolvePagesPageData is currently unreachable. That's fine as forward-looking plumbing, but consider adding a brief comment noting that on-demand revalidation (res.revalidate()) is not yet wired up — it will save someone from wondering why it's never set.

@ask-bonk
Copy link
Copy Markdown
Contributor

ask-bonk Bot commented May 22, 2026

Review posted on PR #1573. Here's a summary of the findings:

revalidateReason (the stated fix)

The core implementation is correct — the tri-state resolution ("build" / "on-demand" / "stale") mirrors Next.js render.tsx faithfully, and the tests cover all three branches plus the SWR edge case. The prod server inherits the fix automatically through resolvePagesPageData.

Two issues flagged

  1. Misleading PR title — says "gssp/getStaticProps" but revalidateReason is only added to getStaticProps (correct per Next.js — it's an ISR concept, not a gSSP concept). The "gssp" part should be dropped.

  2. Dev/prod parity gap on the bundled Cache-Control fix — the unrelated Pages Router: gssp/getStaticProps responses missing default and user-set Cache-Control header #1461 fix adds a default Cache-Control: private, no-cache, no-store, max-age=0, must-revalidate for gSSP responses in the dev server, but the production path (renderPagesPageResponse in pages-page-response.ts) doesn't get the same default. This means dev gSSP responses get the correct no-cache header while prod responses get no Cache-Control at all.

github run

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Pages Router: context.revalidateReason not passed to getStaticProps/getServerSideProps

1 participant