diff --git a/examples/app-router/app/isr/dynamic-params-false/[id]/page.tsx b/examples/app-router/app/isr/dynamic-params-false/[id]/page.tsx new file mode 100644 index 000000000..4b9701730 --- /dev/null +++ b/examples/app-router/app/isr/dynamic-params-false/[id]/page.tsx @@ -0,0 +1,38 @@ +// https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#dynamicparams +export const dynamicParams = false; // or true, to make it try SSR unknown paths + +const POSTS = Array.from({ length: 20 }, (_, i) => ({ + id: String(i + 1), + title: `Post ${i + 1}`, + content: `This is post ${i + 1}`, +})); + +async function fakeGetPostsFetch() { + return POSTS.slice(0, 10); +} + +async function fakeGetPostFetch(id: string) { + return POSTS.find((post) => post.id === id); +} + +export async function generateStaticParams() { + const fakePosts = await fakeGetPostsFetch(); + return fakePosts.map((post) => ({ + id: post.id, + })); +} + +export default async function Page({ + params, +}: { + params: Promise<{ id: string }>; +}) { + const { id } = await params; + const post = await fakeGetPostFetch(id); + return ( +
+

{post?.title}

+

{post?.content}

+
+ ); +} diff --git a/examples/app-router/app/isr/dynamic-params-true/[id]/page.tsx b/examples/app-router/app/isr/dynamic-params-true/[id]/page.tsx new file mode 100644 index 000000000..8134d0bff --- /dev/null +++ b/examples/app-router/app/isr/dynamic-params-true/[id]/page.tsx @@ -0,0 +1,49 @@ +import { notFound } from "next/navigation"; + +// We'll prerender only the params from `generateStaticParams` at build time. +// If a request comes in for a path that hasn't been generated, +// Next.js will server-render the page on-demand. +// https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#dynamicparams +export const dynamicParams = true; // or false, to 404 on unknown paths + +const POSTS = Array.from({ length: 20 }, (_, i) => ({ + id: String(i + 1), + title: `Post ${i + 1}`, + content: `This is post ${i + 1}`, +})); + +async function fakeGetPostsFetch() { + return POSTS.slice(0, 10); +} + +async function fakeGetPostFetch(id: string) { + return POSTS.find((post) => post.id === id); +} + +export async function generateStaticParams() { + const fakePosts = await fakeGetPostsFetch(); + return fakePosts.map((post) => ({ + id: post.id, + })); +} + +export default async function Page({ + params, +}: { + params: Promise<{ id: string }>; +}) { + const { id } = await params; + const post = await fakeGetPostFetch(id); + if (Number(id) === 1337) { + throw new Error("This is an error!"); + } + if (!post) { + notFound(); + } + return ( +
+

{post.title}

+

{post.content}

+
+ ); +} diff --git a/packages/tests-e2e/tests/appRouter/isr.test.ts b/packages/tests-e2e/tests/appRouter/isr.test.ts index 4f6ae78ef..db7a23435 100644 --- a/packages/tests-e2e/tests/appRouter/isr.test.ts +++ b/packages/tests-e2e/tests/appRouter/isr.test.ts @@ -93,3 +93,64 @@ test("Incremental Static Regeneration with data cache", async ({ page }) => { expect(originalCachedDate).toEqual(finalCachedDate); expect(originalFetchedDate).toEqual(finalFetchedDate); }); + +test.describe("dynamicParams set to true", () => { + test("should be HIT on a path that was prebuilt", async ({ page }) => { + const res = await page.goto("/isr/dynamic-params-true/1"); + expect(res?.status()).toEqual(200); + expect(res?.headers()["x-nextjs-cache"]).toEqual("HIT"); + const title = await page.getByTestId("title").textContent(); + const content = await page.getByTestId("content").textContent(); + expect(title).toEqual("Post 1"); + expect(content).toEqual("This is post 1"); + }); + + // In `next start` this test would fail on subsequent requests because `x-nextjs-cache` would be `HIT` + // However, once deployed to AWS, Cloudfront will cache `MISS` + test("should SSR on a path that was not prebuilt", async ({ page }) => { + const res = await page.goto("/isr/dynamic-params-true/11"); + expect(res?.headers()["x-nextjs-cache"]).toEqual("MISS"); + const title = await page.getByTestId("title").textContent(); + const content = await page.getByTestId("content").textContent(); + expect(title).toEqual("Post 11"); + expect(content).toEqual("This is post 11"); + }); + + test("should 404 when you call notFound", async ({ page }) => { + const res = await page.goto("/isr/dynamic-params-true/21"); + expect(res?.status()).toEqual(404); + expect(res?.headers()["cache-control"]).toBe( + "private, no-cache, no-store, max-age=0, must-revalidate", + ); + await expect(page.getByText("404")).toBeAttached(); + }); + + test("should 500 for a path that throws an error", async ({ page }) => { + const res = await page.goto("/isr/dynamic-params-true/1337"); + expect(res?.status()).toEqual(500); + expect(res?.headers()["cache-control"]).toBe( + "private, no-cache, no-store, max-age=0, must-revalidate", + ); + }); +}); + +test.describe("dynamicParams set to false", () => { + test("should be HIT on a path that was prebuilt", async ({ page }) => { + const res = await page.goto("/isr/dynamic-params-false/1"); + expect(res?.status()).toEqual(200); + expect(res?.headers()["x-nextjs-cache"]).toEqual("HIT"); + const title = await page.getByTestId("title").textContent(); + const content = await page.getByTestId("content").textContent(); + expect(title).toEqual("Post 1"); + expect(content).toEqual("This is post 1"); + }); + + test("should 404 for a path that is not found", async ({ page }) => { + const res = await page.goto("/isr/dynamic-params-false/11"); + expect(res?.status()).toEqual(404); + expect(res?.headers()["cache-control"]).toBe( + "private, no-cache, no-store, max-age=0, must-revalidate", + ); + await expect(page.getByText("404")).toBeAttached(); + }); +});