From 9697bcd3c8672f7926cc19222e9c5801a9f49c6b Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Mon, 18 Sep 2023 14:35:00 -0500 Subject: [PATCH] Fix notFound status code with ISR in app (#55542) This ensures we properly set/restore the status code with ISR paths in app router so that when we set the 404 status code with `notFound` it is persisted properly. Fixes: https://github.com/vercel/next.js/issues/43831 Closes: https://github.com/vercel/next.js/issues/48342 x-ref: https://github.com/vercel/next.js/issues/49387#issuecomment-1722575969 --- packages/next/src/server/base-server.ts | 5 ++ .../e2e/app-dir/app-static/app-static.test.ts | 46 +++++++++++++++++++ .../app-static/app/articles/[slug]/page.tsx | 29 ++++++++++++ 3 files changed, 80 insertions(+) create mode 100644 test/e2e/app-dir/app-static/app/articles/[slug]/page.tsx diff --git a/packages/next/src/server/base-server.ts b/packages/next/src/server/base-server.ts index 7b61af7e1fcd..8860e05fd2b1 100644 --- a/packages/next/src/server/base-server.ts +++ b/packages/next/src/server/base-server.ts @@ -2238,6 +2238,7 @@ export default abstract class Server { html: result, pageData: metadata.pageData, headers, + status: isAppPath ? res.statusCode : undefined, }, revalidate: metadata.revalidate, } @@ -2493,6 +2494,10 @@ export default abstract class Server { ) } + if (cachedData.status) { + res.statusCode = cachedData.status + } + return { type: isDataReq ? 'rsc' : 'html', body: isDataReq diff --git a/test/e2e/app-dir/app-static/app-static.test.ts b/test/e2e/app-dir/app-static/app-static.test.ts index ea05292513ba..d5d2395738a8 100644 --- a/test/e2e/app-dir/app-static/app-static.test.ts +++ b/test/e2e/app-dir/app-static/app-static.test.ts @@ -700,6 +700,10 @@ createNextDescribe( 'variable-revalidate-edge/post-method-request/page_client-reference-manifest.js', 'partial-gen-params-no-additional-lang/[lang]/[slug]/page_client-reference-manifest.js', 'partial-gen-params-no-additional-slug/[lang]/[slug]/page_client-reference-manifest.js', + 'articles/[slug]/page.js', + 'articles/[slug]/page_client-reference-manifest.js', + 'articles/works.html', + 'articles/works.rsc', ].sort() ) }) @@ -752,6 +756,22 @@ createNextDescribe( "initialRevalidateSeconds": false, "srcRoute": "/", }, + "/articles/works": Object { + "dataRoute": "/articles/works.rsc", + "experimentalBypassFor": Array [ + Object { + "key": "Next-Action", + "type": "header", + }, + Object { + "key": "content-type", + "type": "header", + "value": "multipart/form-data", + }, + ], + "initialRevalidateSeconds": 1, + "srcRoute": "/articles/[slug]", + }, "/blog/seb": Object { "dataRoute": "/blog/seb.rsc", "experimentalBypassFor": Array [ @@ -1404,6 +1424,23 @@ createNextDescribe( `) expect(curManifest.dynamicRoutes).toMatchInlineSnapshot(` Object { + "/articles/[slug]": Object { + "dataRoute": "/articles/[slug].rsc", + "dataRouteRegex": "^\\\\/articles\\\\/([^\\\\/]+?)\\\\.rsc$", + "experimentalBypassFor": Array [ + Object { + "key": "Next-Action", + "type": "header", + }, + Object { + "key": "content-type", + "type": "header", + "value": "multipart/form-data", + }, + ], + "fallback": null, + "routeRegex": "^\\\\/articles\\\\/([^\\\\/]+?)(?:\\\\/)?$", + }, "/blog/[author]": Object { "dataRoute": "/blog/[author].rsc", "dataRouteRegex": "^\\\\/blog\\\\/([^\\\\/]+?)\\\\.rsc$", @@ -1590,6 +1627,15 @@ createNextDescribe( }) } + it('should correctly handle statusCode with notFound + ISR', async () => { + for (let i = 0; i < 5; i++) { + const res = await next.fetch('/articles/non-existent') + expect(res.status).toBe(404) + expect(await res.text()).toContain('This page could not be found') + await waitFor(500) + } + }) + it('should cache correctly for fetchCache = default-cache', async () => { const res = await next.fetch('/default-cache') expect(res.status).toBe(200) diff --git a/test/e2e/app-dir/app-static/app/articles/[slug]/page.tsx b/test/e2e/app-dir/app-static/app/articles/[slug]/page.tsx new file mode 100644 index 000000000000..934953119260 --- /dev/null +++ b/test/e2e/app-dir/app-static/app/articles/[slug]/page.tsx @@ -0,0 +1,29 @@ +import { notFound } from 'next/navigation' + +export interface Props { + params: { + slug: string + } +} + +const Article = ({ params }: Props) => { + const { slug } = params + + if (slug !== 'works') { + return notFound() + } + return
Articles page with slug
+} + +export const revalidate = 1 +export const dynamicParams = true + +export async function generateStaticParams() { + return [ + { + slug: 'works', // Anything not this should be a 404 with no ISR + }, + ] +} + +export default Article