diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/app/(cached-nesting)/cached-mid-layout/[id]/layout.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/app/(cached-nesting)/cached-mid-layout/[id]/layout.tsx
index a61f15ee7e3f..ed7d8d1716d8 100644
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/app/(cached-nesting)/cached-mid-layout/[id]/layout.tsx
+++ b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/app/(cached-nesting)/cached-mid-layout/[id]/layout.tsx
@@ -1,5 +1,6 @@
import type { ReactNode } from 'react';
import { cacheLife } from 'next/cache';
+import { CachedBox } from '@/components/scenarioBox';
// The awaited param puts the request id into the cache key, so a fresh id is a guaranteed miss
// and the entry cannot come from a build-time fill. `children` passes through as an uncached hole.
@@ -15,11 +16,14 @@ export default async function CachedMidLayout({
const { id } = await params;
await new Promise(resolve => setTimeout(resolve, 100));
return (
-
+
+ );
}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/app/(cached-nesting)/dynamic-layouts/[id]/layout-cached-leaf/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/app/(cached-nesting)/dynamic-layouts/[id]/layout-cached-leaf/page.tsx
index 8e7de36d156a..494dee2483e2 100644
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/app/(cached-nesting)/dynamic-layouts/[id]/layout-cached-leaf/page.tsx
+++ b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/app/(cached-nesting)/dynamic-layouts/[id]/layout-cached-leaf/page.tsx
@@ -1,13 +1,16 @@
import { cacheLife } from 'next/cache';
+import { CachedBox } from '@/components/scenarioBox';
async function CachedLeaf({ id }: { id: string }) {
'use cache';
cacheLife('hours');
await new Promise(resolve => setTimeout(resolve, 100));
return (
-
- {id}:{Date.now()}
-
+
+
+ {id}:{Date.now()}
+
+
);
}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/app/(cached-nesting)/dynamic-layouts/[id]/layout.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/app/(cached-nesting)/dynamic-layouts/[id]/layout.tsx
index 412587fd8f33..4b9b4cffc9bf 100644
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/app/(cached-nesting)/dynamic-layouts/[id]/layout.tsx
+++ b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/app/(cached-nesting)/dynamic-layouts/[id]/layout.tsx
@@ -1,7 +1,16 @@
import type { ReactNode } from 'react';
import { headers } from 'next/headers';
+import { DynamicBox } from '@/components/scenarioBox';
export default async function DynamicLayout({ children }: { children: ReactNode }) {
await headers();
- return
{children}
;
+ return (
+
+
Dynamic layout, cached leaf
+
+
request-time: {Date.now()}
+ {children}
+
+
+ );
}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/app/(cached-nesting)/mixed-lifetimes/[id]/layout.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/app/(cached-nesting)/mixed-lifetimes/[id]/layout.tsx
index 2b0b77b6e3a9..7920ef335dc2 100644
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/app/(cached-nesting)/mixed-lifetimes/[id]/layout.tsx
+++ b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/app/(cached-nesting)/mixed-lifetimes/[id]/layout.tsx
@@ -1,5 +1,6 @@
import type { ReactNode } from 'react';
import { cacheLife } from 'next/cache';
+import { CachedBox } from '@/components/scenarioBox';
// Hard-expires after 2s while the page's cached component lives for hours, so one request can
// refill the layout while hitting the component: two cached levels with different origin traces.
@@ -14,11 +15,14 @@ export default async function ShortLivedLayout({
cacheLife({ revalidate: 1, expire: 2 });
const { id } = await params;
return (
-
-
- {id}:{Date.now()}
-
- {children}
-
+
+
Mixed lifetimes
+
+
+ {id}:{Date.now()}
+
+ {children}
+
+
);
}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/app/(cached-nesting)/mixed-lifetimes/[id]/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/app/(cached-nesting)/mixed-lifetimes/[id]/page.tsx
index 18913cbe9790..c1c0e31494f6 100644
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/app/(cached-nesting)/mixed-lifetimes/[id]/page.tsx
+++ b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/app/(cached-nesting)/mixed-lifetimes/[id]/page.tsx
@@ -1,19 +1,27 @@
import { headers } from 'next/headers';
import { cacheLife } from 'next/cache';
+import { CachedBox, DynamicBox } from '@/components/scenarioBox';
async function LongLivedComponent({ id }: { id: string }) {
'use cache';
cacheLife('hours');
await new Promise(resolve => setTimeout(resolve, 100));
return (
-
+ Loading...}>
+
+
+
);
}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/app/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/app/page.tsx
index 433db8283beb..bea2cf1d875f 100644
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/app/page.tsx
+++ b/dev-packages/e2e-tests/test-applications/nextjs-16-cacheComponents/app/page.tsx
@@ -1,3 +1,120 @@
+'use client';
+
+import { useEffect, useState } from 'react';
+
+type Scenario = { name: string; href: (id: string) => string; description: string };
+
+const groups: { title: string; scenarios: Scenario[] }[] = [
+ {
+ title: 'Route handlers (JSON)',
+ scenarios: [
+ {
+ name: '/api/use-cache',
+ href: id => `/api/use-cache?id=${id}`,
+ description: 'cached function that lives for hours. The second request with the same id is a hit.',
+ },
+ {
+ name: '/api/use-cache-expiring',
+ href: id => `/api/use-cache-expiring?id=${id}`,
+ description: 'the entry expires after 2 seconds. Request again after 3 seconds for a refill.',
+ },
+ {
+ name: '/api/use-cache-swr',
+ href: id => `/api/use-cache-swr?id=${id}`,
+ description:
+ 'the entry goes stale after 2 seconds. A stale read serves the old value and refills in the background.',
+ },
+ ],
+ },
+ {
+ title: 'Rendered pages',
+ scenarios: [
+ {
+ name: '/use-cache-page',
+ href: id => `/use-cache-page?id=${id}`,
+ description: 'a cached data function inside a dynamic hole.',
+ },
+ {
+ name: '/cached-sibling-components',
+ href: id => `/cached-sibling-components?id=${id}`,
+ description: 'two cached sibling components. Each section is its own cache entry.',
+ },
+ {
+ name: '/nested-caches',
+ href: id => `/nested-caches?id=${id}`,
+ description:
+ 'a cached component that expires after 2 seconds and calls a cached function that lives for hours.',
+ },
+ ],
+ },
+ {
+ title: 'Nested layouts',
+ scenarios: [
+ {
+ name: '/cached-mid-layout/[id]',
+ href: id => `/cached-mid-layout/${id}`,
+ description: 'a cached layout above a dynamic page.',
+ },
+ {
+ name: '/dynamic-layouts/[id]/layout-cached-leaf',
+ href: id => `/dynamic-layouts/${id}/layout-cached-leaf`,
+ description: 'dynamic layouts above a cached leaf component.',
+ },
+ {
+ name: '/mixed-lifetimes/[id]',
+ href: id => `/mixed-lifetimes/${id}`,
+ description: 'a cached layout that expires after 2 seconds around a cached component that lives for hours.',
+ },
+ {
+ name: '/shared-layout/[id]/a',
+ href: id => `/shared-layout/${id}/a`,
+ description: 'route a fills the layout entry that routes a and b share.',
+ },
+ {
+ name: '/shared-layout/[id]/b',
+ href: id => `/shared-layout/${id}/b`,
+ description: 'route b hits the layout entry that route a filled.',
+ },
+ ],
+ },
+ {
+ title: 'Pageload',
+ scenarios: [
+ {
+ name: '/pageload-tracing',
+ href: () => '/pageload-tracing',
+ description: 'a prerendered shell with a dynamic hole. Check the pageload and server trace connection here.',
+ },
+ ],
+ },
+];
+
+// The links are plain `` elements so that every click is a full document request with its own
+// server trace. `` would navigate through the client-side router instead.
export default function Page() {
- return
+
+ );
}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-streaming-cacheComponents/app/(cached-nesting)/dynamic-layouts/[id]/layout-cached-leaf/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-streaming-cacheComponents/app/(cached-nesting)/dynamic-layouts/[id]/layout-cached-leaf/page.tsx
index 8e7de36d156a..494dee2483e2 100644
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-streaming-cacheComponents/app/(cached-nesting)/dynamic-layouts/[id]/layout-cached-leaf/page.tsx
+++ b/dev-packages/e2e-tests/test-applications/nextjs-16-streaming-cacheComponents/app/(cached-nesting)/dynamic-layouts/[id]/layout-cached-leaf/page.tsx
@@ -1,13 +1,16 @@
import { cacheLife } from 'next/cache';
+import { CachedBox } from '@/components/scenarioBox';
async function CachedLeaf({ id }: { id: string }) {
'use cache';
cacheLife('hours');
await new Promise(resolve => setTimeout(resolve, 100));
return (
-
- {id}:{Date.now()}
-
+
+
+ {id}:{Date.now()}
+
+
);
}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-streaming-cacheComponents/app/(cached-nesting)/dynamic-layouts/[id]/layout.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-streaming-cacheComponents/app/(cached-nesting)/dynamic-layouts/[id]/layout.tsx
index 412587fd8f33..4b9b4cffc9bf 100644
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-streaming-cacheComponents/app/(cached-nesting)/dynamic-layouts/[id]/layout.tsx
+++ b/dev-packages/e2e-tests/test-applications/nextjs-16-streaming-cacheComponents/app/(cached-nesting)/dynamic-layouts/[id]/layout.tsx
@@ -1,7 +1,16 @@
import type { ReactNode } from 'react';
import { headers } from 'next/headers';
+import { DynamicBox } from '@/components/scenarioBox';
export default async function DynamicLayout({ children }: { children: ReactNode }) {
await headers();
- return
{children}
;
+ return (
+
+
Dynamic layout, cached leaf
+
+
request-time: {Date.now()}
+ {children}
+
+
+ );
}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-streaming-cacheComponents/app/(cached-nesting)/mixed-lifetimes/[id]/layout.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-streaming-cacheComponents/app/(cached-nesting)/mixed-lifetimes/[id]/layout.tsx
index 2b0b77b6e3a9..7920ef335dc2 100644
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-streaming-cacheComponents/app/(cached-nesting)/mixed-lifetimes/[id]/layout.tsx
+++ b/dev-packages/e2e-tests/test-applications/nextjs-16-streaming-cacheComponents/app/(cached-nesting)/mixed-lifetimes/[id]/layout.tsx
@@ -1,5 +1,6 @@
import type { ReactNode } from 'react';
import { cacheLife } from 'next/cache';
+import { CachedBox } from '@/components/scenarioBox';
// Hard-expires after 2s while the page's cached component lives for hours, so one request can
// refill the layout while hitting the component: two cached levels with different origin traces.
@@ -14,11 +15,14 @@ export default async function ShortLivedLayout({
cacheLife({ revalidate: 1, expire: 2 });
const { id } = await params;
return (
-
-
- {id}:{Date.now()}
-
- {children}
-
+
+
Mixed lifetimes
+
+
+ {id}:{Date.now()}
+
+ {children}
+
+
);
}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-streaming-cacheComponents/app/(cached-nesting)/mixed-lifetimes/[id]/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-streaming-cacheComponents/app/(cached-nesting)/mixed-lifetimes/[id]/page.tsx
index 18913cbe9790..c1c0e31494f6 100644
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-streaming-cacheComponents/app/(cached-nesting)/mixed-lifetimes/[id]/page.tsx
+++ b/dev-packages/e2e-tests/test-applications/nextjs-16-streaming-cacheComponents/app/(cached-nesting)/mixed-lifetimes/[id]/page.tsx
@@ -1,19 +1,27 @@
import { headers } from 'next/headers';
import { cacheLife } from 'next/cache';
+import { CachedBox, DynamicBox } from '@/components/scenarioBox';
async function LongLivedComponent({ id }: { id: string }) {
'use cache';
cacheLife('hours');
await new Promise(resolve => setTimeout(resolve, 100));
return (
-
+ Loading...}>
+
+
+
);
}
diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-streaming-cacheComponents/app/page.tsx b/dev-packages/e2e-tests/test-applications/nextjs-16-streaming-cacheComponents/app/page.tsx
index 433db8283beb..8f7d5cf7558e 100644
--- a/dev-packages/e2e-tests/test-applications/nextjs-16-streaming-cacheComponents/app/page.tsx
+++ b/dev-packages/e2e-tests/test-applications/nextjs-16-streaming-cacheComponents/app/page.tsx
@@ -1,3 +1,115 @@
+'use client';
+
+import { useEffect, useState } from 'react';
+
+type Scenario = { name: string; href: (id: string) => string; description: string };
+
+const groups: { title: string; scenarios: Scenario[] }[] = [
+ {
+ title: 'Route handlers (JSON)',
+ scenarios: [
+ {
+ name: '/api/use-cache',
+ href: id => `/api/use-cache?id=${id}`,
+ description: 'cached function that lives for hours. The second request with the same id is a hit.',
+ },
+ {
+ name: '/api/use-cache-expiring',
+ href: id => `/api/use-cache-expiring?id=${id}`,
+ description: 'the entry expires after 2 seconds. Request again after 3 seconds for a refill.',
+ },
+ {
+ name: '/api/use-cache-swr',
+ href: id => `/api/use-cache-swr?id=${id}`,
+ description:
+ 'the entry goes stale after 2 seconds. A stale read serves the old value and refills in the background.',
+ },
+ ],
+ },
+ {
+ title: 'Rendered pages',
+ scenarios: [
+ {
+ name: '/cached-sibling-components',
+ href: id => `/cached-sibling-components?id=${id}`,
+ description: 'two cached sibling components. Each section is its own cache entry.',
+ },
+ {
+ name: '/nested-caches',
+ href: id => `/nested-caches?id=${id}`,
+ description:
+ 'a cached component that expires after 2 seconds and calls a cached function that lives for hours.',
+ },
+ ],
+ },
+ {
+ title: 'Nested layouts',
+ scenarios: [
+ {
+ name: '/cached-mid-layout/[id]',
+ href: id => `/cached-mid-layout/${id}`,
+ description: 'a cached layout above a dynamic page.',
+ },
+ {
+ name: '/dynamic-layouts/[id]/layout-cached-leaf',
+ href: id => `/dynamic-layouts/${id}/layout-cached-leaf`,
+ description: 'dynamic layouts above a cached leaf component.',
+ },
+ {
+ name: '/mixed-lifetimes/[id]',
+ href: id => `/mixed-lifetimes/${id}`,
+ description: 'a cached layout that expires after 2 seconds around a cached component that lives for hours.',
+ },
+ {
+ name: '/shared-layout/[id]/a',
+ href: id => `/shared-layout/${id}/a`,
+ description: 'route a fills the layout entry that routes a and b share.',
+ },
+ {
+ name: '/shared-layout/[id]/b',
+ href: id => `/shared-layout/${id}/b`,
+ description: 'route b hits the layout entry that route a filled.',
+ },
+ ],
+ },
+ {
+ title: 'Pageload',
+ scenarios: [
+ {
+ name: '/pageload-tracing',
+ href: () => '/pageload-tracing',
+ description: 'a prerendered shell with a dynamic hole. Check the pageload and server trace connection here.',
+ },
+ ],
+ },
+];
+
+// The links are plain `` elements so that every click is a full document request with its own
+// server trace. `` would navigate through the client-side router instead.
export default function Page() {
- return