From 4da1919247f61dfc4469645c34057cf06b56785c Mon Sep 17 00:00:00 2001 From: James Date: Sun, 21 Jun 2026 20:25:19 +0100 Subject: [PATCH 1/4] fix(pages): align gssp not-found data parity --- packages/vinext/src/server/dev-server.ts | 6 +-- packages/vinext/src/server/pages-page-data.ts | 19 ++------- packages/vinext/src/server/pages-readiness.ts | 8 ++-- packages/vinext/src/shims/router.ts | 23 ++++++++++ .../e2e/pages-router-prod/production.spec.ts | 27 ++++++++++++ .../pages-basic/pages/gssp-non-json.tsx | 11 +++++ .../pages-basic/pages/gssp-not-found.tsx | 9 ++++ .../pages/gssp-not-found/[slug].tsx | 9 ++++ tests/fixtures/pages-basic/pages/index.tsx | 6 +++ tests/pages-page-data.test.ts | 42 +++++++++---------- tests/pages-readiness.test.ts | 27 ++++++++++++ 11 files changed, 144 insertions(+), 43 deletions(-) create mode 100644 tests/fixtures/pages-basic/pages/gssp-non-json.tsx create mode 100644 tests/fixtures/pages-basic/pages/gssp-not-found.tsx create mode 100644 tests/fixtures/pages-basic/pages/gssp-not-found/[slug].tsx create mode 100644 tests/pages-readiness.test.ts diff --git a/packages/vinext/src/server/dev-server.ts b/packages/vinext/src/server/dev-server.ts index 82f060b89..6bddd2b80 100644 --- a/packages/vinext/src/server/dev-server.ts +++ b/packages/vinext/src/server/dev-server.ts @@ -758,7 +758,7 @@ export function createSSRHandler( }; if (deploymentId) notFoundHeaders[NEXTJS_DEPLOYMENT_ID_HEADER] = deploymentId; res.writeHead(404, notFoundHeaders); - res.end("{}"); + res.end('{"notFound":true}'); return; } await renderErrorPage( @@ -906,7 +906,7 @@ export function createSSRHandler( }; if (deploymentId) notFoundHeaders[NEXTJS_DEPLOYMENT_ID_HEADER] = deploymentId; res.writeHead(404, notFoundHeaders); - res.end("{}"); + res.end('{"notFound":true}'); return; } await renderErrorPage( @@ -1342,7 +1342,7 @@ export function createSSRHandler( }; if (deploymentId) notFoundHeaders[NEXTJS_DEPLOYMENT_ID_HEADER] = deploymentId; res.writeHead(404, notFoundHeaders); - res.end("{}"); + res.end('{"notFound":true}'); return; } await renderErrorPage( diff --git a/packages/vinext/src/server/pages-page-data.ts b/packages/vinext/src/server/pages-page-data.ts index 1a8b2300c..4226c1c5e 100644 --- a/packages/vinext/src/server/pages-page-data.ts +++ b/packages/vinext/src/server/pages-page-data.ts @@ -268,15 +268,16 @@ type ResolvePagesPageDataResult = function buildPagesDataNotFoundResponse(deploymentId?: string): Response { // Matches Next.js: `/_next/data//.json` 404 responses use - // application/json with an empty object body so clients can call - // `res.json()` without throwing before inspecting the status code. + // application/json with a notFound marker so the client router can render + // the configured 404 page without hard-navigating. Missing routes/build IDs + // still use the empty-object deploy-skew response in the outer handlers. // Mirror Next.js pages-handler.ts: set x-nextjs-deployment-id on all // `_next/data` notFound exits so the client can detect a new deployment. const headers: Record = { "Content-Type": "application/json" }; if (deploymentId) { headers[NEXTJS_DEPLOYMENT_ID_HEADER] = deploymentId; } - return new Response("{}", { + return new Response('{"notFound":true}', { status: 404, headers, }); @@ -741,18 +742,6 @@ export async function resolvePagesPageData( return buildPagesNotFoundResult(options); } - // Mirrors Next.js render.tsx's `isSerializableProps(pathname, "getServerSideProps", data.props)` - // check, gated on `!metadata.isRedirect && !metadata.isNotFound` (both - // short-circuit above). Throws a friendly `SerializableError` so the - // caller's existing try/catch surfaces a clear 500 instead of rendering - // an empty page. See - // .nextjs-ref/packages/next/src/server/render.tsx (~line 1200) and - // .nextjs-ref/packages/next/src/lib/is-serializable-props.ts. Tracked in - // vinext#1478. - if (result?.props !== undefined) { - isSerializableProps(options.routePattern, "getServerSideProps", pageProps); - } - gsspRes = res; } diff --git a/packages/vinext/src/server/pages-readiness.ts b/packages/vinext/src/server/pages-readiness.ts index 40bb4b43e..e48a9f514 100644 --- a/packages/vinext/src/server/pages-readiness.ts +++ b/packages/vinext/src/server/pages-readiness.ts @@ -41,11 +41,11 @@ export function buildPagesReadinessNextData(options: { ?.getInitialProps === "function"; const hasAppGip = typeof options.appComponent?.getInitialProps === "function"; return { - gssp: hasPageGssp, + gssp: hasPageGssp ? true : undefined, gsp: hasPageGsp ? true : undefined, - gip: hasPageGip, - appGip: hasAppGip, - autoExport: !hasPageGssp && !hasPageGsp && !hasPageGip && !hasAppGip, + gip: hasPageGip ? true : undefined, + appGip: hasAppGip ? true : undefined, + autoExport: !hasPageGssp && !hasPageGsp && !hasPageGip && !hasAppGip ? true : undefined, __vinext: { hasRewrites: options.hasRewrites }, }; } diff --git a/packages/vinext/src/shims/router.ts b/packages/vinext/src/shims/router.ts index 2b39ebe0b..4057e2d8d 100644 --- a/packages/vinext/src/shims/router.ts +++ b/packages/vinext/src/shims/router.ts @@ -1701,6 +1701,29 @@ async function navigateClientData( return; } + if (res.status === 404) { + let isNotFound = false; + try { + const notFoundBody = (await res.clone().json()) as unknown; + isNotFound = isUnknownRecord(notFoundBody) && notFoundBody.notFound === true; + } catch { + // A stale build/data URL can return a non-JSON 404. Keep the existing + // deploy-skew hard-navigation fallback for that response shape. + } + + if (isNotFound) { + const notFoundFetchUrl = resolvePagesErrorHtmlFetchUrl("/404", initialTarget.locale); + if (!notFoundFetchUrl) { + scheduleHardNavigationAndThrow(url, "Data navigation failed: no 404 route available"); + } + await navigateClientHtml(url, notFoundFetchUrl, controller, navId, assertStillCurrent, { + ...options, + allowNotFoundResponse: true, + }); + return; + } + } + if (!res.ok) { // 404 here is the deploy-skew signal (server buildId rotated) — hard // reload to land on the new build's HTML. Any other non-OK status is diff --git a/tests/e2e/pages-router-prod/production.spec.ts b/tests/e2e/pages-router-prod/production.spec.ts index 5caddab73..ebe348c3f 100644 --- a/tests/e2e/pages-router-prod/production.spec.ts +++ b/tests/e2e/pages-router-prod/production.spec.ts @@ -40,6 +40,33 @@ test.describe("Pages Router Production Build", () => { expect(nextData.props.pageProps.message).toBe("Hello from getServerSideProps"); }); + test("omits gssp from __NEXT_DATA__ for non-GSSP pages", async ({ page }) => { + // Ported from Next.js: test/e2e/getserversideprops/test/index.test.ts + // https://github.com/vercel/next.js/blob/v16.2.6/test/e2e/getserversideprops/test/index.test.ts + await page.goto(`${BASE}/about`); + const nextData = await page.evaluate(() => (window as any).__NEXT_DATA__); + expect("gssp" in nextData).toBe(false); + }); + + for (const href of ["/gssp-not-found?hiding=true", "/gssp-not-found/first?hiding=true"]) { + test(`renders the 404 page on GSSP client navigation to ${href}`, async ({ page }) => { + // Ported from Next.js: test/e2e/getserversideprops/test/index.test.ts + // https://github.com/vercel/next.js/blob/v16.2.6/test/e2e/getserversideprops/test/index.test.ts + await page.goto(`${BASE}/`); + await page.evaluate((target) => (window as any).next.router.push(target), href); + await expect(page.getByTestId("error-title")).toBeVisible(); + expect(page.url()).toContain(href); + }); + } + + test("renders non-JSON getServerSideProps values in production", async ({ request }) => { + // Ported from Next.js: test/e2e/getserversideprops/test/index.test.ts + // https://github.com/vercel/next.js/blob/v16.2.6/test/e2e/getserversideprops/test/index.test.ts + const response = await request.get(`${BASE}/gssp-non-json`); + expect(response.status()).toBe(200); + expect(await response.text()).toContain("hello "); + }); + test("API route returns JSON", async ({ request }) => { const response = await request.get(`${BASE}/api/hello`); expect(response.status()).toBe(200); diff --git a/tests/fixtures/pages-basic/pages/gssp-non-json.tsx b/tests/fixtures/pages-basic/pages/gssp-non-json.tsx new file mode 100644 index 000000000..c8165da40 --- /dev/null +++ b/tests/fixtures/pages-basic/pages/gssp-non-json.tsx @@ -0,0 +1,11 @@ +import type { GetServerSideProps, InferGetServerSidePropsType } from "next"; + +export default function GsspNonJson({ + time, +}: InferGetServerSidePropsType) { + return

hello {time.toString()}

; +} + +export const getServerSideProps: GetServerSideProps<{ time: Date }> = async () => ({ + props: { time: new Date("2026-06-21T00:00:00.000Z") }, +}); diff --git a/tests/fixtures/pages-basic/pages/gssp-not-found.tsx b/tests/fixtures/pages-basic/pages/gssp-not-found.tsx new file mode 100644 index 000000000..f0d0da8e2 --- /dev/null +++ b/tests/fixtures/pages-basic/pages/gssp-not-found.tsx @@ -0,0 +1,9 @@ +import type { GetServerSideProps } from "next"; + +export default function GsspNotFound() { + return

visible page

; +} + +export const getServerSideProps: GetServerSideProps = async ({ query }) => { + return query.hiding === "true" ? { notFound: true } : { props: {} }; +}; diff --git a/tests/fixtures/pages-basic/pages/gssp-not-found/[slug].tsx b/tests/fixtures/pages-basic/pages/gssp-not-found/[slug].tsx new file mode 100644 index 000000000..0a7ba7299 --- /dev/null +++ b/tests/fixtures/pages-basic/pages/gssp-not-found/[slug].tsx @@ -0,0 +1,9 @@ +import type { GetServerSideProps } from "next"; + +export default function DynamicGsspNotFound() { + return

visible dynamic page

; +} + +export const getServerSideProps: GetServerSideProps = async ({ query }) => { + return query.hiding === "true" ? { notFound: true } : { props: {} }; +}; diff --git a/tests/fixtures/pages-basic/pages/index.tsx b/tests/fixtures/pages-basic/pages/index.tsx index cbd05ce92..d5506f51c 100644 --- a/tests/fixtures/pages-basic/pages/index.tsx +++ b/tests/fixtures/pages-basic/pages/index.tsx @@ -10,6 +10,12 @@ export default function Home() {

Hello, vinext!

This is a Pages Router app running on Vite.

Go to About + + GSSP not found + + + Dynamic GSSP not found + ); } diff --git a/tests/pages-page-data.test.ts b/tests/pages-page-data.test.ts index afc0a231a..fb241f4dd 100644 --- a/tests/pages-page-data.test.ts +++ b/tests/pages-page-data.test.ts @@ -349,7 +349,7 @@ describe("pages page data", () => { } expect(result.response.status).toBe(404); expect(result.response.headers.get("content-type")).toBe("application/json"); - await expect(result.response.text()).resolves.toBe("{}"); + await expect(result.response.json()).resolves.toEqual({ notFound: true }); }); it("returns JSON 404 envelope for data requests when getStaticProps returns notFound", async () => { @@ -370,7 +370,7 @@ describe("pages page data", () => { } expect(result.response.status).toBe(404); expect(result.response.headers.get("content-type")).toBe("application/json"); - await expect(result.response.text()).resolves.toBe("{}"); + await expect(result.response.json()).resolves.toEqual({ notFound: true }); }); it("returns JSON 404 envelope for data requests when getServerSideProps returns notFound", async () => { @@ -391,7 +391,25 @@ describe("pages page data", () => { } expect(result.response.status).toBe(404); expect(result.response.headers.get("content-type")).toBe("application/json"); - await expect(result.response.text()).resolves.toBe("{}"); + await expect(result.response.json()).resolves.toEqual({ notFound: true }); + }); + + it("allows non-JSON getServerSideProps values during production requests", async () => { + const date = new Date("2026-06-21T00:00:00.000Z"); + const result = await resolvePagesPageData( + createOptions({ + pageModule: { + async getServerSideProps() { + return { props: { date } }; + }, + }, + }), + ); + + expect(result).toMatchObject({ + kind: "render", + pageProps: { date }, + }); }); // Refs #1543: a crawler/bot UA hitting an unlisted `fallback: true` path @@ -1294,24 +1312,6 @@ describe("pages page data", () => { ); }); - it("throws a Next.js-style error when getServerSideProps returns non-serializable props", async () => { - await expect( - resolvePagesPageData( - createOptions({ - pageModule: { - async getServerSideProps() { - return { props: { fn: () => "nope" } }; - }, - }, - routePattern: "/gssp-bad", - routeUrl: "/gssp-bad", - }), - ), - ).rejects.toThrow( - /Error serializing `\.fn` returned from `getServerSideProps` in "\/gssp-bad"\.\s*Reason: `function` cannot be serialized as JSON/, - ); - }); - // ── x-nextjs-deployment-id header ───────────────────────────────────────── // Mirrors Next.js pages-handler.ts: set x-nextjs-deployment-id on ALL // `_next/data` exits (success, redirect, notFound) for deployment-skew diff --git a/tests/pages-readiness.test.ts b/tests/pages-readiness.test.ts new file mode 100644 index 000000000..52c19c801 --- /dev/null +++ b/tests/pages-readiness.test.ts @@ -0,0 +1,27 @@ +import { describe, expect, it } from "vite-plus/test"; +import { buildPagesReadinessNextData } from "../packages/vinext/src/server/pages-readiness.js"; + +describe("Pages readiness serialization", () => { + it("omits false Next.js data-fetching markers", () => { + expect( + buildPagesReadinessNextData({ + pageModule: {}, + appComponent: null, + hasRewrites: false, + }), + ).toEqual({ + autoExport: true, + __vinext: { hasRewrites: false }, + }); + }); + + it("emits true getServerSideProps marker", () => { + expect( + buildPagesReadinessNextData({ + pageModule: { getServerSideProps: async () => ({ props: {} }) }, + appComponent: null, + hasRewrites: false, + }), + ).toMatchObject({ gssp: true }); + }); +}); From ae0a3219020de6a5da27f03c2cb775dc624d5513 Mon Sep 17 00:00:00 2001 From: James Date: Sun, 21 Jun 2026 20:33:28 +0100 Subject: [PATCH 2/4] fix(router): preserve route state for gssp not-found --- packages/vinext/src/shims/router.ts | 21 ++++++++++++++-- .../e2e/pages-router-prod/production.spec.ts | 25 +++++++++++++++++++ 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/packages/vinext/src/shims/router.ts b/packages/vinext/src/shims/router.ts index 4057e2d8d..5c62c72d7 100644 --- a/packages/vinext/src/shims/router.ts +++ b/packages/vinext/src/shims/router.ts @@ -1439,6 +1439,12 @@ function scheduleHardNavigationAndThrow(url: string, message: string): never { type NavigateClientOptions = { allowNotFoundResponse?: boolean; + /** + * Route state to preserve while rendering an error-route component. A + * getServerSideProps/getStaticProps notFound transition renders /404, but + * Next.js keeps pathname/route/query/asPath pointed at the requested route. + */ + nextDataRouteState?: Pick; /** * The history mode of the originating navigation. Used when a gSSP/gSP data * response carries a `__N_REDIRECT` marker so the re-entrant navigation to @@ -1716,9 +1722,17 @@ async function navigateClientData( if (!notFoundFetchUrl) { scheduleHardNavigationAndThrow(url, "Data navigation failed: no 404 route available"); } + const requestedQuery = mergeRouteParamsIntoQuery( + parseQueryString(initialTarget.search), + initialTarget.params, + ); await navigateClientHtml(url, notFoundFetchUrl, controller, navId, assertStillCurrent, { ...options, allowNotFoundResponse: true, + nextDataRouteState: { + page: initialTarget.pattern, + query: requestedQuery, + }, }); return; } @@ -2043,8 +2057,11 @@ async function navigateClientHtml( window.history.replaceState(window.history.state ?? {}, "", pendingRedirectHistoryUrl); routerRuntimeState.lastPathnameAndSearch = window.location.pathname + window.location.search; } - window.__NEXT_DATA__ = nextData; - applyVinextLocaleGlobals(window, nextData); + const committedNextData = options.nextDataRouteState + ? { ...nextData, ...options.nextDataRouteState } + : nextData; + window.__NEXT_DATA__ = committedNextData; + applyVinextLocaleGlobals(window, committedNextData); await renderPagesRouterElement(element, options.scroll); assertStillCurrent(); } diff --git a/tests/e2e/pages-router-prod/production.spec.ts b/tests/e2e/pages-router-prod/production.spec.ts index ebe348c3f..1a31628f1 100644 --- a/tests/e2e/pages-router-prod/production.spec.ts +++ b/tests/e2e/pages-router-prod/production.spec.ts @@ -59,6 +59,31 @@ test.describe("Pages Router Production Build", () => { }); } + test("preserves requested dynamic route state while rendering GSSP notFound", async ({ + page, + }) => { + await page.goto(`${BASE}/`); + await page.evaluate(() => + (window as any).next.router.push("/gssp-not-found/first?hiding=true"), + ); + await expect(page.getByTestId("error-title")).toBeVisible(); + + const state = await page.evaluate(() => ({ + pathname: (window as any).next.router.pathname, + route: (window as any).next.router.route, + query: (window as any).next.router.query, + asPath: (window as any).next.router.asPath, + nextDataPage: (window as any).__NEXT_DATA__.page, + })); + expect(state).toEqual({ + pathname: "/gssp-not-found/[slug]", + route: "/gssp-not-found/[slug]", + query: { hiding: "true", slug: "first" }, + asPath: "/gssp-not-found/first?hiding=true", + nextDataPage: "/gssp-not-found/[slug]", + }); + }); + test("renders non-JSON getServerSideProps values in production", async ({ request }) => { // Ported from Next.js: test/e2e/getserversideprops/test/index.test.ts // https://github.com/vercel/next.js/blob/v16.2.6/test/e2e/getserversideprops/test/index.test.ts From 452b507e54ce4d285678e2a7867d8c930bdbb955 Mon Sep 17 00:00:00 2001 From: James Date: Sun, 21 Jun 2026 20:45:39 +0100 Subject: [PATCH 3/4] test(pages): expect not-found data marker --- tests/pages-router.test.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/tests/pages-router.test.ts b/tests/pages-router.test.ts index cae8becae..36cf917b4 100644 --- a/tests/pages-router.test.ts +++ b/tests/pages-router.test.ts @@ -2109,17 +2109,16 @@ describe("Pages Router integration", () => { expect(await res.json()).toEqual({}); }); - it("returns JSON 404 when getStaticPaths fallback:false rejects the path", async () => { + it("returns notFound JSON when getStaticPaths fallback:false rejects the path", async () => { // /blog/[slug] has `fallback: false` and only allows the slugs listed - // in getStaticPaths. An unlisted slug must produce a JSON 404 for - // data requests (not the HTML 404 page) so the client router can - // hard-navigate instead of failing to parse HTML as JSON. + // in getStaticPaths. An unlisted slug must produce the Next.js + // notFound marker so the client router can render the configured 404. const res = await fetch( `${baseUrl}/_next/data/${BUILD_ID}/blog/this-slug-does-not-exist.json`, ); expect(res.status).toBe(404); expect(res.headers.get("content-type")).toContain("application/json"); - expect(await res.json()).toEqual({}); + expect(await res.json()).toEqual({ notFound: true }); }); it("returns JSON 404 for a stale buildId (dev)", async () => { From e7f1a136d4c52d55385d14f2cfe443924a636689 Mon Sep 17 00:00:00 2001 From: James Date: Sun, 21 Jun 2026 20:46:38 +0100 Subject: [PATCH 4/4] fix(pages): preserve fallback miss navigation --- packages/vinext/src/server/dev-server.ts | 6 ++--- packages/vinext/src/server/pages-page-data.ts | 23 ++++++++++++++++--- tests/pages-page-data.test.ts | 4 ++-- tests/pages-router.test.ts | 9 ++++---- 4 files changed, 30 insertions(+), 12 deletions(-) diff --git a/packages/vinext/src/server/dev-server.ts b/packages/vinext/src/server/dev-server.ts index 6bddd2b80..7d5db4d90 100644 --- a/packages/vinext/src/server/dev-server.ts +++ b/packages/vinext/src/server/dev-server.ts @@ -749,8 +749,8 @@ export function createSSRHandler( if (isDataReq) { // Data requests get a JSON 404 so the client router can // hard-navigate instead of trying to parse HTML as JSON. - // Mirror Next.js pages-handler.ts: set x-nextjs-deployment-id on - // `_next/data` notFound exits for deployment-skew protection. Fixes #1829. + // Set x-nextjs-deployment-id on the data 404 for deployment-skew + // protection while keeping the empty-object hard-navigation shape. const deploymentId = process.env.__VINEXT_DEPLOYMENT_ID || process.env.NEXT_DEPLOYMENT_ID; const notFoundHeaders: Record = { @@ -758,7 +758,7 @@ export function createSSRHandler( }; if (deploymentId) notFoundHeaders[NEXTJS_DEPLOYMENT_ID_HEADER] = deploymentId; res.writeHead(404, notFoundHeaders); - res.end('{"notFound":true}'); + res.end("{}"); return; } await renderErrorPage( diff --git a/packages/vinext/src/server/pages-page-data.ts b/packages/vinext/src/server/pages-page-data.ts index 4226c1c5e..0640d3525 100644 --- a/packages/vinext/src/server/pages-page-data.ts +++ b/packages/vinext/src/server/pages-page-data.ts @@ -283,6 +283,17 @@ function buildPagesDataNotFoundResponse(deploymentId?: string): Response { }); } +function buildPagesDataFallbackMissResponse(deploymentId?: string): Response { + const headers: Record = { "Content-Type": "application/json" }; + if (deploymentId) { + headers[NEXTJS_DEPLOYMENT_ID_HEADER] = deploymentId; + } + return new Response("{}", { + status: 404, + headers, + }); +} + function buildPagesNotFoundResult( options: Pick, ): ResolvePagesPageDataResponseResult | ResolvePagesPageDataNotFoundResult { @@ -635,9 +646,15 @@ export async function resolvePagesPageData( if (fallback === false && !isValidPath) { // For data requests (`/_next/data/...json`), return a JSON-shaped 404 - // so the client router can `res.json()` without blowing up — matches - // Next.js' behavior. HTML navigations still get the configured 404 page. - return buildPagesNotFoundResult(options); + // without the notFound marker so the client router hard-navigates, + // matching Next.js' NoFallbackError path. + if (options.isDataReq) { + return { + kind: "response", + response: buildPagesDataFallbackMissResponse(options.deploymentId), + }; + } + return { kind: "notFound" }; } // Render the fallback shell for unlisted paths under `fallback: true`. diff --git a/tests/pages-page-data.test.ts b/tests/pages-page-data.test.ts index fb241f4dd..0e585502a 100644 --- a/tests/pages-page-data.test.ts +++ b/tests/pages-page-data.test.ts @@ -324,7 +324,7 @@ describe("pages page data", () => { expect(result).toEqual({ kind: "notFound" }); }); - it("returns JSON 404 envelope for data requests when getStaticPaths excludes a path", async () => { + it("returns empty JSON 404 for data requests when getStaticPaths excludes a path", async () => { const result = await resolvePagesPageData( createOptions({ isDataReq: true, @@ -349,7 +349,7 @@ describe("pages page data", () => { } expect(result.response.status).toBe(404); expect(result.response.headers.get("content-type")).toBe("application/json"); - await expect(result.response.json()).resolves.toEqual({ notFound: true }); + await expect(result.response.json()).resolves.toEqual({}); }); it("returns JSON 404 envelope for data requests when getStaticProps returns notFound", async () => { diff --git a/tests/pages-router.test.ts b/tests/pages-router.test.ts index 36cf917b4..cae8becae 100644 --- a/tests/pages-router.test.ts +++ b/tests/pages-router.test.ts @@ -2109,16 +2109,17 @@ describe("Pages Router integration", () => { expect(await res.json()).toEqual({}); }); - it("returns notFound JSON when getStaticPaths fallback:false rejects the path", async () => { + it("returns JSON 404 when getStaticPaths fallback:false rejects the path", async () => { // /blog/[slug] has `fallback: false` and only allows the slugs listed - // in getStaticPaths. An unlisted slug must produce the Next.js - // notFound marker so the client router can render the configured 404. + // in getStaticPaths. An unlisted slug must produce a JSON 404 for + // data requests (not the HTML 404 page) so the client router can + // hard-navigate instead of failing to parse HTML as JSON. const res = await fetch( `${baseUrl}/_next/data/${BUILD_ID}/blog/this-slug-does-not-exist.json`, ); expect(res.status).toBe(404); expect(res.headers.get("content-type")).toContain("application/json"); - expect(await res.json()).toEqual({ notFound: true }); + expect(await res.json()).toEqual({}); }); it("returns JSON 404 for a stale buildId (dev)", async () => {