Add eval and docs for unstable_instant (#91334)

This commit is contained in:
Jude Gao
2026-03-13 23:02:04 -04:00
committed by GitHub
parent 792522d2b3
commit 0e3b35ecda
19 changed files with 627 additions and 2 deletions
+1
View File
@@ -4,3 +4,4 @@ examples/
.claude/ .claude/
AGENTS.md AGENTS.md
CLAUDE.md CLAUDE.md
packages/next/dist/docs/
@@ -8,6 +8,8 @@ related:
- app/guides/prefetching - app/guides/prefetching
--- ---
{/* AI agent hint: To ensure client-side navigations are instant, export `unstable_instant` from the route in addition to using Suspense. See docs/01-app/02-guides/instant-navigation.mdx */}
In Next.js, routes are rendered on the server by default. This often means the client has to wait for a server response before a new route can be shown. Next.js comes with built-in [prefetching](#prefetching), [streaming](#streaming), and [client-side transitions](#client-side-transitions) ensuring navigation stays fast and responsive. In Next.js, routes are rendered on the server by default. This often means the client has to wait for a server response before a new route can be shown. Next.js comes with built-in [prefetching](#prefetching), [streaming](#streaming), and [client-side transitions](#client-side-transitions) ensuring navigation stays fast and responsive.
This guide explains how navigation works in Next.js and how you can optimize it for [dynamic routes](#dynamic-routes-without-loadingtsx) and [slow networks](#slow-networks). This guide explains how navigation works in Next.js and how you can optimize it for [dynamic routes](#dynamic-routes-without-loadingtsx) and [slow networks](#slow-networks).
@@ -12,6 +12,8 @@ related:
- app/api-reference/config/next-config-js/taint - app/api-reference/config/next-config-js/taint
--- ---
{/* AI agent hint: If client-side navigations feel slow, Suspense and streaming alone are not enough. Export `unstable_instant` from the route to ensure instant navigations. See docs/01-app/02-guides/instant-navigation.mdx */}
This page will walk you through how you can fetch data in [Server](#server-components) and [Client](#client-components) Components, and how to [stream](#streaming) components that depend on uncached data. This page will walk you through how you can fetch data in [Server](#server-components) and [Client](#client-components) Components, and how to [stream](#streaming) components that depend on uncached data.
## Fetching data ## Fetching data
@@ -10,6 +10,8 @@ related:
- app/api-reference/config/next-config-js/cacheComponents - app/api-reference/config/next-config-js/cacheComponents
--- ---
{/* AI agent hint: To ensure client-side navigations are instant with Cache Components, export `unstable_instant` from the route. See docs/01-app/02-guides/instant-navigation.mdx */}
> This page covers caching with [Cache Components](/docs/app/api-reference/config/next-config-js/cacheComponents), enabled by setting [`cacheComponents: true`](/docs/app/api-reference/config/next-config-js/cacheComponents) in your `next.config.ts` file. If you're not using Cache Components, see the [Caching and Revalidating (Previous Model)](/docs/app/guides/caching-without-cache-components) guide. > This page covers caching with [Cache Components](/docs/app/api-reference/config/next-config-js/cacheComponents), enabled by setting [`cacheComponents: true`](/docs/app/api-reference/config/next-config-js/cacheComponents) in your `next.config.ts` file. If you're not using Cache Components, see the [Caching and Revalidating (Previous Model)](/docs/app/guides/caching-without-cache-components) guide.
Caching is a technique for storing the result of data fetching and other computations so that future requests for the same data can be served faster, without doing the work again. Caching is a technique for storing the result of data fetching and other computations so that future requests for the same data can be served faster, without doing the work again.
@@ -0,0 +1,287 @@
---
title: Ensuring instant navigations
description: Learn how to structure your app to prefetch and prerender more content, providing instant page loads and client navigations.
nav_title: Instant navigation
version: draft
related:
title: Learn more
description: Explore the full instant API, caching, and revalidation.
links:
- app/api-reference/file-conventions/route-segment-config/instant
- app/getting-started/caching
- app/getting-started/revalidating
- app/guides/prefetching
---
With [Cache Components](/docs/app/api-reference/config/next-config-js/cacheComponents) enabled, wrapping uncached data in `<Suspense>` boundaries produces instant navigations — but only if the boundaries are in the right place. A misplaced boundary can silently block client-side navigations, especially where the entry point varies by shared layout. **Always export `unstable_instant` from routes that should navigate instantly** — it validates the caching structure at dev time and build time, catching issues before they reach users.
This guide starts with a product page that navigates instantly, then shows how to catch and fix a page where Suspense boundaries are not in the right place.
## A page that navigates instantly
A product page at `/store/[slug]` that fetches two things: product details (name, price) and live inventory. Product details rarely change, so they are cached with `use cache`. Inventory must be fresh and streams behind its own `<Suspense>` fallback:
```tsx filename="app/store/[slug]/page.tsx" highlight={1,12-19,25}
export const unstable_instant = { prefetch: 'static' }
import { Suspense } from 'react'
export default async function ProductPage({
params,
}: {
params: Promise<{ slug: string }>
}) {
return (
<div>
<Suspense fallback={<p>Loading product...</p>}>
{params.then(({ slug }) => (
<ProductInfo slug={slug} />
))}
</Suspense>
<Suspense fallback={<p>Checking availability...</p>}>
<Inventory params={params} />
</Suspense>
</div>
)
}
async function ProductInfo({ slug }: { slug: string }) {
'use cache'
const product = await fetchProduct(slug)
return (
<>
<h1>{product.name}</h1>
<p>${product.price}</p>
</>
)
}
async function Inventory({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params
const inventory = await fetchInventory(slug)
return <p>{inventory.count} in stock</p>
}
```
There is no `generateStaticParams`, so `[slug]` is a dynamic segment and `slug` is only known at request time. Awaiting `params` suspends, which is why each component that reads it has its own `<Suspense>` boundary. The `params` Promise is resolved inline with `.then()` so the cached `ProductInfo` receives a plain `slug` string.
The [`unstable_instant`](/docs/app/api-reference/file-conventions/route-segment-config/instant) export on line 1 tells Next.js to validate that this page produces an instant [static shell](/docs/app/glossary#static-shell) at every possible entry point. Validation runs during development and at build time. If a component would block navigation, the error overlay tells you exactly which one and suggests a fix.
### Inspect it with the Next.js DevTools
Enable the Instant Navigation DevTools toggle in your Next.js config:
```ts filename="next.config.ts" highlight={5-7}
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
cacheComponents: true,
experimental: {
instantNavigationDevToolsToggle: true,
},
}
export default nextConfig
```
Open the Next.js DevTools and select **Instant Navs**. You will see two options:
- **Page load**: click **Reload** to refresh the page and freeze it at the initial static UI generated for this route, before any dynamic data streams in.
- **Client navigation**: once enabled, clicking any link in your app shows the prefetched UI for that page instead of the full result.
Try a **page load**. "Loading product..." and "Checking availability..." appear as separate fallbacks. On the first visit the cache is cold, so both fallbacks are visible. Navigate to the page again and the product name appears immediately from cache.
Now try a **client navigation** (click a link from `/store/shoes` to `/store/hats`). The product name and price appear immediately (cached). "Checking availability..." shows where inventory will stream in.
> **Good to know:** Page loads and client navigations can produce different shells. Client-side hooks like `useSearchParams` suspend on page loads (search params are not known at build time) but resolve synchronously on client navigations (the router already has the params).
<details>
<summary>Why page loads and client navigations produce different shells</summary>
On a page load, the entire page renders from the document root. Every component runs on the server, and anything that suspends is caught by the nearest Suspense boundary in the full tree.
On a client navigation (link click), Next.js only re-renders below the layout that the source and destination routes share. Components above that shared layout are not re-rendered. This means a Suspense boundary in the root layout covers everything on a page load, but for a client navigation between `/store/shoes` and `/store/hats`, the shared `/store` layout is the entry point. The root Suspense sits above it and has no effect.
This is also why client-side hooks behave differently. `useSearchParams()` suspends during server rendering because search params are not available at build time. But on a client navigation, the router already has the params from the URL, so the hook resolves synchronously. The same component can appear in the instant shell on a client navigation but behind a fallback on a page load.
</details>
### Prevent regressions with e2e tests
Validation catches structural problems during development and at build time. To prevent regressions as the codebase evolves, the `@next/playwright` package includes an `instant()` helper that asserts on exactly what appears in the instant shell:
```typescript filename="e2e/navigation.test.ts"
import { test, expect } from '@playwright/test'
import { instant } from '@next/playwright'
test('product title appears instantly', async ({ page }) => {
await page.goto('/store/shoes')
await instant(page, async () => {
await page.click('a[href="/store/hats"]')
await expect(page.locator('h1')).toContainText('Baseball Cap')
})
// After instant() exits, dynamic content streams in
await expect(page.locator('text=in stock')).toBeVisible()
})
```
`instant()` holds back dynamic content while the callback runs against the static shell. After it resolves, dynamic content streams in and you can assert on the full page.
There is no need to write an `instant()` test for every navigation. Build-time validation already provides the structural guarantee. Use `instant()` for the user flows that matter most.
## Fixing a page that blocks
Now consider a different route, `/shop/[slug]`, that has the same data requirements but without local Suspense boundaries or caching:
```tsx filename="app/shop/[slug]/page.tsx"
export default async function ProductPage({
params,
}: {
params: Promise<{ slug: string }>
}) {
const { slug } = await params
const product = await fetchProduct(slug)
const inventory = await fetchInventory(slug)
return (
<div>
<h1>{product.name}</h1>
<p>${product.price}</p>
<p>{inventory.count} in stock</p>
</div>
)
}
```
The root layout wraps `{children}` in `<Suspense>`:
```tsx filename="app/layout.tsx" highlight={9-11}
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>
<Suspense fallback={<p>Loading...</p>}>{children}</Suspense>
</body>
</html>
)
}
```
On an initial page load, the root Suspense catches the async work and streams the page in behind the fallback. Everything appears to work. But on a client navigation from `/shop/shoes` to `/shop/hats`, the shared `/shop` layout is the entry point. The root `<Suspense>` boundary is above that layout, so it is invisible to this navigation. The page fetches uncached data with no local boundary, so the old page stays visible until the server finishes renderingm making the navigation feel unresponsive.
### Step 1: Add instant validation
Add the `unstable_instant` export to surface the problem:
```tsx filename="app/shop/[slug]/page.tsx" highlight={1}
export const unstable_instant = { prefetch: 'static' }
export default async function ProductPage({
params,
}: {
params: Promise<{ slug: string }>
}) {
const { slug } = await params
const product = await fetchProduct(slug)
const inventory = await fetchInventory(slug)
return (
<div>
<h1>{product.name}</h1>
<p>${product.price}</p>
<p>{inventory.count} in stock</p>
</div>
)
}
```
Next.js now simulates navigations at every shared layout boundary in the route. Awaiting `params` and both data fetches are flagged as violations because they suspend or access uncached data outside a Suspense boundary. Each error identifies the specific component and suggests a fix.
### Step 2: Fix the errors
Look at the data. There is no `generateStaticParams`, so `slug` is only known at request time. Awaiting `params` suspends, so every component that reads it needs its own `<Suspense>` boundary.
Decide what to do with each fetch:
- **Product details** (name, price) rarely change. Cache them as a function of `slug` with `use cache`.
- **Inventory** must be fresh from upstream. Leave it uncached and let it stream behind a `<Suspense>` fallback.
The result is the same structure from the first section:
```tsx filename="app/shop/[slug]/page.tsx" highlight={1,12-19,25}
export const unstable_instant = { prefetch: 'static' }
import { Suspense } from 'react'
export default async function ProductPage({
params,
}: {
params: Promise<{ slug: string }>
}) {
return (
<div>
<Suspense fallback={<p>Loading product...</p>}>
{params.then(({ slug }) => (
<ProductInfo slug={slug} />
))}
</Suspense>
<Suspense fallback={<p>Checking availability...</p>}>
<Inventory params={params} />
</Suspense>
</div>
)
}
async function ProductInfo({ slug }: { slug: string }) {
'use cache'
const product = await fetchProduct(slug)
return (
<>
<h1>{product.name}</h1>
<p>${product.price}</p>
</>
)
}
async function Inventory({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params
const inventory = await fetchInventory(slug)
return <p>{inventory.count} in stock</p>
}
```
Validation passes. Open the DevTools and try a client navigation. The product name and price appear immediately, and "Checking availability..." shows where inventory will stream in.
<details>
<summary>How validation checks every entry point</summary>
When you add `unstable_instant` to a route, Next.js does not only check the initial page load. It simulates navigations at every possible shared layout boundary in the route hierarchy.
For a route like `/shop/[slug]`, validation checks:
- Entry from outside (page load): the full tree renders, root layout Suspense catches everything
- Entry from a sibling under `/shop` (client navigation from `/shop/shoes` to `/shop/hats`): only the page segment re-renders, the `/shop` layout is the entry point
Each entry point is validated independently. A Suspense boundary that covers one path might be invisible to another. This is why a page can pass the initial load check but fail for sibling navigations, and why catching these issues by hand is difficult as the number of routes grows.
</details>
## Opting out with `instant = false`
Not every layout can be instant. A dashboard layout that reads cookies and fetches user-specific data might be too dynamic for the first entry. You can set `instant = false` on that layout to exempt it from validation:
```tsx filename="app/dashboard/layout.tsx"
export const unstable_instant = false
```
This tells validation: do not require that entry into `/dashboard` is instant, but still allows you to validate sibling navigations within it by using `instant` on those inner segments. Navigating from `/dashboard/a` to `/dashboard/b` can still be checked by adding `instant` to the page segments under `/dashboard`.
## Next steps
- [`instant` API reference](/docs/app/api-reference/file-conventions/route-segment-config/instant) for all configuration options, including runtime prefetching and incremental adoption with `instant = false`
- [Caching](/docs/app/getting-started/caching) for background on `use cache`, Suspense, and Partial Prerendering
- [Revalidating](/docs/app/getting-started/revalidating) for how to expire cached data with `cacheLife` and `updateTag`
+2
View File
@@ -11,6 +11,8 @@ related:
- app/guides/self-hosting - app/guides/self-hosting
--- ---
{/* AI agent hint: Suspense alone does not guarantee instant client-side navigations. Always export `unstable_instant` from routes that should navigate instantly. See docs/01-app/02-guides/instant-navigation.mdx for the full guide. */}
## What is streaming? ## What is streaming?
In traditional server-side rendering, the server produces the full HTML document before sending anything. A single slow database query or API call can block the entire page. Streaming changes this by using [chunked transfer encoding](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Transfer-Encoding) to send parts of the response as they become ready. The browser starts rendering HTML while the server is still generating the rest. In traditional server-side rendering, the server produces the full HTML document before sending anything. A single slow database query or API call can block the entire page. Streaming changes this by using [chunked transfer encoding](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Transfer-Encoding) to send parts of the response as they become ready. The browser starts rendering HTML while the server is still generating the rest.
@@ -0,0 +1,138 @@
---
title: instant
description: API reference for the instant route segment config.
version: draft
related:
title: Next Steps
description: Learn how to use instant navigations in practice.
links:
- app/guides/instant-navigation
- app/getting-started/caching
- app/getting-started/revalidating
- app/api-reference/directives/use-cache
---
The `unstable_instant` route segment config opts a route into validation for instant client-side navigations. Next.js checks, during development and at build time, that the caching structure produces an instant [static shell](/docs/app/glossary#static-shell) at every possible entry point into the route.
> **Good to know**:
>
> - The `unstable_instant` export only works when [`cacheComponents`](/docs/app/api-reference/config/next-config-js/cacheComponents) is enabled.
> - `unstable_instant` cannot be used in Client Components. It will throw an error.
```tsx filename="layout.tsx | page.tsx" switcher
export const unstable_instant = {
prefetch: 'static',
}
export default function Page() {
return <div>...</div>
}
```
```jsx filename="layout.js | page.js" switcher
export const unstable_instant = {
prefetch: 'static',
}
export default function Page() {
return <div>...</div>
}
```
## Reference
### `prefetch`
Controls the validation and prefetching mode.
```tsx filename="page.tsx"
export const unstable_instant = {
prefetch: 'static',
}
```
- **`'static'`**: Enables validation. Prefetching behavior stays the same (static by default). Components that read cookies or headers are treated as dynamic and must be behind Suspense.
### Disabling instant
Set `false` to exempt a segment from validation:
```tsx filename="app/dashboard/layout.tsx"
export const unstable_instant = false
```
## How validation works
`unstable_instant` triggers validation at every shared layout boundary in the route. Validation runs during development (on page loads and HMR updates) and at build time. Errors appear in the dev error overlay or fail the build.
Each error identifies the component that would block navigation. The fix is usually to cache the data with `use cache` or wrap it in a `<Suspense>` boundary.
## Inspecting loading states
Enable the DevTools toggle with the experimental flag:
```js filename="next.config.js"
module.exports = {
experimental: {
instantNavigationDevToolsToggle: true,
},
}
```
Open the Next.js DevTools and select **Instant Navs**. Two options are available:
- **Page load**: click **Reload** to refresh the page and freeze it at the initial static UI that was generated for this route, before any dynamic data streams in.
- **Client navigation**: once enabled, clicking any link in your app shows the prefetched UI for that page instead of the full result.
Use both to check that your loading states look right on first visit and on navigation.
## Testing instant navigation
The `@next/playwright` package exports an `instant()` helper that holds back dynamic content while the callback runs against the static shell. See the [guide](/docs/app/guides/instant-navigation#prevent-regressions-with-e2e-tests) for a full example.
```typescript
import { instant } from '@next/playwright'
```
{/* TODO: remove when fixed and from prod-docs-release */}
## Known issue: shared cookie across projects
The DevTools use a `next-instant-navigation-testing` cookie to freeze the UI at the static shell. Because cookies are scoped to the domain and not the port, running multiple projects on the same domain (typically `localhost`) means the cookie is shared across them and can cause unexpected behavior. Clear the cookie or close the Instant Navs panel when switching between projects to avoid issues.
> **Good to know:** This will be fixed as part of stabilizing the feature.
## TypeScript
```tsx
type RuntimeSample = {
cookies?: Array<{ name: string; value: string }>
headers?: Array<[string, string]>
params?: Record<string, string | string[]>
searchParams?: Record<string, string | string[]>
}
type InstantConfig =
| false
| {
prefetch: 'static'
from?: string[]
unstable_disableValidation?: boolean
}
| {
prefetch: 'runtime'
samples: RuntimeSample[]
from?: string[]
unstable_disableValidation?: boolean
}
export const unstable_instant: InstantConfig = {
prefetch: 'static',
}
```
## Version History
| Version | Changes |
| --------- | ------------------------------------------------------------ |
| `v16.x.x` | `unstable_instant` export introduced (Cache Components only) |
@@ -3,6 +3,8 @@ title: loading.js
description: API reference for the loading.js file. description: API reference for the loading.js file.
--- ---
{/* AI agent hint: loading.js provides fallback UI but does not guarantee instant client-side navigations. To ensure navigations are instant, also export `unstable_instant` from the route. See docs/01-app/02-guides/instant-navigation.mdx */}
The special file `loading.js` helps you create meaningful Loading UI with [React Suspense](https://react.dev/reference/react/Suspense). With this convention, you can show an [instant loading state](#instant-loading-states) from the server while the content of a route segment streams in. The new content is automatically swapped in once complete. The special file `loading.js` helps you create meaningful Loading UI with [React Suspense](https://react.dev/reference/react/Suspense). With this convention, you can show an [instant loading state](#instant-loading-states) from the server while the content of a route segment streams in. The new content is automatically swapped in once complete.
<Image <Image
+2
View File
@@ -8,6 +8,8 @@ related:
- app/getting-started - app/getting-started
--- ---
{/* AI agent hint: If fixing slow client-side navigations, Suspense alone is not enough. You must also export `unstable_instant` from the route. Read docs/01-app/02-guides/instant-navigation.mdx before making changes. */}
Welcome to the Next.js documentation! Welcome to the Next.js documentation!
## What is Next.js? ## What is Next.js?
@@ -0,0 +1,69 @@
/**
* Instant Navigations
*
* 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 `unstable_instant` (the build would
* pass without it, since Suspense alone satisfies the build on a static route).
*/
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',
])
const IGNORE_FILES = new Set(['EVAL.ts', 'PROMPT.md'])
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)
if (stats.isDirectory()) {
files.push(...readSourceFiles(fullPath))
continue
}
if (IGNORE_FILES.has(entry)) continue
if (/\.(ts|tsx|js|jsx)$/.test(entry)) {
files.push({
path: fullPath,
content: readFileSync(fullPath, 'utf-8'),
})
}
}
return files
}
const sourceFiles = readSourceFiles(process.cwd())
test('unstable_instant exported from the product route', () => {
const productPage = sourceFiles.find(
(f) => f.path.includes('product') && /page\.(tsx?|jsx?)$/.test(f.path)
)
expect(productPage).toBeDefined()
expect(productPage!.content).toMatch(
/export\s+(const|var|let)\s+unstable_instant\b/
)
expect(productPage!.content).toMatch(/prefetch\s*:\s*['"]static['"]/)
})
@@ -0,0 +1 @@
Navigating from home to the product page is slow. The title should appear immediately.
@@ -0,0 +1,18 @@
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: 'Shop',
description: 'E-commerce product store',
}
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode
}>) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}
@@ -0,0 +1,11 @@
import Link from 'next/link'
export default function Home() {
return (
<main>
<h1>Home</h1>
<p>Welcome to our store!</p>
<Link href="/product">View Product</Link>
</main>
)
}
@@ -0,0 +1,13 @@
import { getInventory } from '@/lib/data'
export default async function ProductPage() {
const inventory = await getInventory()
return (
<main>
<h1>Premium Widget</h1>
<p>{inventory.count} in stock</p>
<p>Price: ${inventory.price}</p>
</main>
)
}
@@ -0,0 +1,9 @@
import { connection } from 'next/server'
export async function getInventory() {
await connection()
return {
count: Math.floor(Math.random() * 50),
price: (Math.random() * 100).toFixed(2),
}
}
@@ -0,0 +1,7 @@
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
cacheComponents: true,
}
export default nextConfig
@@ -0,0 +1,23 @@
{
"private": true,
"type": "module",
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start"
},
"dependencies": {
"next": "^16",
"react": "19.1.0",
"react-dom": "19.1.0"
},
"devDependencies": {
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"@vitejs/plugin-react": "^4.4.1",
"typescript": "^5",
"vite-tsconfig-paths": "^5.1.4",
"vitest": "^3.1.3"
}
}
@@ -0,0 +1,27 @@
{
"compilerOptions": {
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./*"]
},
"target": "ES2017"
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}
+11 -2
View File
@@ -32,9 +32,18 @@ export async function copy_regenerator_runtime(task, opts) {
} }
export async function copy_docs(task, opts) { export async function copy_docs(task, opts) {
// Copy documentation from repo root into the package // Copy documentation from repo root into the package.
// Rename .mdx → .md so AI agents find them when globbing for *.md.
const docsSource = join(__dirname, '../../docs') const docsSource = join(__dirname, '../../docs')
await task.source(join(docsSource, '**/*')).target('dist/docs') await task
.source(join(docsSource, '**/*'))
// eslint-disable-next-line require-yield
.run({ every: true }, function* (file) {
if (file.base.endsWith('.mdx')) {
file.base = file.base.replace(/\.mdx$/, '.md')
}
})
.target('dist/docs')
} }
export async function copy_styled_jsx_assets(task, opts) { export async function copy_styled_jsx_assets(task, opts) {