diff --git a/docs/01-app/01-getting-started/04-linking-and-navigating.mdx b/docs/01-app/01-getting-started/04-linking-and-navigating.mdx
index 08a04ec62454..e94e7d965e2a 100644
--- a/docs/01-app/01-getting-started/04-linking-and-navigating.mdx
+++ b/docs/01-app/01-getting-started/04-linking-and-navigating.mdx
@@ -5,6 +5,7 @@ related:
links:
- app/api-reference/components/link
- app/api-reference/file-conventions/loading
+ - app/guides/optimizing-prefetching
- app/guides/prefetching
---
@@ -95,7 +96,7 @@ By skipping or partially prefetching dynamic routes, Next.js avoids unnecessary
height="748"
/>
-> **Good to know:** See the [Prefetching guide](/docs/app/guides/prefetching) for the full behavior, including how to control prefetching per link and how it changes when you adopt [Partial Prefetching](/docs/app/guides/adopting-partial-prefetching).
+> **Good to know:** See the [Prefetching guide](/docs/app/guides/prefetching) for the full behavior, including how to control prefetching per link and how it changes when you adopt [Partial Prefetching](/docs/app/guides/adopting-partial-prefetching). To include URL-dependent content in a link's prefetch, see [Optimizing prefetching](/docs/app/guides/optimizing-prefetching).
To improve the navigation experience to dynamic routes, you can use [streaming](#streaming).
diff --git a/evals/evals/agent-035-connection-dynamic/EVAL.ts b/evals/evals/agent-035-connection-dynamic/EVAL.ts
index f1e42e6c9903..d795003af2eb 100644
--- a/evals/evals/agent-035-connection-dynamic/EVAL.ts
+++ b/evals/evals/agent-035-connection-dynamic/EVAL.ts
@@ -1,76 +1,50 @@
-/**
- * connection() for Dynamic Rendering
- *
- * Tests whether the agent uses connection() from next/server to force dynamic
- * rendering instead of the deprecated unstable_noStore().
- *
- * Tricky because agents use unstable_noStore(), force-dynamic segment config,
- * or unrelated Dynamic APIs instead of the stable connection() function.
- */
+/** Cache Components: synchronous request-time I/O in shared UI. */
import { expect, test } from 'vitest'
-import { readFileSync, existsSync, readdirSync } from 'fs'
+import { existsSync, readFileSync, readdirSync, statSync } from 'fs'
import { join } from 'path'
-
-function readAppFiles(): string {
- const appDir = join(process.cwd(), 'app')
- if (!existsSync(appDir)) return ''
- const entries = readdirSync(appDir, { recursive: true }) as string[]
- const files = entries.filter((f) => f.endsWith('.tsx') || f.endsWith('.ts'))
- return files.map((f) => readFileSync(join(appDir, f), 'utf-8')).join('\n')
+import { environment } from '@vercel/agent-eval/eval'
+
+const IGNORE_DIRS = new Set(['.git', '.next', 'node_modules', 'dist', 'build'])
+
+function readSource(dir: string): string[] {
+ if (!existsSync(dir)) return []
+ return readdirSync(dir).flatMap((entry) => {
+ if (IGNORE_DIRS.has(entry)) return []
+ const path = join(dir, entry)
+ if (statSync(path).isDirectory()) return readSource(path)
+ if (entry === 'EVAL.ts' || !/\.(ts|tsx|js|jsx)$/.test(entry)) return []
+ return readFileSync(path, 'utf8')
+ })
}
-test('Component imports connection from next/server', () => {
- const content = readAppFiles()
+const source = readSource(process.cwd()).join('\n')
+const config = readFileSync(join(process.cwd(), 'next.config.ts'), 'utf8')
- // Should import connection from next/server
- expect(content).toMatch(/import.*connection.*from\s+['"]next\/server['"]/)
+test('enables Cache Components without opt-outs', () => {
+ expect(config).toMatch(/cacheComponents\s*:\s*true/)
+ expect(source).not.toMatch(/export\s+(?:const|var|let)\s+instant\s*=\s*false/)
})
-test('Component uses await connection()', () => {
- const content = readAppFiles()
-
- // connection() returns a Promise and must be awaited
- expect(content).toMatch(/await\s+connection\s*\(\s*\)/)
+test('uses an explicit synchronous I/O boundary', () => {
+ expect(source).toMatch(/\b(?:io|connection)\s*\(/)
+ expect(source).toMatch(/]/)
})
-test('Component is async', () => {
- const content = readAppFiles()
-
- // Component must be async to use await
- expect(content).toMatch(/async\s+function|export\s+default\s+async/)
+test('keeps the operations frame useful while metadata loads', async () => {
+ await expect(environment).toSatisfyCriterion(
+ `The stable Operations navigation and page frame must remain outside the Suspense boundary used for request metadata. Accept equivalent component and file organization.`
+ )
})
-test('Component uses Date for timestamp', () => {
- const content = readAppFiles()
-
- // Should use Date for generating timestamp
- expect(content).toMatch(/new\s+Date\s*\(|Date\.now\s*\(/)
+test('keeps request metadata fresh', async () => {
+ await expect(environment).toSatisfyCriterion(
+ `The request identifier, current time, and sampling value must remain request-specific. Render them in a focused async child that awaits io() or connection() before calling randomUUID, new Date or Date.now, and Math.random. Prefer io() unless the implementation specifically needs to wait for a real user request. Accept equivalent component and file organization.`
+ )
})
-test('Does NOT use unstable_noStore (deprecated)', () => {
- const content = readAppFiles()
-
- // Should NOT use deprecated unstable_noStore
- expect(content).not.toMatch(/unstable_noStore/)
-})
-
-test('Does NOT use force-dynamic segment config as primary approach', () => {
- const content = readAppFiles()
-
- // Should use connection() as the primary approach instead of segment config
- const hasForceDynamic =
- content.includes("dynamic = 'force-dynamic'") ||
- content.includes('dynamic = "force-dynamic"')
- const hasConnection = /connection\s*\(\s*\)/.test(content)
-
- // Enforce that connection() is the primary dynamic approach
- // If force-dynamic is used, it cannot be the only dynamic mechanism
- // When connection() is available/used, force-dynamic should not be redundantly added
- if (hasForceDynamic && !hasConnection) {
- // force-dynamic is being used as the sole dynamic mechanism (wrong)
- expect.fail(
- 'force-dynamic segment config should not be the primary approach - use connection() instead'
- )
- }
+test('does not reuse request metadata', async () => {
+ await expect(environment).toSatisfyCriterion(
+ `The request identifier, current time, and sampling value must not be cached, evaluated at module scope, or replaced with static values. Reject any solution that can reuse those values across requests. Accept equivalent component and file organization.`
+ )
})
diff --git a/evals/evals/agent-035-connection-dynamic/PROMPT.md b/evals/evals/agent-035-connection-dynamic/PROMPT.md
index 70b64e401bda..bfb437bc2894 100644
--- a/evals/evals/agent-035-connection-dynamic/PROMPT.md
+++ b/evals/evals/agent-035-connection-dynamic/PROMPT.md
@@ -1 +1 @@
-Create a server component that displays the current server timestamp on each request. The timestamp should be different on every page load (not prerendered at build time). Use Next.js's recommended approach for opting out of static prerendering when you need dynamic data without using cookies/headers.
+Enable Cache Components for this operations console. The navigation must stay immediately useful, while the request identifier, current time, and sampling value must still be generated per request. Finish with a production build and do not leave routes opted out.
diff --git a/evals/evals/agent-035-connection-dynamic/app/layout.tsx b/evals/evals/agent-035-connection-dynamic/app/layout.tsx
index 23429283cae0..03a7ce6ef914 100644
--- a/evals/evals/agent-035-connection-dynamic/app/layout.tsx
+++ b/evals/evals/agent-035-connection-dynamic/app/layout.tsx
@@ -1,18 +1,14 @@
-import type { Metadata } from 'next'
+import type { ReactNode } from 'react'
+import { RequestMetadata } from '@/components/request-metadata'
-export const metadata: Metadata = {
- title: 'Random Number',
- description: 'Display a random number on each request',
-}
-
-export default function RootLayout({
- children,
-}: Readonly<{
- children: React.ReactNode
-}>) {
+export default function RootLayout({ children }: { children: ReactNode }) {
return (
- {children}
+
+
+
+ {children}
+
)
}
diff --git a/evals/evals/agent-035-connection-dynamic/app/page.tsx b/evals/evals/agent-035-connection-dynamic/app/page.tsx
index bc360b667d65..20adbd86f3fd 100644
--- a/evals/evals/agent-035-connection-dynamic/app/page.tsx
+++ b/evals/evals/agent-035-connection-dynamic/app/page.tsx
@@ -1,8 +1,8 @@
-export default function Home() {
+export default function HomePage() {
return (
-
Random Number Generator
-
Please implement a random number that changes on each request.
+
Deployment health
+
All systems operational
)
}
diff --git a/evals/evals/agent-035-connection-dynamic/components/request-metadata.tsx b/evals/evals/agent-035-connection-dynamic/components/request-metadata.tsx
new file mode 100644
index 000000000000..6c191a0f78da
--- /dev/null
+++ b/evals/evals/agent-035-connection-dynamic/components/request-metadata.tsx
@@ -0,0 +1,13 @@
+import { randomUUID } from 'node:crypto'
+
+export function RequestMetadata() {
+ const generatedAt = new Date().toISOString()
+ const sample = Math.random()
+ const requestId = randomUUID()
+
+ return (
+
+ )
+}
diff --git a/evals/evals/agent-035-connection-dynamic/tsconfig.json b/evals/evals/agent-035-connection-dynamic/tsconfig.json
index 64c21044c49f..8eb9f7f78971 100644
--- a/evals/evals/agent-035-connection-dynamic/tsconfig.json
+++ b/evals/evals/agent-035-connection-dynamic/tsconfig.json
@@ -23,5 +23,5 @@
"target": "ES2017"
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
- "exclude": ["node_modules"]
+ "exclude": ["node_modules", "EVAL.ts"]
}
diff --git a/evals/evals/agent-040-instant/EVAL.ts b/evals/evals/agent-040-instant/EVAL.ts
index 6764f178f0ba..aceef5bb71d6 100644
--- a/evals/evals/agent-040-instant/EVAL.ts
+++ b/evals/evals/agent-040-instant/EVAL.ts
@@ -1,67 +1,43 @@
/**
- * Instant Navigations
+ * Instant Validation
*
- * Verifies that the agent enables instant navigation on the product route.
- *
- * The eval harness runs `next build` as a script — with `cacheComponents: true`,
- * the build validates that the caching structure produces an instant static shell.
- * This test checks the agent actually exported `instant` (the build would
- * pass without it, since Suspense alone satisfies the build on a static route).
+ * Starts with a non-instant navigation and verifies that the agent follows the
+ * framework's default validation feedback to produce a meaningful Cache
+ * Components shell: the stable title is available immediately while live
+ * inventory streams behind a Suspense boundary.
*/
import { expect, test } from 'vitest'
-import { readFileSync, existsSync, readdirSync, statSync } from 'fs'
-import { join } from 'path'
-
-type SourceFile = { path: string; content: string }
-
-const IGNORE_DIRS = new Set([
- '.git',
- '.next',
- 'node_modules',
- 'dist',
- 'build',
- 'coverage',
-])
+import { environment } from '@vercel/agent-eval/eval'
-const IGNORE_FILES = new Set(['EVAL.ts', 'PROMPT.md'])
+test('keeps the product title in the static shell', async () => {
+ await expect(environment).toSatisfyCriterion(
+ `The product title "Premium Widget" must render in the product page's static shell. It must remain outside the Suspense boundary and async work used for the live inventory, so the title is available before the inventory resolves.
-function readSourceFiles(dir: string): SourceFile[] {
- if (!existsSync(dir)) return []
-
- const files: SourceFile[] = []
- for (const entry of readdirSync(dir)) {
- if (IGNORE_DIRS.has(entry)) continue
-
- const fullPath = join(dir, entry)
- const stats = statSync(fullPath)
+Accept equivalent component and file organization. Reject solutions where the page awaits the inventory before returning or places the title behind the inventory boundary.`
+ )
+})
- if (stats.isDirectory()) {
- files.push(...readSourceFiles(fullPath))
- continue
- }
+test('streams inventory behind a meaningful fallback', async () => {
+ await expect(environment).toSatisfyCriterion(
+ `The inventory count and price must render from an async child beneath a Suspense boundary with a visible, meaningful fallback. The product page must be able to return its title and fallback without waiting for getInventory().
- if (IGNORE_FILES.has(entry)) continue
+Accept equivalent component and file organization. Reject a null or empty fallback, or a solution where the page awaits inventory before returning.`
+ )
+})
- if (/\.(ts|tsx|js|jsx)$/.test(entry)) {
- files.push({
- path: fullPath,
- content: readFileSync(fullPath, 'utf-8'),
- })
- }
- }
+test('keeps inventory live and uncached', async () => {
+ await expect(environment).toSatisfyCriterion(
+ `The randomized inventory must remain request-time data. Preserve the runtime boundary in getInventory(), such as connection(), and do not cache the inventory or replace its count and price with static values.
- return files
-}
+Accept equivalent component and file organization. Reject any solution that makes the randomized inventory reusable across requests.`
+ )
+})
-const sourceFiles = readSourceFiles(process.cwd())
+test('keeps default instant validation enabled', async () => {
+ await expect(environment).toSatisfyCriterion(
+ `The solution must keep the framework's default instant validation enabled and solve the static-shell problem directly. It must not add instant = false, disable Cache Components, or use Partial Prefetching as a substitute for a valid product shell.
-test('instant exported from the product route', () => {
- const productPage = sourceFiles.find(
- (f) => f.path.includes('product') && /page\.(tsx?|jsx?)$/.test(f.path)
+Accept equivalent component and file organization.`
)
-
- expect(productPage).toBeDefined()
- expect(productPage!.content).toMatch(/export\s+(const|var|let)\s+instant\b/)
- expect(productPage!.content).toMatch(/prefetch\s*:\s*['"]static['"]/)
})
diff --git a/evals/evals/agent-040-instant/PROMPT.md b/evals/evals/agent-040-instant/PROMPT.md
index f8cf59cb0706..5ea6f2295695 100644
--- a/evals/evals/agent-040-instant/PROMPT.md
+++ b/evals/evals/agent-040-instant/PROMPT.md
@@ -1 +1 @@
-Navigating from home to the product page is slow. The title should appear immediately.
+Navigating from home to the product page is slow. Make the product title appear immediately while the live inventory loads.
diff --git a/evals/evals/agent-040-instant/tsconfig.json b/evals/evals/agent-040-instant/tsconfig.json
index 64c21044c49f..8eb9f7f78971 100644
--- a/evals/evals/agent-040-instant/tsconfig.json
+++ b/evals/evals/agent-040-instant/tsconfig.json
@@ -23,5 +23,5 @@
"target": "ES2017"
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
- "exclude": ["node_modules"]
+ "exclude": ["node_modules", "EVAL.ts"]
}
diff --git a/evals/evals/agent-042-enable-ppr/EVAL.ts b/evals/evals/agent-042-enable-ppr/EVAL.ts
index 20606268172d..ff209bf284f9 100644
--- a/evals/evals/agent-042-enable-ppr/EVAL.ts
+++ b/evals/evals/agent-042-enable-ppr/EVAL.ts
@@ -1,31 +1,52 @@
/**
- * Enable PPR
+ * Cache Components: route config semantics
*
- * Tests whether the agent knows that partial pre-rendering is enabled via
- * `cacheComponents: true` in next.config.ts, NOT the old
- * `experimental: { ppr: true }` flag.
- *
- * Tricky because most training data and older docs reference the experimental
- * flag. The current way to enable PPR in Next.js 16 is `cacheComponents: true`.
+ * Verifies that adoption translates legacy configuration instead of deleting
+ * behavior or silencing validation.
*/
import { expect, test } from 'vitest'
-import { readFileSync } from 'fs'
+import { existsSync, readFileSync, readdirSync, statSync } from 'fs'
import { join } from 'path'
+import { environment } from '@vercel/agent-eval/eval'
+
+const IGNORE_DIRS = new Set(['.git', '.next', 'node_modules', 'dist', 'build'])
+
+function readSource(dir: string): string[] {
+ if (!existsSync(dir)) return []
+ return readdirSync(dir).flatMap((entry) => {
+ if (IGNORE_DIRS.has(entry)) return []
+ const path = join(dir, entry)
+ if (statSync(path).isDirectory()) return readSource(path)
+ if (entry === 'EVAL.ts' || !/\.(ts|tsx|js|jsx)$/.test(entry)) return []
+ return readFileSync(path, 'utf8')
+ })
+}
-test('Enables PPR via cacheComponents in next.config', () => {
- const config = readFileSync(join(process.cwd(), 'next.config.ts'), 'utf-8')
+const source = readSource(process.cwd()).join('\n')
+const config = readFileSync(join(process.cwd(), 'next.config.ts'), 'utf8')
+test('enables the current Cache Components config only', () => {
expect(config).toMatch(/cacheComponents\s*:\s*true/)
+ expect(config).not.toMatch(/dynamicIO\s*:/)
+ expect(config).not.toMatch(/useCache\s*:/)
})
-test('Does not use the old experimental.ppr flag', () => {
- const config = readFileSync(join(process.cwd(), 'next.config.ts'), 'utf-8')
+test('removes route opt-outs and incompatible segment config', () => {
+ expect(source).not.toMatch(/export\s+(?:const|var|let)\s+instant\s*=\s*false/)
+ expect(source).not.toMatch(
+ /export\s+(?:const|var|let)\s+(?:dynamic|revalidate|fetchCache)\s*=/
+ )
+})
- // Strip comments to avoid false positives from explanatory comments
- const stripped = config
- .replace(/\/\*[\s\S]*?\*\//g, '')
- .replace(/\/\/.*$/gm, '')
+test('preserves the hourly newsroom feed', async () => {
+ await expect(environment).toSatisfyCriterion(
+ `The newsroom feed must remain cached with an explicit approximately-hourly cache lifetime. A focused data-level cache is valid, and a page-level cache is also valid because the Newsroom page contains only the feed and stable framing with the same lifetime. Reject solutions that remove or materially change the feed's hourly refresh behavior.`
+ )
+})
- expect(stripped).not.toMatch(/ppr\s*:\s*true/)
+test('keeps editor preview request-specific', async () => {
+ await expect(environment).toSatisfyCriterion(
+ `The editor preview must still read its cookie at request time outside every public cache. Place the request-specific preview below Suspense or equivalent meaningful loading UI while leaving useful stable preview framing in the static shell. Reject solutions that cache the cookie value, hide the entire preview page behind one fallback, or remove the request-specific behavior.`
+ )
})
diff --git a/evals/evals/agent-042-enable-ppr/PROMPT.md b/evals/evals/agent-042-enable-ppr/PROMPT.md
index 2f9a333ba322..97820f8fcf1b 100644
--- a/evals/evals/agent-042-enable-ppr/PROMPT.md
+++ b/evals/evals/agent-042-enable-ppr/PROMPT.md
@@ -1 +1 @@
-Enable partial pre-rendering for this app.
+Move this publishing app to Cache Components in one branch. Keep the newsroom feed on its hourly refresh policy and keep editor preview request-specific. Finish with a production build without leaving routes opted out.
diff --git a/evals/evals/agent-042-enable-ppr/app/ProductList.tsx b/evals/evals/agent-042-enable-ppr/app/ProductList.tsx
deleted file mode 100644
index 748a9909edfe..000000000000
--- a/evals/evals/agent-042-enable-ppr/app/ProductList.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-async function getProducts() {
- const res = await fetch('https://api.example.com/products')
- return res.json()
-}
-
-export async function ProductList() {
- const products = await getProducts()
-
- return (
-
+
+ )
+}
diff --git a/evals/evals/agent-042-enable-ppr/next.config.ts b/evals/evals/agent-042-enable-ppr/next.config.ts
index e4f5738a310b..5c906c55af96 100644
--- a/evals/evals/agent-042-enable-ppr/next.config.ts
+++ b/evals/evals/agent-042-enable-ppr/next.config.ts
@@ -1,5 +1,10 @@
import type { NextConfig } from 'next'
-const nextConfig: NextConfig = {}
+const nextConfig: NextConfig = {
+ experimental: {
+ // @ts-expect-error -- legacy config retained for the migration exercise
+ dynamicIO: true,
+ },
+}
export default nextConfig
diff --git a/evals/evals/agent-042-enable-ppr/tsconfig.json b/evals/evals/agent-042-enable-ppr/tsconfig.json
index 00978ef407fd..cc321ed0658b 100644
--- a/evals/evals/agent-042-enable-ppr/tsconfig.json
+++ b/evals/evals/agent-042-enable-ppr/tsconfig.json
@@ -23,5 +23,5 @@
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
- "exclude": ["node_modules"]
+ "exclude": ["node_modules", "EVAL.ts"]
}
diff --git a/evals/evals/agent-048-prefetch-url-data/EVAL.ts b/evals/evals/agent-048-prefetch-url-data/EVAL.ts
new file mode 100644
index 000000000000..c0de1576146b
--- /dev/null
+++ b/evals/evals/agent-048-prefetch-url-data/EVAL.ts
@@ -0,0 +1,84 @@
+/**
+ * Prefetch URL data
+ *
+ * Verifies the basic Partial Prefetching inclusion case: the agent caches data
+ * that depends on searchParams and makes it available in a per-link prefetch.
+ */
+
+import { expect, test } from 'vitest'
+import { existsSync, readFileSync, readdirSync, statSync } from 'fs'
+import { join } from 'path'
+import { environment } from '@vercel/agent-eval/eval'
+
+const IGNORE_DIRS = new Set([
+ '.git',
+ '.next',
+ 'node_modules',
+ 'dist',
+ 'build',
+ 'coverage',
+])
+
+const IGNORE_FILES = new Set(['EVAL.ts', 'PROMPT.md'])
+
+function stripComments(source: string): string {
+ return source.replace(/\/\*[\s\S]*?\*\//g, '').replace(/\/\/.*$/gm, '')
+}
+
+function readSourceFiles(dir: string): string[] {
+ if (!existsSync(dir)) return []
+
+ const files: string[] = []
+ for (const entry of readdirSync(dir)) {
+ if (IGNORE_DIRS.has(entry)) continue
+
+ const fullPath = join(dir, entry)
+ const stats = statSync(fullPath)
+ if (stats.isDirectory()) {
+ files.push(...readSourceFiles(fullPath))
+ } else if (!IGNORE_FILES.has(entry) && /\.(ts|tsx|js|jsx)$/.test(entry)) {
+ files.push(stripComments(readFileSync(fullPath, 'utf-8')))
+ }
+ }
+
+ return files
+}
+
+const source = readSourceFiles(process.cwd()).join('\n---FILE---\n')
+const config = stripComments(
+ readFileSync(join(process.cwd(), 'next.config.ts'), 'utf-8')
+)
+
+test('enables Partial Prefetching globally or for the catalog route', () => {
+ expect(
+ /partialPrefetching\s*:\s*true/.test(config) ||
+ /export\s+const\s+prefetch\s*=\s*['"]partial['"]/.test(source)
+ ).toBe(true)
+})
+
+test('preserves the category URL structure', () => {
+ expect(source).toMatch(/\/catalog\?category=/)
+ expect(source).toMatch(/\bsearchParams\b/)
+})
+
+test('opts the category links into per-link prefetching', () => {
+ expect(source).toMatch(/]*\bprefetch(?:\s*=\s*\{true\})?(?:\s|>)/)
+})
+
+test('caches the category data', () => {
+ expect(source).toMatch(/['"]use cache['"]/)
+})
+
+test('does not use a navigation-stage boundary', () => {
+ expect(source).not.toMatch(/\bunstable_navigation\b/)
+})
+
+test('prefetches the selected category and cached products', async () => {
+ await expect(environment).toSatisfyCriterion(
+ `The implementation must make the selected category heading and its cached products available from the completed per-link prefetch before navigation.
+
+A correct solution preserves the existing /catalog?category=... URLs, enables Partial Prefetching globally or on the catalog route, and opts the category Links into per-link prefetching with prefetch={true} or the equivalent bare prefetch prop. The catalog route reads the category from searchParams beneath Suspense and renders both the matching heading and getProducts(category) result in the prefetched result. The category data must use a use cache scope, with either the default cache profile or an explicit cacheLife(); searchParams must remain outside that cache scope. Keep the Suspense boundary: the shared App Shell uses its fallback, while a completed per-link prefetch can resolve the cached content inside it before navigation.
+
+This is the basic URL-data inclusion case. Do not use unstable_navigation(), unstable_prefetch(), connection(), or an uncached request boundary to defer the category content. Reject solutions that only prefetch the reusable Catalog shell while leaving the selected heading or product list behind the loading fallback.`
+ )
+})
diff --git a/evals/evals/agent-048-prefetch-url-data/PROMPT.md b/evals/evals/agent-048-prefetch-url-data/PROMPT.md
new file mode 100644
index 000000000000..6c8e5d0309c1
--- /dev/null
+++ b/evals/evals/agent-048-prefetch-url-data/PROMPT.md
@@ -0,0 +1 @@
+Keep the existing `/catalog?category=...` URLs. Prepare each category link's URL-specific heading and products before the click so selecting it can navigate immediately without showing the loading state. Do not make the click wait for a full server render.
diff --git a/evals/evals/agent-048-prefetch-url-data/app/catalog/page.tsx b/evals/evals/agent-048-prefetch-url-data/app/catalog/page.tsx
new file mode 100644
index 000000000000..4b39f6859c1c
--- /dev/null
+++ b/evals/evals/agent-048-prefetch-url-data/app/catalog/page.tsx
@@ -0,0 +1,39 @@
+import { Suspense } from 'react'
+import { getProducts } from '@/lib/data'
+
+type CatalogSearchParams = Promise<{ category?: string }>
+
+export default function CatalogPage({
+ searchParams,
+}: {
+ searchParams: CatalogSearchParams
+}) {
+ return (
+
+