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();
+ });
+});