Recipe-app main
This commit is contained in:
Generated
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
---
|
||||
title: Directives
|
||||
description: Directives are used to modify the behavior of your Next.js application.
|
||||
---
|
||||
|
||||
The following directives are available:
|
||||
Generated
Vendored
+176
@@ -0,0 +1,176 @@
|
||||
---
|
||||
title: 'use cache: private'
|
||||
description: 'Learn how to use the "use cache: private" directive to cache functions that access runtime request APIs.'
|
||||
version: experimental
|
||||
related:
|
||||
title: Related
|
||||
description: View related API references.
|
||||
links:
|
||||
- app/api-reference/directives/use-cache
|
||||
- app/api-reference/config/next-config-js/cacheComponents
|
||||
- app/api-reference/functions/cacheLife
|
||||
- app/api-reference/functions/cacheTag
|
||||
---
|
||||
|
||||
The `'use cache: private'` directive allows functions to access runtime request APIs like `cookies()`, `headers()`, and `searchParams` within a cached scope. However, results are **never stored on the server**, they're cached only in the browser's memory and do not persist across page reloads.
|
||||
|
||||
Reach for `'use cache: private'` when:
|
||||
|
||||
- You want to cache a function that already accesses runtime data, and refactoring to [move the runtime access outside and pass values as arguments](/docs/app/getting-started/caching#working-with-runtime-apis) is not practical.
|
||||
- Compliance requirements prevent storing certain data on the server, even temporarily
|
||||
|
||||
Because this directive accesses runtime data, the function executes on every server render and is excluded from running during [static shell](/docs/app/getting-started/caching#how-rendering-works) generation.
|
||||
|
||||
It is **not** possible to configure custom cache handlers for `'use cache: private'`.
|
||||
|
||||
For a comparison of the different cache directives, see [How `use cache: remote` differs from `use cache` and `use cache: private`](/docs/app/api-reference/directives/use-cache-remote#how-use-cache-remote-differs-from-use-cache-and-use-cache-private).
|
||||
|
||||
> **Good to know**: This directive is marked as `experimental` because it depends on runtime prefetching, which is not yet stable. Runtime prefetching is an upcoming feature that will let the router prefetch past the [static shell](/docs/app/getting-started/caching#how-rendering-works) into **any** cached scope, not just private caches.
|
||||
|
||||
## Usage
|
||||
|
||||
To use `'use cache: private'`, enable the [`cacheComponents`](/docs/app/api-reference/config/next-config-js/cacheComponents) flag in your `next.config.ts` file:
|
||||
|
||||
```tsx filename="next.config.ts" switcher
|
||||
import type { NextConfig } from 'next'
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
cacheComponents: true,
|
||||
}
|
||||
|
||||
export default nextConfig
|
||||
```
|
||||
|
||||
```jsx filename="next.config.js" switcher
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
cacheComponents: true,
|
||||
}
|
||||
|
||||
export default nextConfig
|
||||
```
|
||||
|
||||
Then add `'use cache: private'` to your function along with a `cacheLife` configuration.
|
||||
|
||||
> **Good to know**: This directive is not available in Route Handlers.
|
||||
|
||||
### Basic example
|
||||
|
||||
In this example, we demonstrate that you can access cookies within a `'use cache: private'` scope:
|
||||
|
||||
```tsx filename="app/product/[id]/page.tsx" switcher
|
||||
import { Suspense } from 'react'
|
||||
import { cookies } from 'next/headers'
|
||||
import { cacheLife, cacheTag } from 'next/cache'
|
||||
|
||||
export async function generateStaticParams() {
|
||||
return [{ id: '1' }]
|
||||
}
|
||||
|
||||
export default async function ProductPage({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ id: string }>
|
||||
}) {
|
||||
const { id } = await params
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ProductDetails id={id} />
|
||||
<Suspense fallback={<div>Loading recommendations...</div>}>
|
||||
<Recommendations productId={id} />
|
||||
</Suspense>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
async function Recommendations({ productId }: { productId: string }) {
|
||||
const recommendations = await getRecommendations(productId)
|
||||
|
||||
return (
|
||||
<div>
|
||||
{recommendations.map((rec) => (
|
||||
<ProductCard key={rec.id} product={rec} />
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
async function getRecommendations(productId: string) {
|
||||
'use cache: private'
|
||||
cacheTag(`recommendations-${productId}`)
|
||||
cacheLife({ stale: 60 })
|
||||
|
||||
// Access cookies within private cache functions
|
||||
const sessionId = (await cookies()).get('session-id')?.value || 'guest'
|
||||
|
||||
return getPersonalizedRecommendations(productId, sessionId)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/product/[id]/page.js" switcher
|
||||
import { Suspense } from 'react'
|
||||
import { cookies } from 'next/headers'
|
||||
import { cacheLife, cacheTag } from 'next/cache'
|
||||
|
||||
export async function generateStaticParams() {
|
||||
return [{ id: '1' }]
|
||||
}
|
||||
|
||||
export default async function ProductPage({ params }) {
|
||||
const { id } = await params
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ProductDetails id={id} />
|
||||
<Suspense fallback={<div>Loading recommendations...</div>}>
|
||||
<Recommendations productId={id} />
|
||||
</Suspense>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
async function Recommendations({ productId }) {
|
||||
const recommendations = await getRecommendations(productId)
|
||||
|
||||
return (
|
||||
<div>
|
||||
{recommendations.map((rec) => (
|
||||
<ProductCard key={rec.id} product={rec} />
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
async function getRecommendations(productId) {
|
||||
'use cache: private'
|
||||
cacheTag(`recommendations-${productId}`)
|
||||
cacheLife({ stale: 60 })
|
||||
|
||||
// Access cookies within private cache functions
|
||||
const sessionId = (await cookies()).get('session-id')?.value || 'guest'
|
||||
|
||||
return getPersonalizedRecommendations(productId, sessionId)
|
||||
}
|
||||
```
|
||||
|
||||
> **Good to know**: The `stale` time must be at least 30 seconds for runtime prefetching to work. See [`cacheLife` client cache behavior](/docs/app/api-reference/functions/cacheLife#client-cache-behavior) for details.
|
||||
|
||||
## Request APIs allowed in private caches
|
||||
|
||||
The following request-specific APIs can be used inside `'use cache: private'` functions:
|
||||
|
||||
| API | Allowed in `use cache` | Allowed in `'use cache: private'` |
|
||||
| -------------- | ---------------------- | --------------------------------- |
|
||||
| `cookies()` | No | Yes |
|
||||
| `headers()` | No | Yes |
|
||||
| `searchParams` | No | Yes |
|
||||
| `connection()` | No | No |
|
||||
|
||||
> **Note:** The [`connection()`](https://nextjs.org/docs/app/api-reference/functions/connection) API is prohibited in both `use cache` and `'use cache: private'` as it provides connection-specific information that cannot be safely cached.
|
||||
|
||||
## Version History
|
||||
|
||||
| Version | Changes |
|
||||
| --------- | -------------------------------------------------------------------- |
|
||||
| `v16.0.0` | `"use cache: private"` is enabled with the Cache Components feature. |
|
||||
Generated
Vendored
+610
@@ -0,0 +1,610 @@
|
||||
---
|
||||
title: 'use cache: remote'
|
||||
description: 'Learn how to use the "use cache: remote" directive for persistent, shared caching using remote cache handlers.'
|
||||
related:
|
||||
title: Related
|
||||
description: View related API references.
|
||||
links:
|
||||
- app/api-reference/directives/use-cache
|
||||
- app/api-reference/directives/use-cache-private
|
||||
- app/api-reference/config/next-config-js/cacheComponents
|
||||
- app/api-reference/config/next-config-js/cacheHandlers
|
||||
- app/api-reference/functions/cacheLife
|
||||
- app/api-reference/functions/cacheTag
|
||||
- app/api-reference/functions/connection
|
||||
---
|
||||
|
||||
While the `use cache` directive is sufficient for most application needs, you might notice that cached operations are re-running more often than expected, or that your upstream services (CMS, databases, external APIs) are getting more hits than you'd expect. This can happen because `use cache` stores entries in-memory, which has inherent limitations:
|
||||
|
||||
- Cache entries being evicted to make room for new ones
|
||||
- Memory constraints in your deployment environment
|
||||
- Cache not persisting across requests or server restarts
|
||||
|
||||
Note that `use cache` still provides value beyond server-side caching: it informs Next.js what can be prefetched and defines stale times for client-side navigation.
|
||||
|
||||
The `'use cache: remote'` directive lets you declaratively specify that a cached output should be stored in a remote cache instead of in-memory, providing durable caching shared across all server instances. This comes with tradeoffs: infrastructure cost and network latency during cache lookups.
|
||||
|
||||
## Usage
|
||||
|
||||
To use `'use cache: remote'`, enable the [`cacheComponents`](/docs/app/api-reference/config/next-config-js/cacheComponents) flag in your `next.config.ts` file:
|
||||
|
||||
```ts filename="next.config.ts" switcher
|
||||
import type { NextConfig } from 'next'
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
cacheComponents: true,
|
||||
}
|
||||
|
||||
export default nextConfig
|
||||
```
|
||||
|
||||
```js filename="next.config.js" switcher
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
cacheComponents: true,
|
||||
}
|
||||
|
||||
export default nextConfig
|
||||
```
|
||||
|
||||
Then add `'use cache: remote'` to the functions or components where you've determined remote caching is justified. The handler implementation is configured via [`cacheHandlers`](/docs/app/api-reference/config/next-config-js/cacheHandlers), though hosting providers should typically provide this automatically. If you're self-hosting, see the `cacheHandlers` configuration reference to set up your cache storage.
|
||||
|
||||
### When to avoid remote caching
|
||||
|
||||
- If you already have a server-side cache key-value store wrapping your data layer, `use cache` may be sufficient to include data in the static shell without adding another caching layer
|
||||
- If operations are already fast (< 50ms) due to proximity or local access, the remote cache lookup might not improve performance
|
||||
- If cache keys have mostly unique values per request (search filters, price ranges, user-specific parameters), cache utilization will be near-zero
|
||||
- If data changes frequently (seconds to minutes), cache hits will quickly go stale, leading to frequent misses and waiting for upstream revalidation
|
||||
|
||||
### When remote caching makes sense
|
||||
|
||||
Remote caching provides the most value when content is deferred to request time (outside the static shell). This typically happens when a component accesses request values like [`cookies()`](/docs/app/api-reference/functions/cookies), [`headers()`](/docs/app/api-reference/functions/headers), or [`searchParams`](/docs/app/api-reference/file-conventions/page#searchparams-optional), placing it inside a Suspense boundary. In this context:
|
||||
|
||||
- Each request executes the component and looks up the cache
|
||||
- In serverless environments, each instance has its own ephemeral memory with low cache hit rates
|
||||
- Remote caching provides a shared cache across all instances, improving hit rates and reducing backend load
|
||||
|
||||
Compelling scenarios for `'use cache: remote'`:
|
||||
|
||||
- **Rate-limited APIs**: Your upstream service has rate limits or request quotas that you risk hitting
|
||||
- **Protecting slow backends**: Your database or API becomes a bottleneck under high traffic
|
||||
- **Expensive operations**: Database queries or computations that are costly to run repeatedly
|
||||
- **Flaky or unreliable services**: External services that occasionally fail or have availability issues
|
||||
|
||||
In these cases, the cost and latency of remote caching is justified by avoiding worse outcomes (rate limit errors, backend overload, high compute bills, or degraded user experience).
|
||||
|
||||
For static shell content, `use cache` is usually sufficient. If your upstream source can't handle concurrent revalidation requests (like a rate-limited CMS), `use cache: remote` acts as a shared cache layer. This is the same pattern as putting a key-value store in front of a database, but declared in code.
|
||||
|
||||
### How `use cache: remote` differs from `use cache` and `use cache: private`
|
||||
|
||||
Next.js provides three caching directives, each designed for different use cases:
|
||||
|
||||
| Feature | `use cache` | `'use cache: remote'` | `'use cache: private'` |
|
||||
| --------------------------------------- | ------------------------------- | --------------------------------- | ---------------------- |
|
||||
| **Server-side caching** | In-memory or cache handler | Remote cache handler | None |
|
||||
| **Cache scope** | Shared across all users | Shared across all users | Per-client (browser) |
|
||||
| **Can access cookies/headers directly** | No (must pass as arguments) | No (must pass as arguments) | Yes |
|
||||
| **Server cache utilization** | May be low outside static shell | High (shared across instances) | N/A |
|
||||
| **Additional costs** | None | Infrastructure (storage, network) | None |
|
||||
| **Latency impact** | None | Cache handler lookup | None |
|
||||
|
||||
### Caching with runtime data
|
||||
|
||||
Both `use cache` and `'use cache: remote'` can't access runtime values like cookies or search params directly. You can extract these values and pass them as arguments to cached functions. See [with runtime data](/docs/app/getting-started/caching#working-with-runtime-apis) for this pattern.
|
||||
|
||||
> **Good to know**: `use cache` stores entries in-memory. In serverless environments, memory is not shared between instances and is typically destroyed after serving a request, leading to frequent cache misses for runtime caching.
|
||||
|
||||
### Cache key considerations
|
||||
|
||||
Be thoughtful about which values you include in cache keys. Each unique value creates a separate cache entry, reducing cache utilization. Consider this example with search filters:
|
||||
|
||||
```tsx filename="app/products/[category]/page.tsx"
|
||||
import { Suspense } from 'react'
|
||||
|
||||
export default async function ProductsPage({
|
||||
params,
|
||||
searchParams,
|
||||
}: {
|
||||
params: Promise<{ category: string }>
|
||||
searchParams: Promise<{ minPrice?: string }>
|
||||
}) {
|
||||
return (
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
<ProductList params={params} searchParams={searchParams} />
|
||||
</Suspense>
|
||||
)
|
||||
}
|
||||
|
||||
async function ProductList({
|
||||
params,
|
||||
searchParams,
|
||||
}: {
|
||||
params: Promise<{ category: string }>
|
||||
searchParams: Promise<{ minPrice?: string }>
|
||||
}) {
|
||||
const { category } = await params
|
||||
|
||||
const { minPrice } = await searchParams
|
||||
|
||||
// Cache only on category (few unique values)
|
||||
// Don't include price filter (many unique values)
|
||||
const products = await getProductsByCategory(category)
|
||||
|
||||
// Filter price in memory instead of creating cache entries
|
||||
// for every price value
|
||||
const filtered = minPrice
|
||||
? products.filter((p) => p.price >= parseFloat(minPrice))
|
||||
: products
|
||||
|
||||
return <div>{/* render filtered products */}</div>
|
||||
}
|
||||
|
||||
async function getProductsByCategory(category: string) {
|
||||
'use cache: remote'
|
||||
// Only category is part of the cache key
|
||||
// Much better utilization than caching every price filter value
|
||||
return db.products.findByCategory(category)
|
||||
}
|
||||
```
|
||||
|
||||
In this example, the remote handler stores more data per cache entry (all products in a category) to achieve better cache hit rates. This is worth it when the cost of cache misses (hitting your backend) outweighs the storage cost of larger entries.
|
||||
|
||||
The same principle applies to user-specific data. Rather than caching per-user data directly, use user preferences to determine what shared data to cache.
|
||||
|
||||
For example, if users have a language preference in their session, extract that preference and use it to cache shared content:
|
||||
|
||||
- Instead of remote caching `getUserProfile(sessionID)`, which creates one entry per user
|
||||
- Remote cache `getCMSContent(language)` to create one entry per language
|
||||
|
||||
```tsx filename="app/components/welcome-message.tsx"
|
||||
import { cookies } from 'next/headers'
|
||||
import { cacheLife } from 'next/cache'
|
||||
|
||||
export async function WelcomeMessage() {
|
||||
// Extract the language preference (not unique per user)
|
||||
const language = (await cookies()).get('language')?.value || 'en'
|
||||
|
||||
// Cache based on language (few unique values: en, es, fr, de, etc.)
|
||||
// All users who prefer 'en' share the same cache entry
|
||||
const content = await getCMSContent(language)
|
||||
|
||||
return <div>{content.welcomeMessage}</div>
|
||||
}
|
||||
|
||||
async function getCMSContent(language: string) {
|
||||
'use cache: remote'
|
||||
cacheLife({ expire: 3600 })
|
||||
// Creates ~10-50 cache entries (one per language)
|
||||
// instead of thousands (one per user)
|
||||
return cms.getHomeContent(language)
|
||||
}
|
||||
```
|
||||
|
||||
This way all users who prefer the same language share a cache entry, improving cache utilization and reducing load on your CMS.
|
||||
|
||||
The pattern is the same in both examples: find the dimension with fewer unique values (category vs. price, language vs. user ID), cache on that dimension, and filter or select the rest in memory.
|
||||
|
||||
If the service used by `getUserProfile` cannot scale with your frontend load, you may still be able to use the `use cache` directive with a short `cacheLife` for in-memory caching. However, for most user data, you likely want to fetch directly from the source (which might already be wrapped in a key/value store as mentioned in the guidelines above).
|
||||
|
||||
Only use [`'use cache: private'`](/docs/app/api-reference/directives/use-cache-private) if you have compliance requirements or can't refactor to pass runtime data as arguments.
|
||||
|
||||
### Nesting rules
|
||||
|
||||
Remote caches have specific nesting rules:
|
||||
|
||||
- Remote caches **can** be nested inside other remote caches (`'use cache: remote'`)
|
||||
- Remote caches **can** be nested inside regular caches (`'use cache'`)
|
||||
- Remote caches **cannot** be nested inside private caches (`'use cache: private'`)
|
||||
- Private caches **cannot** be nested inside remote caches
|
||||
|
||||
```tsx
|
||||
// VALID: Remote inside remote
|
||||
async function outerRemote() {
|
||||
'use cache: remote'
|
||||
const result = await innerRemote()
|
||||
return result
|
||||
}
|
||||
|
||||
async function innerRemote() {
|
||||
'use cache: remote'
|
||||
return getData()
|
||||
}
|
||||
|
||||
// VALID: Remote inside regular cache
|
||||
async function outerCache() {
|
||||
'use cache'
|
||||
// The inner remote cache will work when deferred to request time
|
||||
const result = await innerRemote()
|
||||
return result
|
||||
}
|
||||
|
||||
async function innerRemote() {
|
||||
'use cache: remote'
|
||||
return getData()
|
||||
}
|
||||
|
||||
// INVALID: Remote inside private
|
||||
async function outerPrivate() {
|
||||
'use cache: private'
|
||||
const result = await innerRemote() // Error!
|
||||
return result
|
||||
}
|
||||
|
||||
async function innerRemote() {
|
||||
'use cache: remote'
|
||||
return getData()
|
||||
}
|
||||
|
||||
// INVALID: Private inside remote
|
||||
async function outerRemote() {
|
||||
'use cache: remote'
|
||||
const result = await innerPrivate() // Error!
|
||||
return result
|
||||
}
|
||||
|
||||
async function innerPrivate() {
|
||||
'use cache: private'
|
||||
return getData()
|
||||
}
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
The following examples demonstrate common patterns for using `'use cache: remote'`. For details about `cacheLife` parameters (`stale`, `revalidate`, `expire`), see the [`cacheLife` API reference](/docs/app/api-reference/functions/cacheLife).
|
||||
|
||||
### With user preferences
|
||||
|
||||
Cache product pricing based on the user's currency preference. Since the currency is stored in a cookie, this component renders at request time. Remote caching is valuable here because all users with the same currency share the cached price, and in serverless environments, all instances share the same remote cache.
|
||||
|
||||
```tsx filename="app/product/[id]/page.tsx" switcher
|
||||
import { Suspense } from 'react'
|
||||
import { cookies } from 'next/headers'
|
||||
import { cacheTag, cacheLife } from 'next/cache'
|
||||
|
||||
export async function generateStaticParams() {
|
||||
return [{ id: '1' }, { id: '2' }, { id: '3' }]
|
||||
}
|
||||
|
||||
export default async function ProductPage({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ id: string }>
|
||||
}) {
|
||||
const { id } = await params
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ProductDetails id={id} />
|
||||
<Suspense fallback={<div>Loading price...</div>}>
|
||||
<ProductPrice productId={id} />
|
||||
</Suspense>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function ProductDetails({ id }: { id: string }) {
|
||||
return <div>Product: {id}</div>
|
||||
}
|
||||
|
||||
async function ProductPrice({ productId }: { productId: string }) {
|
||||
// Reading cookies defers this component to request time
|
||||
const currency = (await cookies()).get('currency')?.value ?? 'USD'
|
||||
|
||||
// Cache the price per product and currency combination
|
||||
// All users with the same currency share this cache entry
|
||||
const price = await getProductPrice(productId, currency)
|
||||
|
||||
return (
|
||||
<div>
|
||||
Price: {price} {currency}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
async function getProductPrice(productId: string, currency: string) {
|
||||
'use cache: remote'
|
||||
cacheTag(`product-price-${productId}`)
|
||||
cacheLife({ expire: 3600 }) // 1 hour
|
||||
|
||||
// Cached per (productId, currency) - few currencies means high cache utilization
|
||||
return db.products.getPrice(productId, currency)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/product/[id]/page.js" switcher
|
||||
import { Suspense } from 'react'
|
||||
import { cookies } from 'next/headers'
|
||||
import { cacheTag, cacheLife } from 'next/cache'
|
||||
|
||||
export async function generateStaticParams() {
|
||||
return [{ id: '1' }, { id: '2' }, { id: '3' }]
|
||||
}
|
||||
|
||||
export default async function ProductPage({ params }) {
|
||||
const { id } = await params
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ProductDetails id={id} />
|
||||
<Suspense fallback={<div>Loading price...</div>}>
|
||||
<ProductPrice productId={id} />
|
||||
</Suspense>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function ProductDetails({ id }) {
|
||||
return <div>Product: {id}</div>
|
||||
}
|
||||
|
||||
async function ProductPrice({ productId }) {
|
||||
// Reading cookies defers this component to request time
|
||||
const currency = (await cookies()).get('currency')?.value ?? 'USD'
|
||||
|
||||
// Cache the price per product and currency combination
|
||||
// All users with the same currency share this cache entry
|
||||
const price = await getProductPrice(productId, currency)
|
||||
|
||||
return (
|
||||
<div>
|
||||
Price: {price} {currency}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
async function getProductPrice(productId, currency) {
|
||||
'use cache: remote'
|
||||
cacheTag(`product-price-${productId}`)
|
||||
cacheLife({ expire: 3600 }) // 1 hour
|
||||
|
||||
// Cached per (productId, currency) - few currencies means high cache utilization
|
||||
return db.products.getPrice(productId, currency)
|
||||
}
|
||||
```
|
||||
|
||||
### Reducing database load
|
||||
|
||||
Cache expensive database queries, reducing load on your database. In this example, we don't access `cookies()`, `headers()`, or `searchParams`. If we had a requirement to not include these stats in the static shell, we could use [`connection()`](/docs/app/api-reference/functions/connection) to explicitly defer to request time:
|
||||
|
||||
```tsx filename="app/dashboard/page.tsx"
|
||||
import { Suspense } from 'react'
|
||||
import { connection } from 'next/server'
|
||||
import { cacheLife, cacheTag } from 'next/cache'
|
||||
|
||||
export default function DashboardPage() {
|
||||
return (
|
||||
<Suspense fallback={<div>Loading stats...</div>}>
|
||||
<DashboardStats />
|
||||
</Suspense>
|
||||
)
|
||||
}
|
||||
|
||||
async function DashboardStats() {
|
||||
// Defer to request time
|
||||
await connection()
|
||||
|
||||
const stats = await getGlobalStats()
|
||||
|
||||
return <StatsDisplay stats={stats} />
|
||||
}
|
||||
|
||||
async function getGlobalStats() {
|
||||
'use cache: remote'
|
||||
cacheTag('global-stats')
|
||||
cacheLife({ expire: 60 }) // 1 minute
|
||||
|
||||
// This expensive database query is cached and shared across all users,
|
||||
// reducing load on your database
|
||||
const stats = await db.analytics.aggregate({
|
||||
total_users: 'count',
|
||||
active_sessions: 'count',
|
||||
revenue: 'sum',
|
||||
})
|
||||
|
||||
return stats
|
||||
}
|
||||
```
|
||||
|
||||
With this setup, your upstream database sees at most one request per minute, regardless of how many users visit the dashboard.
|
||||
|
||||
### API responses in streaming contexts
|
||||
|
||||
Cache API responses that are fetched during streaming or after dynamic operations:
|
||||
|
||||
```tsx filename="app/feed/page.tsx"
|
||||
import { Suspense } from 'react'
|
||||
import { connection } from 'next/server'
|
||||
import { cacheLife, cacheTag } from 'next/cache'
|
||||
|
||||
export default async function FeedPage() {
|
||||
return (
|
||||
<div>
|
||||
<Suspense fallback={<Skeleton />}>
|
||||
<FeedItems />
|
||||
</Suspense>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
async function FeedItems() {
|
||||
// Defer to request time
|
||||
await connection()
|
||||
|
||||
const items = await getFeedItems()
|
||||
|
||||
return items.map((item) => <FeedItem key={item.id} item={item} />)
|
||||
}
|
||||
|
||||
async function getFeedItems() {
|
||||
'use cache: remote'
|
||||
cacheTag('feed-items')
|
||||
cacheLife({ expire: 120 }) // 2 minutes
|
||||
|
||||
// This API call is cached, reducing requests to your external service
|
||||
const response = await fetch('https://api.example.com/feed')
|
||||
return response.json()
|
||||
}
|
||||
```
|
||||
|
||||
### Computed data after dynamic checks
|
||||
|
||||
Cache expensive computations that occur after dynamic security or feature checks:
|
||||
|
||||
```tsx filename="app/reports/page.tsx"
|
||||
import { connection } from 'next/server'
|
||||
import { cacheLife } from 'next/cache'
|
||||
|
||||
export default async function ReportsPage() {
|
||||
// Defer to request time (for security check)
|
||||
await connection()
|
||||
|
||||
const report = await generateReport()
|
||||
|
||||
return <ReportViewer report={report} />
|
||||
}
|
||||
|
||||
async function generateReport() {
|
||||
'use cache: remote'
|
||||
cacheLife({ expire: 3600 }) // 1 hour
|
||||
|
||||
// This expensive computation is cached and shared across all authorized users,
|
||||
// avoiding repeated calculations
|
||||
const data = await db.transactions.findMany()
|
||||
|
||||
return {
|
||||
totalRevenue: calculateRevenue(data),
|
||||
topProducts: analyzeProducts(data),
|
||||
trends: calculateTrends(data),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Mixed caching strategies
|
||||
|
||||
Combine static, remote, and private caching for optimal performance:
|
||||
|
||||
```tsx filename="app/product/[id]/page.tsx"
|
||||
import { Suspense } from 'react'
|
||||
import { connection } from 'next/server'
|
||||
import { cookies } from 'next/headers'
|
||||
import { cacheLife, cacheTag } from 'next/cache'
|
||||
|
||||
// Static product data - prerendered at build time
|
||||
async function getProduct(id: string) {
|
||||
'use cache'
|
||||
cacheTag(`product-${id}`)
|
||||
|
||||
// This is cached at build time and shared across all users
|
||||
return db.products.find({ where: { id } })
|
||||
}
|
||||
|
||||
// Shared pricing data - cached at runtime in remote handler
|
||||
async function getProductPrice(id: string) {
|
||||
'use cache: remote'
|
||||
cacheTag(`product-price-${id}`)
|
||||
cacheLife({ expire: 300 }) // 5 minutes
|
||||
|
||||
// This is cached at runtime and shared across all users
|
||||
return db.products.getPrice({ where: { id } })
|
||||
}
|
||||
|
||||
// User-specific recommendations - private cache per user
|
||||
async function getRecommendations(productId: string) {
|
||||
'use cache: private'
|
||||
cacheLife({ expire: 60 }) // 1 minute
|
||||
|
||||
const sessionId = (await cookies()).get('session-id')?.value
|
||||
|
||||
// This is cached per-user and never shared
|
||||
return db.recommendations.findMany({
|
||||
where: { productId, sessionId },
|
||||
})
|
||||
}
|
||||
|
||||
export default async function ProductPage({ params }) {
|
||||
const { id } = await params
|
||||
|
||||
// Static product data
|
||||
const product = await getProduct(id)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ProductDetails product={product} />
|
||||
|
||||
{/* Dynamic shared price */}
|
||||
<Suspense fallback={<PriceSkeleton />}>
|
||||
<ProductPriceComponent productId={id} />
|
||||
</Suspense>
|
||||
|
||||
{/* Dynamic personalized recommendations */}
|
||||
<Suspense fallback={<RecommendationsSkeleton />}>
|
||||
<ProductRecommendations productId={id} />
|
||||
</Suspense>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function ProductDetails({ product }) {
|
||||
return (
|
||||
<div>
|
||||
<h1>{product.name}</h1>
|
||||
<p>{product.description}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
async function ProductPriceComponent({ productId }) {
|
||||
// Defer to request time
|
||||
await connection()
|
||||
|
||||
const price = await getProductPrice(productId)
|
||||
return <div>Price: ${price}</div>
|
||||
}
|
||||
|
||||
async function ProductRecommendations({ productId }) {
|
||||
const recommendations = await getRecommendations(productId)
|
||||
return <RecommendationsList items={recommendations} />
|
||||
}
|
||||
|
||||
function PriceSkeleton() {
|
||||
return <div>Loading price...</div>
|
||||
}
|
||||
|
||||
function RecommendationsSkeleton() {
|
||||
return <div>Loading recommendations...</div>
|
||||
}
|
||||
|
||||
function RecommendationsList({ items }) {
|
||||
return (
|
||||
<ul>
|
||||
{items.map((item) => (
|
||||
<li key={item.id}>{item.name}</li>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
> **Good to know**:
|
||||
>
|
||||
> - Remote caches are stored in server-side cache handlers and shared across all users
|
||||
> - `'use cache: remote'` works outside the static shell where [`use cache`](/docs/app/api-reference/directives/use-cache) may not provide server-side cache hits
|
||||
> - Use [`cacheTag()`](/docs/app/api-reference/functions/cacheTag) and [`revalidateTag()`](/docs/app/api-reference/functions/revalidateTag) to invalidate remote caches on-demand
|
||||
> - Use [`cacheLife()`](/docs/app/api-reference/functions/cacheLife) to configure cache expiration
|
||||
> - For user-specific data, use [`'use cache: private'`](/docs/app/api-reference/directives/use-cache-private) instead of `'use cache: remote'`
|
||||
> - Remote caches reduce origin load by storing computed or fetched data server-side
|
||||
|
||||
## Platform Support
|
||||
|
||||
| Deployment Option | Supported |
|
||||
| ------------------------------------------------------------------- | --------- |
|
||||
| [Node.js server](/docs/app/getting-started/deploying#nodejs-server) | Yes |
|
||||
| [Docker container](/docs/app/getting-started/deploying#docker) | Yes |
|
||||
| [Static export](/docs/app/getting-started/deploying#static-export) | No |
|
||||
| [Adapters](/docs/app/getting-started/deploying#adapters) | Yes |
|
||||
|
||||
## Version History
|
||||
|
||||
| Version | Changes |
|
||||
| --------- | ------------------------------------------------------------------- |
|
||||
| `v16.0.0` | `"use cache: remote"` is enabled with the Cache Components feature. |
|
||||
Generated
Vendored
+663
@@ -0,0 +1,663 @@
|
||||
---
|
||||
title: use cache
|
||||
description: Learn how to use the "use cache" directive to cache data in your Next.js application.
|
||||
related:
|
||||
title: Related
|
||||
description: View related API references.
|
||||
links:
|
||||
- app/api-reference/directives/use-cache-private
|
||||
- app/api-reference/config/next-config-js/cacheComponents
|
||||
- app/api-reference/config/next-config-js/cacheLife
|
||||
- app/api-reference/config/next-config-js/cacheHandlers
|
||||
- app/api-reference/functions/cacheTag
|
||||
- app/api-reference/functions/cacheLife
|
||||
- app/api-reference/functions/revalidateTag
|
||||
---
|
||||
|
||||
The `use cache` directive allows you to mark a route, React component, or a function as cacheable. It can be used at the top of a file to indicate that all exports in the file should be cached, or inline at the top of function or component to cache the return value.
|
||||
|
||||
> **Good to know:**
|
||||
>
|
||||
> - To use cookies or headers, read them outside cached scopes and pass values as arguments. This is the preferred pattern.
|
||||
> - If the in-memory cache isn't sufficient for runtime data, [`'use cache: remote'`](/docs/app/api-reference/directives/use-cache-remote) allows platforms to provide a dedicated cache handler, though it requires a network roundtrip to check the cache and typically incurs platform fees.
|
||||
> - For compliance requirements or when you can't refactor to pass runtime data as arguments to a `use cache` scope, see [`'use cache: private'`](/docs/app/api-reference/directives/use-cache-private).
|
||||
|
||||
## Usage
|
||||
|
||||
`use cache` is a Cache Components feature. To enable it, add the [`cacheComponents`](/docs/app/api-reference/config/next-config-js/cacheComponents) option to your `next.config.ts` file:
|
||||
|
||||
```ts filename="next.config.ts" switcher
|
||||
import type { NextConfig } from 'next'
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
cacheComponents: true,
|
||||
}
|
||||
|
||||
export default nextConfig
|
||||
```
|
||||
|
||||
```js filename="next.config.js" switcher
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
cacheComponents: true,
|
||||
}
|
||||
|
||||
module.exports = nextConfig
|
||||
```
|
||||
|
||||
Then, add `use cache` at the file, component, or function level:
|
||||
|
||||
```tsx
|
||||
// File level
|
||||
'use cache'
|
||||
|
||||
export default async function Page() {
|
||||
// ...
|
||||
}
|
||||
|
||||
// Component level
|
||||
export async function MyComponent() {
|
||||
'use cache'
|
||||
return <></>
|
||||
}
|
||||
|
||||
// Function level
|
||||
export async function getData() {
|
||||
'use cache'
|
||||
const data = await fetch('/api/data')
|
||||
return data
|
||||
}
|
||||
```
|
||||
|
||||
> **Good to know**: When used at file level, all function exports must be async functions.
|
||||
|
||||
## How `use cache` works
|
||||
|
||||
### Cache keys
|
||||
|
||||
A cache entry's key is generated using a serialized version of its inputs, which includes:
|
||||
|
||||
1. **Build ID** - Unique per build, changing this invalidates all cache entries
|
||||
2. **Function ID** - A secure hash of the function's location and signature in the codebase
|
||||
3. **Serializable arguments** - Props (for components) or function arguments
|
||||
4. **HMR refresh hash** (development only) - Invalidates cache on hot module replacement
|
||||
|
||||
When a cached function references variables from outer scopes, those variables are automatically captured and bound as arguments, making them part of the cache key.
|
||||
|
||||
```tsx filename="lib/data.ts"
|
||||
async function Component({ userId }: { userId: string }) {
|
||||
const getData = async (filter: string) => {
|
||||
'use cache'
|
||||
// Cache key includes both userId (from closure) and filter (argument)
|
||||
return fetch(`/api/users/${userId}/data?filter=${filter}`)
|
||||
}
|
||||
|
||||
return getData('active')
|
||||
}
|
||||
```
|
||||
|
||||
In the snippet above, `userId` is captured from the outer scope and `filter` is passed as an argument, so both become part of the `getData` function's cache key. This means different user and filter combinations will have separate cache entries.
|
||||
|
||||
## Serialization
|
||||
|
||||
Arguments to cached functions and their return values must be serializable.
|
||||
|
||||
For a complete reference, see:
|
||||
|
||||
- [Serializable arguments](https://react.dev/reference/rsc/use-server#serializable-parameters-and-return-values) - Uses **React Server Components** serialization
|
||||
- [Serializable return types](https://react.dev/reference/rsc/use-client#serializable-types) - Uses **React Client Components** serialization
|
||||
|
||||
> **Good to know:** Arguments and return values use different serialization systems. Server Component serialization (for arguments) is more restrictive than Client Component serialization (for return values). This means you can return JSX elements but cannot accept them as arguments unless using pass-through patterns.
|
||||
|
||||
### Supported types
|
||||
|
||||
**Arguments:**
|
||||
|
||||
- Primitives: `string`, `number`, `boolean`, `null`, `undefined`
|
||||
- Plain objects: `{ key: value }`
|
||||
- Arrays: `[1, 2, 3]`
|
||||
- Dates, Maps, Sets, TypedArrays, ArrayBuffers
|
||||
- React elements (as pass-through only)
|
||||
|
||||
**Return values:**
|
||||
|
||||
- Same as arguments, plus JSX elements
|
||||
|
||||
### Unsupported types
|
||||
|
||||
- Class instances
|
||||
- Functions (except as pass-through)
|
||||
- Symbols, WeakMaps, WeakSets
|
||||
- URL instances
|
||||
|
||||
```tsx filename="app/components/user-card.tsx"
|
||||
// Valid - primitives and plain objects
|
||||
async function UserCard({
|
||||
id,
|
||||
config,
|
||||
}: {
|
||||
id: string
|
||||
config: { theme: string }
|
||||
}) {
|
||||
'use cache'
|
||||
return <div>{id}</div>
|
||||
}
|
||||
|
||||
// Invalid - class instance
|
||||
async function UserProfile({ user }: { user: UserClass }) {
|
||||
'use cache'
|
||||
// Error: Cannot serialize class instance
|
||||
return <div>{user.name}</div>
|
||||
}
|
||||
```
|
||||
|
||||
### Pass-through (non-serializable arguments)
|
||||
|
||||
You can accept non-serializable values **as long as you don't introspect them**. This enables composition patterns with `children` and Server Actions:
|
||||
|
||||
```tsx filename="app/components/cached-wrapper.tsx"
|
||||
async function CachedWrapper({ children }: { children: ReactNode }) {
|
||||
'use cache'
|
||||
// Don't read or modify children - just pass it through
|
||||
return (
|
||||
<div className="wrapper">
|
||||
<header>Cached Header</header>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// Usage: children can be dynamic
|
||||
export default function Page() {
|
||||
return (
|
||||
<CachedWrapper>
|
||||
<DynamicComponent /> {/* Not cached, passed through */}
|
||||
</CachedWrapper>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
You can also pass Server Actions through cached components:
|
||||
|
||||
```tsx filename="app/components/cached-form.tsx"
|
||||
async function CachedForm({ action }: { action: () => Promise<void> }) {
|
||||
'use cache'
|
||||
// Don't call action here - just pass it through
|
||||
return <form action={action}>{/* ... */}</form>
|
||||
}
|
||||
```
|
||||
|
||||
## Constraints
|
||||
|
||||
Cached functions execute in an isolated environment. The following constraints ensure cache behavior remains predictable and secure.
|
||||
|
||||
### Request-time APIs
|
||||
|
||||
Cached functions and components **cannot** directly access runtime APIs like `cookies()`, `headers()`, or `searchParams`. Instead, read these values outside the cached scope and pass them as arguments.
|
||||
|
||||
### Runtime caching considerations
|
||||
|
||||
While `use cache` is designed primarily to include uncached data in the static shell, it can also cache data at runtime using in-memory LRU (Least Recently Used) storage.
|
||||
|
||||
Runtime cache behavior depends on your hosting environment:
|
||||
|
||||
| Environment | Runtime Caching Behavior |
|
||||
| --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **Serverless** | Cache entries typically don't persist across requests (each request can be a different instance). Build-time caching works normally. |
|
||||
| **Self-hosted** | Cache entries persist across requests. Control cache size with [`cacheMaxMemorySize`](/docs/app/api-reference/config/next-config-js/incrementalCacheHandlerPath). |
|
||||
|
||||
If the default in-memory cache isn't enough, consider **[`use cache: remote`](/docs/app/api-reference/directives/use-cache-remote)** which allows platforms to provide a dedicated cache handler (like Redis or KV database). This helps reduce hits against data sources not scaled to your total traffic, though it comes with costs (storage, network latency, platform fees).
|
||||
|
||||
Very rarely, for compliance requirements or when you can't refactor your code to pass runtime data as arguments to a `use cache` scope, you might need [`use cache: private`](/docs/app/api-reference/directives/use-cache-private).
|
||||
|
||||
### React.cache isolation
|
||||
|
||||
[`React.cache`](https://react.dev/reference/react/cache) operates in an isolated scope inside `use cache` boundaries. Values stored via `React.cache` outside a `use cache` function are not visible inside it.
|
||||
|
||||
This means you cannot use `React.cache` to pass data into a `use cache` scope:
|
||||
|
||||
```tsx
|
||||
import { cache } from 'react'
|
||||
|
||||
const store = cache(() => ({ current: null as string | null }))
|
||||
|
||||
function Parent() {
|
||||
const shared = store()
|
||||
shared.current = 'value from parent'
|
||||
return <Child />
|
||||
}
|
||||
|
||||
async function Child() {
|
||||
'use cache'
|
||||
const shared = store()
|
||||
// shared.current is null, not 'value from parent'
|
||||
// use cache has its own isolated React.cache scope
|
||||
return <div>{shared.current}</div>
|
||||
}
|
||||
```
|
||||
|
||||
This isolation ensures cached functions have predictable, self-contained behavior. To pass data into a `use cache` scope, use function arguments instead.
|
||||
|
||||
## `use cache` at runtime
|
||||
|
||||
On the **server**, cache entries are stored in-memory and respect the `revalidate` and `expire` times from your `cacheLife` configuration. You can customize the cache storage by configuring [`cacheHandlers`](/docs/app/api-reference/config/next-config-js/cacheHandlers) in your `next.config.js` file.
|
||||
|
||||
On the **client**, content from the server cache is stored in the browser's memory for the duration defined by the `stale` time. The client router enforces a **minimum 30-second stale time**, regardless of configuration.
|
||||
|
||||
The `x-nextjs-stale-time` response header communicates cache lifetime from server to client, ensuring coordinated behavior.
|
||||
|
||||
## Revalidation
|
||||
|
||||
By default, `use cache` uses the `default` profile with these settings:
|
||||
|
||||
- **stale**: 5 minutes (client-side)
|
||||
- **revalidate**: 15 minutes (server-side)
|
||||
- **expire**: Never expires by time
|
||||
|
||||
```tsx filename="lib/data.ts"
|
||||
async function getData() {
|
||||
'use cache'
|
||||
// Implicitly uses default profile
|
||||
return fetch('/api/data')
|
||||
}
|
||||
```
|
||||
|
||||
### Customizing cache lifetime
|
||||
|
||||
Use the [`cacheLife`](/docs/app/api-reference/functions/cacheLife) function to customize cache duration:
|
||||
|
||||
```tsx filename="lib/data.ts"
|
||||
import { cacheLife } from 'next/cache'
|
||||
|
||||
async function getData() {
|
||||
'use cache'
|
||||
cacheLife('hours') // Use built-in 'hours' profile
|
||||
return fetch('/api/data')
|
||||
}
|
||||
```
|
||||
|
||||
### On-demand revalidation
|
||||
|
||||
Use [`cacheTag`](/docs/app/api-reference/functions/cacheTag), [`updateTag`](/docs/app/api-reference/functions/updateTag), or [`revalidateTag`](/docs/app/api-reference/functions/revalidateTag) for on-demand cache invalidation:
|
||||
|
||||
```tsx filename="lib/data.ts"
|
||||
import { cacheTag } from 'next/cache'
|
||||
|
||||
async function getProducts() {
|
||||
'use cache'
|
||||
cacheTag('products')
|
||||
return fetch('/api/products')
|
||||
}
|
||||
```
|
||||
|
||||
```tsx filename="app/actions.ts"
|
||||
'use server'
|
||||
|
||||
import { updateTag } from 'next/cache'
|
||||
|
||||
export async function updateProduct() {
|
||||
await db.products.update(...)
|
||||
updateTag('products') // Invalidates all 'products' caches
|
||||
}
|
||||
```
|
||||
|
||||
Both `cacheLife` and `cacheTag` integrate across client and server caching layers, meaning you configure your caching semantics in one place and they apply everywhere.
|
||||
|
||||
## Examples
|
||||
|
||||
### Caching an entire route with `use cache`
|
||||
|
||||
To prerender an entire route, add `use cache` to the top of **both** the `layout` and `page` files. Each of these segments are treated as separate entry points in your application, and will be cached independently.
|
||||
|
||||
```tsx filename="app/layout.tsx" switcher
|
||||
'use cache'
|
||||
|
||||
export default async function Layout({ children }: { children: ReactNode }) {
|
||||
return <div>{children}</div>
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/layout.js" switcher
|
||||
'use cache'
|
||||
|
||||
export default async function Layout({ children }) {
|
||||
return <div>{children}</div>
|
||||
}
|
||||
```
|
||||
|
||||
Any components imported and nested in `page` file are part of the cache output associated with the `page`.
|
||||
|
||||
```tsx filename="app/page.tsx" switcher
|
||||
'use cache'
|
||||
|
||||
async function Users() {
|
||||
const users = await fetch('/api/users')
|
||||
// loop through users
|
||||
}
|
||||
|
||||
export default async function Page() {
|
||||
return (
|
||||
<main>
|
||||
<Users />
|
||||
</main>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/page.js" switcher
|
||||
'use cache'
|
||||
|
||||
async function Users() {
|
||||
const users = await fetch('/api/users')
|
||||
// loop through users
|
||||
}
|
||||
|
||||
export default async function Page() {
|
||||
return (
|
||||
<main>
|
||||
<Users />
|
||||
</main>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
> **Good to know**:
|
||||
>
|
||||
> - If `use cache` is added only to the `layout` or the `page`, only that route segment and any components imported into it will be cached.
|
||||
|
||||
### Caching a component's output with `use cache`
|
||||
|
||||
You can use `use cache` at the component level to cache any fetches or computations performed within that component. The cache entry will be reused as long as the serialized props produce the same value in each instance.
|
||||
|
||||
```tsx filename="app/components/bookings.tsx" highlight={2} switcher
|
||||
export async function Bookings({ type = 'haircut' }: BookingsProps) {
|
||||
'use cache'
|
||||
async function getBookingsData() {
|
||||
const data = await fetch(`/api/bookings?type=${encodeURIComponent(type)}`)
|
||||
return data
|
||||
}
|
||||
return //...
|
||||
}
|
||||
|
||||
interface BookingsProps {
|
||||
type: string
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/components/bookings.js" highlight={2} switcher
|
||||
export async function Bookings({ type = 'haircut' }) {
|
||||
'use cache'
|
||||
async function getBookingsData() {
|
||||
const data = await fetch(`/api/bookings?type=${encodeURIComponent(type)}`)
|
||||
return data
|
||||
}
|
||||
return //...
|
||||
}
|
||||
```
|
||||
|
||||
### Caching function output with `use cache`
|
||||
|
||||
Since you can add `use cache` to any asynchronous function, you aren't limited to caching components or routes only. You might want to cache a network request, a database query, or a slow computation.
|
||||
|
||||
```tsx filename="app/actions.ts" highlight={2} switcher
|
||||
export async function getData() {
|
||||
'use cache'
|
||||
|
||||
const data = await fetch('/api/data')
|
||||
return data
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/actions.js" highlight={2} switcher
|
||||
export async function getData() {
|
||||
'use cache'
|
||||
|
||||
const data = await fetch('/api/data')
|
||||
return data
|
||||
}
|
||||
```
|
||||
|
||||
### Interleaving
|
||||
|
||||
In React, composition with `children` or slots is a well-known pattern for building flexible components. When using `use cache`, you can continue to compose your UI in this way. Anything included as `children`, or other compositional slots, in the returned JSX will be passed through the cached component without affecting its cache entry.
|
||||
|
||||
As long as you don't directly reference any of the JSX slots inside the body of the cacheable function itself, their presence in the returned output won't affect the cache entry.
|
||||
|
||||
```tsx filename="app/page.tsx" switcher
|
||||
export default async function Page() {
|
||||
const uncachedData = await getData()
|
||||
return (
|
||||
// Pass compositional slots as props, e.g. header and children
|
||||
<CacheComponent header={<h1>Home</h1>}>
|
||||
{/* DynamicComponent is provided as the children slot */}
|
||||
<DynamicComponent data={uncachedData} />
|
||||
</CacheComponent>
|
||||
)
|
||||
}
|
||||
|
||||
async function CacheComponent({
|
||||
header, // header: a compositional slot, injected as a prop
|
||||
children, // children: another slot for nested composition
|
||||
}: {
|
||||
header: ReactNode
|
||||
children: ReactNode
|
||||
}) {
|
||||
'use cache'
|
||||
const cachedData = await fetch('/api/cached-data')
|
||||
return (
|
||||
<div>
|
||||
{header}
|
||||
<PrerenderedComponent data={cachedData} />
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/page.js" switcher
|
||||
export default async function Page() {
|
||||
const uncachedData = await getData()
|
||||
return (
|
||||
// Pass compositional slots as props, e.g. header and children
|
||||
<CacheComponent header={<h1>Home</h1>}>
|
||||
{/* DynamicComponent is provided as the children slot */}
|
||||
<DynamicComponent data={uncachedData} />
|
||||
</CacheComponent>
|
||||
)
|
||||
}
|
||||
|
||||
async function CacheComponent({
|
||||
header, // header: a compositional slot, injected as a prop
|
||||
children, // children: another slot for nested composition
|
||||
}) {
|
||||
'use cache'
|
||||
const cachedData = await fetch('/api/cached-data')
|
||||
return (
|
||||
<div>
|
||||
{header}
|
||||
<PrerenderedComponent data={cachedData} />
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
You can also pass Server Actions through cached components to Client Components without invoking them inside the cacheable function.
|
||||
|
||||
```tsx filename="app/page.tsx" switcher
|
||||
import ClientComponent from './ClientComponent'
|
||||
|
||||
export default async function Page() {
|
||||
const performUpdate = async () => {
|
||||
'use server'
|
||||
// Perform some server-side update
|
||||
await db.update(...)
|
||||
}
|
||||
|
||||
return <CachedComponent performUpdate={performUpdate} />
|
||||
}
|
||||
|
||||
async function CachedComponent({
|
||||
performUpdate,
|
||||
}: {
|
||||
performUpdate: () => Promise<void>
|
||||
}) {
|
||||
'use cache'
|
||||
// Do not call performUpdate here
|
||||
return <ClientComponent action={performUpdate} />
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/page.js" switcher
|
||||
import ClientComponent from './ClientComponent'
|
||||
|
||||
export default async function Page() {
|
||||
const performUpdate = async () => {
|
||||
'use server'
|
||||
// Perform some server-side update
|
||||
await db.update(...)
|
||||
}
|
||||
|
||||
return <CachedComponent performUpdate={performUpdate} />
|
||||
}
|
||||
|
||||
async function CachedComponent({ performUpdate }) {
|
||||
'use cache'
|
||||
// Do not call performUpdate here
|
||||
return <ClientComponent action={performUpdate} />
|
||||
}
|
||||
```
|
||||
|
||||
```tsx filename="app/ClientComponent.tsx" switcher
|
||||
'use client'
|
||||
|
||||
export default function ClientComponent({
|
||||
action,
|
||||
}: {
|
||||
action: () => Promise<void>
|
||||
}) {
|
||||
return <button onClick={action}>Update</button>
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/ClientComponent.js" switcher
|
||||
'use client'
|
||||
|
||||
export default function ClientComponent({ action }) {
|
||||
return <button onClick={action}>Update</button>
|
||||
}
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Debugging cache behavior
|
||||
|
||||
#### Verbose logging
|
||||
|
||||
Set `NEXT_PRIVATE_DEBUG_CACHE=1` for verbose cache logging:
|
||||
|
||||
```bash
|
||||
NEXT_PRIVATE_DEBUG_CACHE=1 npm run dev
|
||||
# or for production
|
||||
NEXT_PRIVATE_DEBUG_CACHE=1 npm run start
|
||||
```
|
||||
|
||||
> **Good to know:** This environment variable also logs ISR and other caching mechanisms. See [Verifying correct production behavior](/docs/app/guides/incremental-static-regeneration#verifying-correct-production-behavior) for more details.
|
||||
|
||||
#### Console log replays
|
||||
|
||||
In development, console logs from cached functions appear with a `Cache` prefix.
|
||||
|
||||
### Build Hangs (Cache Timeout)
|
||||
|
||||
If your build hangs, you're accessing Promises that resolve to uncached or runtime data, created outside a `use cache` boundary. The cached function waits for data that can't resolve during the build, causing a timeout after 50 seconds.
|
||||
|
||||
When the build timeouts you'll see this error message:
|
||||
|
||||
> Error: Filling a cache during prerender timed out, likely because request-specific arguments such as params, searchParams, cookies() or uncached data were used inside "use cache".
|
||||
|
||||
Common ways this happens: passing such Promises as props, accessing them via closure, or retrieving them from shared storage (Maps).
|
||||
|
||||
> **Good to know:** Directly calling `cookies()` or `headers()` inside `use cache` fails immediately with a [different error](/docs/messages/next-request-in-use-cache), not a timeout.
|
||||
|
||||
**Passing runtime data Promises as props:**
|
||||
|
||||
```tsx filename="app/page.tsx"
|
||||
import { cookies } from 'next/headers'
|
||||
import { Suspense } from 'react'
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
<Dynamic />
|
||||
</Suspense>
|
||||
)
|
||||
}
|
||||
|
||||
async function Dynamic() {
|
||||
const cookieStore = cookies()
|
||||
return <Cached promise={cookieStore} /> // Build hangs
|
||||
}
|
||||
|
||||
async function Cached({ promise }: { promise: Promise<unknown> }) {
|
||||
'use cache'
|
||||
const data = await promise // Waits for runtime data during build
|
||||
return <p>..</p>
|
||||
}
|
||||
```
|
||||
|
||||
Await the `cookies` store in the `Dynamic` component, and pass a cookie value to the `Cached` component.
|
||||
|
||||
**Shared deduplication storage:**
|
||||
|
||||
```tsx filename="app/page.tsx"
|
||||
// Problem: Map stores dynamic Promises, accessed by cached code
|
||||
import { Suspense } from 'react'
|
||||
|
||||
const cache = new Map<string, Promise<string>>()
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<>
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
<Dynamic id="data" />
|
||||
</Suspense>
|
||||
<Cached id="data" />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
async function Dynamic({ id }: { id: string }) {
|
||||
// Stores dynamic Promise in shared Map
|
||||
cache.set(
|
||||
id,
|
||||
fetch(`https://api.example.com/${id}`).then((r) => r.text())
|
||||
)
|
||||
return <p>Dynamic</p>
|
||||
}
|
||||
|
||||
async function Cached({ id }: { id: string }) {
|
||||
'use cache'
|
||||
return <p>{await cache.get(id)}</p> // Build hangs - retrieves dynamic Promise
|
||||
}
|
||||
```
|
||||
|
||||
Use Next.js's built-in `fetch()` deduplication or use separate Maps for cached and uncached contexts.
|
||||
|
||||
## Platform Support
|
||||
|
||||
| Deployment Option | Supported |
|
||||
| ------------------------------------------------------------------- | ----------------- |
|
||||
| [Node.js server](/docs/app/getting-started/deploying#nodejs-server) | Yes |
|
||||
| [Docker container](/docs/app/getting-started/deploying#docker) | Yes |
|
||||
| [Static export](/docs/app/getting-started/deploying#static-export) | No |
|
||||
| [Adapters](/docs/app/getting-started/deploying#adapters) | Platform-specific |
|
||||
|
||||
Learn how to [configure caching](/docs/app/guides/self-hosting#caching-and-isr) when self-hosting Next.js.
|
||||
|
||||
## Version History
|
||||
|
||||
| Version | Changes |
|
||||
| --------- | ----------------------------------------------------------- |
|
||||
| `v16.0.0` | `"use cache"` is enabled with the Cache Components feature. |
|
||||
| `v15.0.0` | `"use cache"` is introduced as an experimental feature. |
|
||||
Generated
Vendored
+123
@@ -0,0 +1,123 @@
|
||||
---
|
||||
title: use client
|
||||
description: Learn how to use the use client directive to render a component on the client.
|
||||
---
|
||||
|
||||
The `'use client'` directive declares an entry point for the components to be rendered on the **client side** and should be used when creating interactive user interfaces (UI) that require client-side JavaScript capabilities, such as state management, event handling, and access to browser APIs. This is a React feature.
|
||||
|
||||
> **Good to know:**
|
||||
>
|
||||
> You do not need to add the `'use client'` directive to every file that contains Client Components. You only need to add it to the files whose components you want to render directly within Server Components. The `'use client'` directive defines the client-server [boundary](https://nextjs.org/docs/app/building-your-application/rendering#network-boundary), and the components exported from such a file serve as entry points to the client.
|
||||
|
||||
## Usage
|
||||
|
||||
To declare an entry point for the Client Components, add the `'use client'` directive **at the top of the file**, before any imports:
|
||||
|
||||
```tsx filename="app/components/counter.tsx" highlight={1} switcher
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
|
||||
export default function Counter() {
|
||||
const [count, setCount] = useState(0)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>Count: {count}</p>
|
||||
<button onClick={() => setCount(count + 1)}>Increment</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/components/counter.js" highlight={1} switcher
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
|
||||
export default function Counter() {
|
||||
const [count, setCount] = useState(0)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>Count: {count}</p>
|
||||
<button onClick={() => setCount(count + 1)}>Increment</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
When using the `'use client'` directive, the props of the Client Components must be [serializable](https://react.dev/reference/rsc/use-client#serializable-types). This means the props need to be in a format that React can serialize when sending data from the server to the client.
|
||||
|
||||
```tsx filename="app/components/counter.tsx" highlight={4} switcher
|
||||
'use client'
|
||||
|
||||
export default function Counter({
|
||||
onClick /* ❌ Function is not serializable */,
|
||||
}) {
|
||||
return (
|
||||
<div>
|
||||
<button onClick={onClick}>Increment</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/components/counter.js" highlight={4} switcher
|
||||
'use client'
|
||||
|
||||
export default function Counter({
|
||||
onClick /* ❌ Function is not serializable */,
|
||||
}) {
|
||||
return (
|
||||
<div>
|
||||
<button onClick={onClick}>Increment</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Nesting Client Components within Server Components
|
||||
|
||||
Combining Server and Client Components allows you to build applications that are both performant and interactive:
|
||||
|
||||
1. **Server Components**: Use for static content, data fetching, and SEO-friendly elements.
|
||||
2. **Client Components**: Use for interactive elements that require state, effects, or browser APIs.
|
||||
3. **Component composition**: Nest Client Components within Server Components as needed for a clear separation of server and client logic.
|
||||
|
||||
In the following example:
|
||||
|
||||
- `Header` is a Server Component handling static content.
|
||||
- `Counter` is a Client Component enabling interactivity within the page.
|
||||
|
||||
```tsx filename="app/page.tsx" highlight={2,8} switcher
|
||||
import Header from './header'
|
||||
import Counter from './counter' // This is a Client Component
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<div>
|
||||
<Header />
|
||||
<Counter />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/page.js" highlight={2,8} switcher
|
||||
import Header from './header'
|
||||
import Counter from './counter' // This is a Client Component
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<div>
|
||||
<Header />
|
||||
<Counter />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Reference
|
||||
|
||||
See the [React documentation](https://react.dev/reference/rsc/use-client) for more information on `'use client'`.
|
||||
Generated
Vendored
+194
@@ -0,0 +1,194 @@
|
||||
---
|
||||
title: use server
|
||||
description: Learn how to use the use server directive to execute code on the server.
|
||||
---
|
||||
|
||||
The `use server` directive designates a function or file to be executed on the **server side**. It can be used at the top of a file to indicate that all functions in the file are server-side, or inline at the top of a function to mark the function as a [Server Function](https://19.react.dev/reference/rsc/server-functions). This is a React feature.
|
||||
|
||||
## Using `use server` at the top of a file
|
||||
|
||||
The following example shows a file with a `use server` directive at the top. All functions in the file are executed on the server.
|
||||
|
||||
```tsx filename="app/actions.ts" highlight={1} switcher
|
||||
'use server'
|
||||
import { db } from '@/lib/db' // Your database client
|
||||
import { auth } from '@/lib/auth'
|
||||
|
||||
export async function createUser(data: { name: string; email: string }) {
|
||||
const session = await auth()
|
||||
if (!session?.user) {
|
||||
throw new Error('Unauthorized')
|
||||
}
|
||||
|
||||
const user = await db.user.create({ data })
|
||||
return { id: user.id, name: user.name }
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/actions.js" highlight={1} switcher
|
||||
'use server'
|
||||
import { db } from '@/lib/db' // Your database client
|
||||
import { auth } from '@/lib/auth'
|
||||
|
||||
export async function createUser(data) {
|
||||
const session = await auth()
|
||||
if (!session?.user) {
|
||||
throw new Error('Unauthorized')
|
||||
}
|
||||
|
||||
const user = await db.user.create({ data })
|
||||
return { id: user.id, name: user.name }
|
||||
}
|
||||
```
|
||||
|
||||
### Using Server Functions in a Client Component
|
||||
|
||||
To use Server Functions in Client Components you need to create your Server Functions in a dedicated file using the `use server` directive at the top of the file. These Server Functions can then be imported into Client and Server Components and executed.
|
||||
|
||||
Assuming you have a `fetchUsers` Server Function in `actions.ts`:
|
||||
|
||||
```tsx filename="app/actions.ts" highlight={1} switcher
|
||||
'use server'
|
||||
import { db } from '@/lib/db' // Your database client
|
||||
import { auth } from '@/lib/auth'
|
||||
|
||||
export async function fetchUsers() {
|
||||
const session = await auth()
|
||||
if (!session?.user) {
|
||||
throw new Error('Unauthorized')
|
||||
}
|
||||
|
||||
const users = await db.user.findMany({
|
||||
select: { id: true, name: true, email: true },
|
||||
})
|
||||
return users
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/actions.js" highlight={1} switcher
|
||||
'use server'
|
||||
import { db } from '@/lib/db' // Your database client
|
||||
import { auth } from '@/lib/auth'
|
||||
|
||||
export async function fetchUsers() {
|
||||
const session = await auth()
|
||||
if (!session?.user) {
|
||||
throw new Error('Unauthorized')
|
||||
}
|
||||
|
||||
const users = await db.user.findMany({
|
||||
select: { id: true, name: true, email: true },
|
||||
})
|
||||
return users
|
||||
}
|
||||
```
|
||||
|
||||
Then you can import the `fetchUsers` Server Function into a Client Component and execute it on the client-side.
|
||||
|
||||
```tsx filename="app/components/my-button.tsx" highlight={1,2,5} switcher
|
||||
'use client'
|
||||
import { fetchUsers } from '../actions'
|
||||
|
||||
export default function MyButton() {
|
||||
return <button onClick={() => fetchUsers()}>Fetch Users</button>
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/components/my-button.js" highlight={1,2,5} switcher
|
||||
'use client'
|
||||
import { fetchUsers } from '../actions'
|
||||
|
||||
export default function MyButton() {
|
||||
return <button onClick={() => fetchUsers()}>Fetch Users</button>
|
||||
}
|
||||
```
|
||||
|
||||
## Using `use server` inline
|
||||
|
||||
In the following example, `use server` is used inline at the top of a function to mark it as a [Server Function](https://19.react.dev/reference/rsc/server-functions):
|
||||
|
||||
```tsx filename="app/posts/[id]/page.tsx" switcher highlight={8}
|
||||
import { EditPost } from './edit-post'
|
||||
import { revalidatePath } from 'next/cache'
|
||||
|
||||
export default async function PostPage({ params }: { params: { id: string } }) {
|
||||
const post = await getPost(params.id)
|
||||
|
||||
async function updatePost(formData: FormData) {
|
||||
'use server'
|
||||
// Verify auth before saving (e.g. inside savePost)
|
||||
await savePost(params.id, formData)
|
||||
revalidatePath(`/posts/${params.id}`)
|
||||
}
|
||||
|
||||
return <EditPost updatePostAction={updatePost} post={post} />
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/posts/[id]/page.js" switcher highlight={8}
|
||||
import { EditPost } from './edit-post'
|
||||
import { revalidatePath } from 'next/cache'
|
||||
|
||||
export default async function PostPage({ params }) {
|
||||
const post = await getPost(params.id)
|
||||
|
||||
async function updatePost(formData) {
|
||||
'use server'
|
||||
// Verify auth before saving (e.g. inside savePost)
|
||||
await savePost(params.id, formData)
|
||||
revalidatePath(`/posts/${params.id}`)
|
||||
}
|
||||
|
||||
return <EditPost updatePostAction={updatePost} post={post} />
|
||||
}
|
||||
```
|
||||
|
||||
## Security considerations
|
||||
|
||||
Design your data access functions as secure primitives: validate inputs, check authentication and authorization, and constrain return types to only what the caller needs. When Server Functions delegate to a [Data Access Layer](/docs/app/guides/data-security#using-a-data-access-layer-for-mutations), these guarantees live in one place and apply consistently.
|
||||
|
||||
{/* TODO: showcase input validation */}
|
||||
|
||||
### Authentication and authorization
|
||||
|
||||
Always authenticate and authorize users before performing sensitive server-side operations. Read authentication from cookies or headers rather than accepting tokens as function parameters.
|
||||
|
||||
```tsx filename="app/actions.ts" highlight={1,7,8,9,10} switcher
|
||||
'use server'
|
||||
|
||||
import { db } from '@/lib/db' // Your database client
|
||||
import { auth } from '@/lib/auth' // Your authentication library
|
||||
|
||||
export async function createUser(data: { name: string; email: string }) {
|
||||
const session = await auth()
|
||||
if (!session?.user) {
|
||||
throw new Error('Unauthorized')
|
||||
}
|
||||
const newUser = await db.user.create({ data })
|
||||
return { id: newUser.id, name: newUser.name }
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/actions.js" highlight={1,7,8,9,10} switcher
|
||||
'use server'
|
||||
|
||||
import { db } from '@/lib/db' // Your database client
|
||||
import { auth } from '@/lib/auth' // Your authentication library
|
||||
|
||||
export async function createUser(data) {
|
||||
const session = await auth()
|
||||
if (!session?.user) {
|
||||
throw new Error('Unauthorized')
|
||||
}
|
||||
const newUser = await db.user.create({ data })
|
||||
return { id: newUser.id, name: newUser.name }
|
||||
}
|
||||
```
|
||||
|
||||
### Return values
|
||||
|
||||
Server Function return values are serialized and sent to the client. Only return data the UI needs, not raw database records. See the [Data Security guide](/docs/app/guides/data-security#controlling-return-values) for more details.
|
||||
|
||||
## Reference
|
||||
|
||||
See the [React documentation](https://react.dev/reference/rsc/use-server) for more information on `use server`.
|
||||
Generated
Vendored
+1068
File diff suppressed because it is too large
Load Diff
Generated
Vendored
+406
@@ -0,0 +1,406 @@
|
||||
---
|
||||
title: Form Component
|
||||
description: Learn how to use the `<Form>` component to handle form submissions and search params updates with client-side navigation.
|
||||
---
|
||||
|
||||
The `<Form>` component extends the HTML `<form>` element to provide <AppOnly>[**prefetching**](/docs/app/getting-started/linking-and-navigating#prefetching) of [loading UI](/docs/app/api-reference/file-conventions/loading),</AppOnly> **client-side navigation** on submission, and **progressive enhancement**.
|
||||
|
||||
It's useful for forms that update URL search params as it reduces the boilerplate code needed to achieve the above.
|
||||
|
||||
Basic usage:
|
||||
|
||||
<AppOnly>
|
||||
|
||||
```tsx filename="/app/ui/search.tsx" switcher
|
||||
import Form from 'next/form'
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<Form action="/search">
|
||||
{/* On submission, the input value will be appended to
|
||||
the URL, e.g. /search?query=abc */}
|
||||
<input name="query" />
|
||||
<button type="submit">Submit</button>
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="/app/ui/search.js" switcher
|
||||
import Form from 'next/form'
|
||||
|
||||
export default function Search() {
|
||||
return (
|
||||
<Form action="/search">
|
||||
{/* On submission, the input value will be appended to
|
||||
the URL, e.g. /search?query=abc */}
|
||||
<input name="query" />
|
||||
<button type="submit">Submit</button>
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
</AppOnly>
|
||||
|
||||
<PagesOnly>
|
||||
|
||||
```tsx filename="/ui/search.js" switcher
|
||||
import Form from 'next/form'
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<Form action="/search">
|
||||
{/* On submission, the input value will be appended to
|
||||
the URL, e.g. /search?query=abc */}
|
||||
<input name="query" />
|
||||
<button type="submit">Submit</button>
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="/ui/search.js" switcher
|
||||
import Form from 'next/form'
|
||||
|
||||
export default function Search() {
|
||||
return (
|
||||
<Form action="/search">
|
||||
{/* On submission, the input value will be appended to
|
||||
the URL, e.g. /search?query=abc */}
|
||||
<input name="query" />
|
||||
<button type="submit">Submit</button>
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
</PagesOnly>
|
||||
|
||||
## Reference
|
||||
|
||||
The behavior of the `<Form>` component depends on whether the `action` prop is passed a `string` or `function`.
|
||||
|
||||
<AppOnly>
|
||||
|
||||
- When `action` is a **string**, the `<Form>` behaves like a native HTML form that uses a **`GET`** method. The form data is encoded into the URL as search params, and when the form is submitted, it navigates to the specified URL. In addition, Next.js:
|
||||
- [Prefetches](/docs/app/getting-started/linking-and-navigating#prefetching) the path when the form becomes visible, this preloads shared UI (e.g. `layout.js` and `loading.js`), resulting in faster navigation.
|
||||
- Performs a [client-side navigation](/docs/app/getting-started/linking-and-navigating#client-side-transitions) instead of a full page reload when the form is submitted. This retains shared UI and client-side state.
|
||||
- When `action` is a **function** (Server Action), `<Form>` behaves like a [React form](https://react.dev/reference/react-dom/components/form), executing the action when the form is submitted.
|
||||
|
||||
</AppOnly>
|
||||
|
||||
<PagesOnly>
|
||||
|
||||
- When `action` is a **string**, the `<Form>` behaves like a native HTML form that uses a **`GET`** method. The form data is encoded into the URL as search params, and when the form is submitted, it navigates to the specified URL. In addition, Next.js:
|
||||
- Performs a [client-side navigation](/docs/app/getting-started/linking-and-navigating#client-side-transitions) instead of a full page reload when the form is submitted. This retains shared UI and client-side state.
|
||||
|
||||
</PagesOnly>
|
||||
|
||||
### `action` (string) Props
|
||||
|
||||
<PagesOnly>
|
||||
|
||||
When `action` is a string, the `<Form>` component supports the following props:
|
||||
|
||||
| Prop | Example | Type | Required |
|
||||
| --------- | ------------------ | ------------------------------- | -------- |
|
||||
| `action` | `action="/search"` | `string` (URL or relative path) | Yes |
|
||||
| `replace` | `replace={false}` | `boolean` | - |
|
||||
| `scroll` | `scroll={true}` | `boolean` | - |
|
||||
|
||||
- **`action`**: The URL or path to navigate to when the form is submitted.
|
||||
- An empty string `""` will navigate to the same route with updated search params.
|
||||
- **`replace`**: Replaces the current history state instead of pushing a new one to the [browser's history](https://developer.mozilla.org/en-US/docs/Web/API/History_API) stack. Default is `false`.
|
||||
- **`scroll`**: Controls the scroll behavior during navigation. Defaults to `true`, this means it will scroll to the top of the new route, and maintain the scroll position for backwards and forwards navigation.
|
||||
|
||||
</PagesOnly>
|
||||
|
||||
<AppOnly>
|
||||
|
||||
When `action` is a string, the `<Form>` component supports the following props:
|
||||
|
||||
| Prop | Example | Type | Required |
|
||||
| ---------- | ------------------ | ------------------------------- | -------- |
|
||||
| `action` | `action="/search"` | `string` (URL or relative path) | Yes |
|
||||
| `replace` | `replace={false}` | `boolean` | - |
|
||||
| `scroll` | `scroll={true}` | `boolean` | - |
|
||||
| `prefetch` | `prefetch={true}` | `boolean` | - |
|
||||
|
||||
- **`action`**: The URL or path to navigate to when the form is submitted.
|
||||
- An empty string `""` will navigate to the same route with updated search params.
|
||||
- **`replace`**: Replaces the current history state instead of pushing a new one to the [browser's history](https://developer.mozilla.org/en-US/docs/Web/API/History_API) stack. Default is `false`.
|
||||
- **`scroll`**: Controls the scroll behavior during navigation. Defaults to `true`, this means it will scroll to the top of the new route, and maintain the scroll position for backwards and forwards navigation.
|
||||
- **`prefetch`**: Controls whether the path should be prefetched when the form becomes visible in the user's viewport. Defaults to `true`.
|
||||
|
||||
### `action` (function) Props
|
||||
|
||||
When `action` is a function, the `<Form>` component supports the following prop:
|
||||
|
||||
| Prop | Example | Type | Required |
|
||||
| -------- | ------------------- | -------------------------- | -------- |
|
||||
| `action` | `action={myAction}` | `function` (Server Action) | Yes |
|
||||
|
||||
- **`action`**: The Server Action to be called when the form is submitted. See the [React docs](https://react.dev/reference/react-dom/components/form#props) for more.
|
||||
|
||||
> **Good to know**: When `action` is a function, the `replace` and `scroll` props are ignored.
|
||||
|
||||
</AppOnly>
|
||||
|
||||
### Caveats
|
||||
|
||||
<AppOnly>
|
||||
|
||||
- **`formAction`**: Can be used in a `<button>` or `<input type="submit">` fields to override the `action` prop. Next.js will perform a client-side navigation, however, this approach doesn't support prefetching.
|
||||
- When using [`basePath`](/docs/app/api-reference/config/next-config-js/basePath), you must also include it in the `formAction` path. e.g. `formAction="/base-path/search"`.
|
||||
- **`key`**: Passing a `key` prop to a string `action` is not supported. If you'd like to trigger a re-render or perform a mutation, consider using a function `action` instead.
|
||||
|
||||
</AppOnly>
|
||||
|
||||
- **`onSubmit`**: Can be used to handle form submission logic. However, calling `event.preventDefault()` will override `<Form>` behavior such as navigating to the specified URL.
|
||||
- **[`method`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form#method), [`encType`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form#enctype), [`target`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form#target)**: Are not supported as they override `<Form>` behavior.
|
||||
- Similarly, `formMethod`, `formEncType`, and `formTarget` can be used to override the `method`, `encType`, and `target` props respectively, and using them will fallback to native browser behavior.
|
||||
- If you need to use these props, use the HTML `<form>` element instead.
|
||||
- **`<input type="file">`**: Using this input type when the `action` is a string will match browser behavior by submitting the filename instead of the file object.
|
||||
|
||||
<AppOnly>
|
||||
|
||||
## Examples
|
||||
|
||||
### Search form that leads to a search result page
|
||||
|
||||
You can create a search form that navigates to a search results page by passing the path as an `action`:
|
||||
|
||||
```tsx filename="/app/page.tsx" switcher
|
||||
import Form from 'next/form'
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<Form action="/search">
|
||||
<input name="query" />
|
||||
<button type="submit">Submit</button>
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="/app/page.js" switcher
|
||||
import Form from 'next/form'
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<Form action="/search">
|
||||
<input name="query" />
|
||||
<button type="submit">Submit</button>
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
When the user updates the query input field and submits the form, the form data will be encoded into the URL as search params, e.g. `/search?query=abc`.
|
||||
|
||||
> **Good to know**: If you pass an empty string `""` to `action`, the form will navigate to the same route with updated search params.
|
||||
|
||||
On the results page, you can access the query using the [`searchParams`](/docs/app/api-reference/file-conventions/page#searchparams-optional) `page.js` prop and use it to fetch data from an external source.
|
||||
|
||||
```tsx filename="/app/search/page.tsx" switcher
|
||||
import { getSearchResults } from '@/lib/search'
|
||||
|
||||
export default async function SearchPage({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: Promise<{ [key: string]: string | string[] | undefined }>
|
||||
}) {
|
||||
const results = await getSearchResults((await searchParams).query)
|
||||
|
||||
return <div>...</div>
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="/app/search/page.js" switcher
|
||||
import { getSearchResults } from '@/lib/search'
|
||||
|
||||
export default async function SearchPage({ searchParams }) {
|
||||
const results = await getSearchResults((await searchParams).query)
|
||||
|
||||
return <div>...</div>
|
||||
}
|
||||
```
|
||||
|
||||
When the `<Form>` becomes visible in the user's viewport, shared UI (such as `layout.js` and `loading.js`) on the `/search` page will be prefetched. On submission, the form will immediately navigate to the new route and show loading UI while the results are being fetched. You can design the fallback UI using [`loading.js`](/docs/app/api-reference/file-conventions/loading):
|
||||
|
||||
```tsx filename="/app/search/loading.tsx" switcher
|
||||
export default function Loading() {
|
||||
return <div>Loading...</div>
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="/app/search/loading.js" switcher
|
||||
export default function Loading() {
|
||||
return <div>Loading...</div>
|
||||
}
|
||||
```
|
||||
|
||||
To cover cases when shared UI hasn't yet loaded, you can show instant feedback to the user using [`useFormStatus`](https://react.dev/reference/react-dom/hooks/useFormStatus).
|
||||
|
||||
First, create a component that displays a loading state when the form is pending:
|
||||
|
||||
```tsx filename="/app/ui/search-button.tsx" switcher
|
||||
'use client'
|
||||
import { useFormStatus } from 'react-dom'
|
||||
|
||||
export default function SearchButton() {
|
||||
const status = useFormStatus()
|
||||
return (
|
||||
<button type="submit">{status.pending ? 'Searching...' : 'Search'}</button>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="/app/ui/search-button.js" switcher
|
||||
'use client'
|
||||
import { useFormStatus } from 'react-dom'
|
||||
|
||||
export default function SearchButton() {
|
||||
const status = useFormStatus()
|
||||
return (
|
||||
<button type="submit">{status.pending ? 'Searching...' : 'Search'}</button>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
Then, update the search form page to use the `SearchButton` component:
|
||||
|
||||
```tsx filename="/app/page.tsx" switcher
|
||||
import Form from 'next/form'
|
||||
import { SearchButton } from '@/ui/search-button'
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<Form action="/search">
|
||||
<input name="query" />
|
||||
<SearchButton />
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="/app/ui/search-button.js" switcher
|
||||
import Form from 'next/form'
|
||||
import { SearchButton } from '@/ui/search-button'
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<Form action="/search">
|
||||
<input name="query" />
|
||||
<SearchButton />
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Mutations with Server Actions
|
||||
|
||||
You can perform mutations by passing a function to the `action` prop.
|
||||
|
||||
```tsx filename="/app/posts/create/page.tsx" switcher
|
||||
import Form from 'next/form'
|
||||
import { createPost } from '@/posts/actions'
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<Form action={createPost}>
|
||||
<input name="title" />
|
||||
{/* ... */}
|
||||
<button type="submit">Create Post</button>
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="/app/posts/create/page.js" switcher
|
||||
import Form from 'next/form'
|
||||
import { createPost } from '@/posts/actions'
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<Form action={createPost}>
|
||||
<input name="title" />
|
||||
{/* ... */}
|
||||
<button type="submit">Create Post</button>
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
After a mutation, it's common to redirect to the new resource. You can use the [`redirect`](/docs/app/guides/redirecting) function from `next/navigation` to navigate to the new post page.
|
||||
|
||||
> **Good to know**: Since the "destination" of the form submission is not known until the action is executed, `<Form>` cannot automatically prefetch shared UI.
|
||||
|
||||
```tsx filename="/app/posts/actions.ts" switcher
|
||||
'use server'
|
||||
import { redirect } from 'next/navigation'
|
||||
|
||||
export async function createPost(formData: FormData) {
|
||||
// Create a new post
|
||||
// ...
|
||||
|
||||
// Redirect to the new post
|
||||
redirect(`/posts/${data.id}`)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="/app/posts/actions.js" switcher
|
||||
'use server'
|
||||
import { redirect } from 'next/navigation'
|
||||
|
||||
export async function createPost(formData) {
|
||||
// Create a new post
|
||||
// ...
|
||||
|
||||
// Redirect to the new post
|
||||
redirect(`/posts/${data.id}`)
|
||||
}
|
||||
```
|
||||
|
||||
Then, in the new page, you can fetch data using the `params` prop:
|
||||
|
||||
```tsx filename="/app/posts/[id]/page.tsx" switcher
|
||||
import { getPost } from '@/posts/data'
|
||||
|
||||
export default async function PostPage({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ id: string }>
|
||||
}) {
|
||||
const { id } = await params
|
||||
const data = await getPost(id)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>{data.title}</h1>
|
||||
{/* ... */}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="/app/posts/[id]/page.js" switcher
|
||||
import { getPost } from '@/posts/data'
|
||||
|
||||
export default async function PostPage({ params }) {
|
||||
const { id } = await params
|
||||
const data = await getPost(id)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>{data.title}</h1>
|
||||
{/* ... */}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
See the [Server Actions](/docs/app/getting-started/mutating-data) docs for more examples.
|
||||
|
||||
</AppOnly>
|
||||
Generated
Vendored
+1425
File diff suppressed because it is too large
Load Diff
Generated
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
---
|
||||
title: Components
|
||||
description: API Reference for Next.js built-in components.
|
||||
---
|
||||
|
||||
{/* The content of this doc is shared between the app and pages router. You can use the `<PagesOnly>Content</PagesOnly>` component to add content that is specific to the Pages Router. Any shared content should not be wrapped in a component. */}
|
||||
Generated
Vendored
+1377
File diff suppressed because it is too large
Load Diff
Generated
Vendored
+468
@@ -0,0 +1,468 @@
|
||||
---
|
||||
title: Script Component
|
||||
description: Optimize third-party scripts in your Next.js application using the built-in `next/script` Component.
|
||||
---
|
||||
|
||||
{/* The content of this doc is shared between the app and pages router. You can use the `<PagesOnly>Content</PagesOnly>` component to add content that is specific to the Pages Router. Any shared content should not be wrapped in a component. */}
|
||||
|
||||
This API reference will help you understand how to use [props](#props) available for the Script Component. For features and usage, please see the [Optimizing Scripts](/docs/app/guides/scripts) page.
|
||||
|
||||
```tsx filename="app/dashboard/page.tsx" switcher
|
||||
import Script from 'next/script'
|
||||
|
||||
export default function Dashboard() {
|
||||
return (
|
||||
<>
|
||||
<Script src="https://example.com/script.js" />
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/dashboard/page.js" switcher
|
||||
import Script from 'next/script'
|
||||
|
||||
export default function Dashboard() {
|
||||
return (
|
||||
<>
|
||||
<Script src="https://example.com/script.js" />
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
Here's a summary of the props available for the Script Component:
|
||||
|
||||
| Prop | Example | Type | Required |
|
||||
| ----------------------- | --------------------------------- | -------- | ------------------------------------- |
|
||||
| [`src`](#src) | `src="http://example.com/script"` | String | Required unless inline script is used |
|
||||
| [`strategy`](#strategy) | `strategy="lazyOnload"` | String | - |
|
||||
| [`onLoad`](#onload) | `onLoad={onLoadFunc}` | Function | - |
|
||||
| [`onReady`](#onready) | `onReady={onReadyFunc}` | Function | - |
|
||||
| [`onError`](#onerror) | `onError={onErrorFunc}` | Function | - |
|
||||
|
||||
## Required Props
|
||||
|
||||
The `<Script />` component requires the following properties.
|
||||
|
||||
### `src`
|
||||
|
||||
A path string specifying the URL of an external script. This can be either an absolute external URL or an internal path. The `src` property is required unless an inline script is used.
|
||||
|
||||
## Optional Props
|
||||
|
||||
The `<Script />` component accepts a number of additional properties beyond those which are required.
|
||||
|
||||
### `strategy`
|
||||
|
||||
The loading strategy of the script. There are four different strategies that can be used:
|
||||
|
||||
- `beforeInteractive`: Load before any Next.js code and before any page hydration occurs.
|
||||
- `afterInteractive`: (**default**) Load early but after some hydration on the page occurs.
|
||||
- `lazyOnload`: Load during browser idle time.
|
||||
- `worker`: (experimental) Load in a web worker.
|
||||
|
||||
### `beforeInteractive`
|
||||
|
||||
Scripts that load with the `beforeInteractive` strategy are injected into the initial HTML from the server, downloaded before any Next.js module, and executed in the order they are placed.
|
||||
|
||||
Scripts denoted with this strategy are preloaded and fetched before any first-party code, but their execution **does not block page hydration from occurring**.
|
||||
|
||||
<AppOnly>
|
||||
|
||||
`beforeInteractive` scripts must be placed inside the root layout (`app/layout.tsx`) and are designed to load scripts that are needed by the entire site (i.e. the script will load when any page in the application has been loaded server-side).
|
||||
|
||||
</AppOnly>
|
||||
|
||||
<PagesOnly>
|
||||
|
||||
`beforeInteractive` scripts must be placed inside the `Document` Component (`pages/_document.js`) and are designed to load scripts that are needed by the entire site (i.e. the script will load when any page in the application has been loaded server-side).
|
||||
|
||||
</PagesOnly>
|
||||
|
||||
**This strategy should only be used for critical scripts that need to be fetched as soon as possible.**
|
||||
|
||||
<AppOnly>
|
||||
|
||||
```tsx filename="app/layout.tsx" switcher
|
||||
import Script from 'next/script'
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>
|
||||
{children}
|
||||
<Script
|
||||
src="https://example.com/script.js"
|
||||
strategy="beforeInteractive"
|
||||
/>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/layout.js" switcher
|
||||
import Script from 'next/script'
|
||||
|
||||
export default function RootLayout({ children }) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>
|
||||
{children}
|
||||
<Script
|
||||
src="https://example.com/script.js"
|
||||
strategy="beforeInteractive"
|
||||
/>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
</AppOnly>
|
||||
|
||||
<PagesOnly>
|
||||
|
||||
```jsx filename="pages/_document.js"
|
||||
import { Html, Head, Main, NextScript } from 'next/document'
|
||||
import Script from 'next/script'
|
||||
|
||||
export default function Document() {
|
||||
return (
|
||||
<Html>
|
||||
<Head />
|
||||
<body>
|
||||
<Main />
|
||||
<NextScript />
|
||||
<Script
|
||||
src="https://example.com/script.js"
|
||||
strategy="beforeInteractive"
|
||||
/>
|
||||
</body>
|
||||
</Html>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
</PagesOnly>
|
||||
|
||||
> **Good to know**: Scripts with `beforeInteractive` will always be injected inside the `head` of the HTML document regardless of where it's placed in the component.
|
||||
|
||||
Some examples of scripts that should be fetched as soon as possible with `beforeInteractive` include:
|
||||
|
||||
- Bot detectors
|
||||
- Cookie consent managers
|
||||
|
||||
### `afterInteractive`
|
||||
|
||||
Scripts that use the `afterInteractive` strategy are injected into the HTML client-side and will load after some (or all) hydration occurs on the page. **This is the default strategy** of the Script component and should be used for any script that needs to load as soon as possible but not before any first-party Next.js code.
|
||||
|
||||
`afterInteractive` scripts can be placed inside of any page or layout and will only load and execute when that page (or group of pages) is opened in the browser.
|
||||
|
||||
```jsx filename="app/page.js"
|
||||
import Script from 'next/script'
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<>
|
||||
<Script src="https://example.com/script.js" strategy="afterInteractive" />
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
Some examples of scripts that are good candidates for `afterInteractive` include:
|
||||
|
||||
- Tag managers
|
||||
- Analytics
|
||||
|
||||
### `lazyOnload`
|
||||
|
||||
Scripts that use the `lazyOnload` strategy are injected into the HTML client-side during browser idle time and will load after all resources on the page have been fetched. This strategy should be used for any background or low priority scripts that do not need to load early.
|
||||
|
||||
`lazyOnload` scripts can be placed inside of any page or layout and will only load and execute when that page (or group of pages) is opened in the browser.
|
||||
|
||||
```jsx filename="app/page.js"
|
||||
import Script from 'next/script'
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<>
|
||||
<Script src="https://example.com/script.js" strategy="lazyOnload" />
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
Examples of scripts that do not need to load immediately and can be fetched with `lazyOnload` include:
|
||||
|
||||
- Chat support plugins
|
||||
- Social media widgets
|
||||
|
||||
### `worker`
|
||||
|
||||
> **Warning:** The `worker` strategy is not yet stable and does not yet work with the App Router. Use with caution.
|
||||
|
||||
Scripts that use the `worker` strategy are off-loaded to a web worker in order to free up the main thread and ensure that only critical, first-party resources are processed on it. While this strategy can be used for any script, it is an advanced use case that is not guaranteed to support all third-party scripts.
|
||||
|
||||
To use `worker` as a strategy, the `nextScriptWorkers` flag must be enabled in `next.config.js`:
|
||||
|
||||
```js filename="next.config.js"
|
||||
module.exports = {
|
||||
experimental: {
|
||||
nextScriptWorkers: true,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
`worker` scripts can **only currently be used in the `pages/` directory**:
|
||||
|
||||
```tsx filename="pages/home.tsx" switcher
|
||||
import Script from 'next/script'
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<>
|
||||
<Script src="https://example.com/script.js" strategy="worker" />
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="pages/home.js" switcher
|
||||
import Script from 'next/script'
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<>
|
||||
<Script src="https://example.com/script.js" strategy="worker" />
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### `onLoad`
|
||||
|
||||
> **Warning:** `onLoad` does not yet work with Server Components and can only be used in Client Components. Further, `onLoad` can't be used with `beforeInteractive` – consider using `onReady` instead.
|
||||
|
||||
Some third-party scripts require users to run JavaScript code once after the script has finished loading in order to instantiate content or call a function. If you are loading a script with either `afterInteractive` or `lazyOnload` as a loading strategy, you can execute code after it has loaded using the `onLoad` property.
|
||||
|
||||
Here's an example of executing a lodash method only after the library has been loaded.
|
||||
|
||||
```tsx filename="app/page.tsx" switcher
|
||||
'use client'
|
||||
|
||||
import Script from 'next/script'
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<>
|
||||
<Script
|
||||
src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.20/lodash.min.js"
|
||||
onLoad={() => {
|
||||
console.log(_.sample([1, 2, 3, 4]))
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/page.js" switcher
|
||||
'use client'
|
||||
|
||||
import Script from 'next/script'
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<>
|
||||
<Script
|
||||
src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.20/lodash.min.js"
|
||||
onLoad={() => {
|
||||
console.log(_.sample([1, 2, 3, 4]))
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### `onReady`
|
||||
|
||||
> **Warning:** `onReady` does not yet work with Server Components and can only be used in Client Components.
|
||||
|
||||
Some third-party scripts require users to run JavaScript code after the script has finished loading and every time the component is mounted (after a route navigation for example). You can execute code after the script's load event when it first loads and then after every subsequent component re-mount using the `onReady` property.
|
||||
|
||||
Here's an example of how to re-instantiate a Google Maps JS embed every time the component is mounted:
|
||||
|
||||
<AppOnly>
|
||||
|
||||
```tsx filename="app/page.tsx" switcher
|
||||
'use client'
|
||||
|
||||
import { useRef } from 'react'
|
||||
import Script from 'next/script'
|
||||
|
||||
export default function Page() {
|
||||
const mapRef = useRef()
|
||||
|
||||
return (
|
||||
<>
|
||||
<div ref={mapRef}></div>
|
||||
<Script
|
||||
id="google-maps"
|
||||
src="https://maps.googleapis.com/maps/api/js"
|
||||
onReady={() => {
|
||||
new google.maps.Map(mapRef.current, {
|
||||
center: { lat: -34.397, lng: 150.644 },
|
||||
zoom: 8,
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/page.js" switcher
|
||||
'use client'
|
||||
|
||||
import { useRef } from 'react'
|
||||
import Script from 'next/script'
|
||||
|
||||
export default function Page() {
|
||||
const mapRef = useRef()
|
||||
|
||||
return (
|
||||
<>
|
||||
<div ref={mapRef}></div>
|
||||
<Script
|
||||
id="google-maps"
|
||||
src="https://maps.googleapis.com/maps/api/js"
|
||||
onReady={() => {
|
||||
new google.maps.Map(mapRef.current, {
|
||||
center: { lat: -34.397, lng: 150.644 },
|
||||
zoom: 8,
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
</AppOnly>
|
||||
|
||||
<PagesOnly>
|
||||
|
||||
```jsx
|
||||
import { useRef } from 'react'
|
||||
import Script from 'next/script'
|
||||
|
||||
export default function Page() {
|
||||
const mapRef = useRef()
|
||||
|
||||
return (
|
||||
<>
|
||||
<div ref={mapRef}></div>
|
||||
<Script
|
||||
id="google-maps"
|
||||
src="https://maps.googleapis.com/maps/api/js"
|
||||
onReady={() => {
|
||||
new google.maps.Map(mapRef.current, {
|
||||
center: { lat: -34.397, lng: 150.644 },
|
||||
zoom: 8,
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
</PagesOnly>
|
||||
|
||||
### `onError`
|
||||
|
||||
> **Warning:** `onError` does not yet work with Server Components and can only be used in Client Components. `onError` cannot be used with the `beforeInteractive` loading strategy.
|
||||
|
||||
Sometimes it is helpful to catch when a script fails to load. These errors can be handled with the `onError` property:
|
||||
|
||||
<AppOnly>
|
||||
|
||||
```tsx filename="app/page.tsx" switcher
|
||||
'use client'
|
||||
|
||||
import Script from 'next/script'
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<>
|
||||
<Script
|
||||
src="https://example.com/script.js"
|
||||
onError={(e: Error) => {
|
||||
console.error('Script failed to load', e)
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/page.js" switcher
|
||||
'use client'
|
||||
|
||||
import Script from 'next/script'
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<>
|
||||
<Script
|
||||
src="https://example.com/script.js"
|
||||
onError={(e) => {
|
||||
console.error('Script failed to load', e)
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
</AppOnly>
|
||||
|
||||
<PagesOnly>
|
||||
|
||||
```jsx
|
||||
import Script from 'next/script'
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<>
|
||||
<Script
|
||||
src="https://example.com/script.js"
|
||||
onError={(e: Error) => {
|
||||
console.error('Script failed to load', e)
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
</PagesOnly>
|
||||
|
||||
## Version History
|
||||
|
||||
| Version | Changes |
|
||||
| --------- | ------------------------------------------------------------------------- |
|
||||
| `v13.0.0` | `beforeInteractive` and `afterInteractive` is modified to support `app`. |
|
||||
| `v12.2.4` | `onReady` prop added. |
|
||||
| `v12.2.2` | Allow `next/script` with `beforeInteractive` to be placed in `_document`. |
|
||||
| `v11.0.0` | `next/script` introduced. |
|
||||
Generated
Vendored
+268
@@ -0,0 +1,268 @@
|
||||
---
|
||||
title: favicon, icon, and apple-icon
|
||||
description: API Reference for the Favicon, Icon and Apple Icon file conventions.
|
||||
---
|
||||
|
||||
The `favicon`, `icon`, or `apple-icon` file conventions allow you to set icons for your application.
|
||||
|
||||
They are useful for adding app icons that appear in places like web browser tabs, phone home screens, and search engine results.
|
||||
|
||||
There are two ways to set app icons:
|
||||
|
||||
- [Using image files (.ico, .jpg, .png)](#image-files-ico-jpg-png)
|
||||
- [Using code to generate an icon (.js, .ts, .tsx)](#generate-icons-using-code-js-ts-tsx)
|
||||
|
||||
## Image files (.ico, .jpg, .png)
|
||||
|
||||
Use an image file to set an app icon by placing a `favicon`, `icon`, or `apple-icon` image file within your `/app` directory.
|
||||
The `favicon` image can only be located in the top level of `app/`.
|
||||
|
||||
Next.js will evaluate the file and automatically add the appropriate tags to your app's `<head>` element.
|
||||
|
||||
| File convention | Supported file types | Valid locations |
|
||||
| --------------------------- | --------------------------------------- | --------------- |
|
||||
| [`favicon`](#favicon) | `.ico` | `app/` |
|
||||
| [`icon`](#icon) | `.ico`, `.jpg`, `.jpeg`, `.png`, `.svg` | `app/**/*` |
|
||||
| [`apple-icon`](#apple-icon) | `.jpg`, `.jpeg`, `.png` | `app/**/*` |
|
||||
|
||||
### `favicon`
|
||||
|
||||
Add a `favicon.ico` image file to the root `/app` route segment.
|
||||
|
||||
```html filename="<head> output"
|
||||
<link rel="icon" href="/favicon.ico" sizes="any" />
|
||||
```
|
||||
|
||||
### `icon`
|
||||
|
||||
Add an `icon.(ico|jpg|jpeg|png|svg)` image file.
|
||||
|
||||
```html filename="<head> output"
|
||||
<link
|
||||
rel="icon"
|
||||
href="/icon?<generated>"
|
||||
type="image/<generated>"
|
||||
sizes="<generated>"
|
||||
/>
|
||||
```
|
||||
|
||||
### `apple-icon`
|
||||
|
||||
Add an `apple-icon.(jpg|jpeg|png)` image file.
|
||||
|
||||
```html filename="<head> output"
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
href="/apple-icon?<generated>"
|
||||
type="image/<generated>"
|
||||
sizes="<generated>"
|
||||
/>
|
||||
```
|
||||
|
||||
> **Good to know**:
|
||||
>
|
||||
> - You can set multiple icons by adding a number suffix to the file name. For example, `icon1.png`, `icon2.png`, etc. Numbered files will sort lexically.
|
||||
> - Favicons can only be set in the root `/app` segment. If you need more granularity, you can use [`icon`](#icon).
|
||||
> - The appropriate `<link>` tags and attributes such as `rel`, `href`, `type`, and `sizes` are determined by the icon type and metadata of the evaluated file.
|
||||
> - For example, a 32 by 32px `.png` file will have `type="image/png"` and `sizes="32x32"` attributes.
|
||||
> - `sizes="any"` is added to icons when the extension is `.svg` or the image size of the file is not determined. More details in this [favicon handbook](https://evilmartians.com/chronicles/how-to-favicon-in-2021-six-files-that-fit-most-needs).
|
||||
|
||||
## Generate icons using code (.js, .ts, .tsx)
|
||||
|
||||
In addition to using [literal image files](#image-files-ico-jpg-png), you can programmatically **generate** icons using code.
|
||||
|
||||
Generate an app icon by creating an `icon` or `apple-icon` route that default exports a function.
|
||||
|
||||
| File convention | Supported file types |
|
||||
| --------------- | -------------------- |
|
||||
| `icon` | `.js`, `.ts`, `.tsx` |
|
||||
| `apple-icon` | `.js`, `.ts`, `.tsx` |
|
||||
|
||||
The easiest way to generate an icon is to use the [`ImageResponse`](/docs/app/api-reference/functions/image-response) API from `next/og`.
|
||||
|
||||
```tsx filename="app/icon.tsx" switcher
|
||||
import { ImageResponse } from 'next/og'
|
||||
|
||||
// Image metadata
|
||||
export const size = {
|
||||
width: 32,
|
||||
height: 32,
|
||||
}
|
||||
export const contentType = 'image/png'
|
||||
|
||||
// Image generation
|
||||
export default function Icon() {
|
||||
return new ImageResponse(
|
||||
(
|
||||
// ImageResponse JSX element
|
||||
<div
|
||||
style={{
|
||||
fontSize: 24,
|
||||
background: 'black',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
color: 'white',
|
||||
}}
|
||||
>
|
||||
A
|
||||
</div>
|
||||
),
|
||||
// ImageResponse options
|
||||
{
|
||||
// For convenience, we can re-use the exported icons size metadata
|
||||
// config to also set the ImageResponse's width and height.
|
||||
...size,
|
||||
}
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/icon.js" switcher
|
||||
import { ImageResponse } from 'next/og'
|
||||
|
||||
// Image metadata
|
||||
export const size = {
|
||||
width: 32,
|
||||
height: 32,
|
||||
}
|
||||
export const contentType = 'image/png'
|
||||
|
||||
// Image generation
|
||||
export default function Icon() {
|
||||
return new ImageResponse(
|
||||
(
|
||||
// ImageResponse JSX element
|
||||
<div
|
||||
style={{
|
||||
fontSize: 24,
|
||||
background: 'black',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
color: 'white',
|
||||
}}
|
||||
>
|
||||
A
|
||||
</div>
|
||||
),
|
||||
// ImageResponse options
|
||||
{
|
||||
// For convenience, we can re-use the exported icons size metadata
|
||||
// config to also set the ImageResponse's width and height.
|
||||
...size,
|
||||
}
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```html filename="<head> output"
|
||||
<link rel="icon" href="/icon?<generated>" type="image/png" sizes="32x32" />
|
||||
```
|
||||
|
||||
> **Good to know**:
|
||||
>
|
||||
> - By default, generated icons are [**statically optimized**](/docs/app/glossary#prerendering) (generated at build time and cached) unless they use [Request-time APIs](/docs/app/glossary#request-time-apis) or uncached data.
|
||||
> - You can generate multiple icons in the same file using [`generateImageMetadata`](/docs/app/api-reference/functions/generate-image-metadata).
|
||||
> - You cannot generate a `favicon` icon. Use [`icon`](#icon) or a [favicon.ico](#favicon) file instead.
|
||||
> - App icons are special Route Handlers that are cached by default unless they use a [Request-time API](/docs/app/glossary#request-time-apis) or [dynamic config](/docs/app/guides/caching-without-cache-components#dynamic) option.
|
||||
|
||||
### Props
|
||||
|
||||
The default export function receives the following props:
|
||||
|
||||
#### `params` (optional)
|
||||
|
||||
A promise that resolves to an object containing the [dynamic route parameters](/docs/app/api-reference/file-conventions/dynamic-routes) object from the root segment down to the segment `icon` or `apple-icon` is colocated in.
|
||||
|
||||
> **Good to know**: If you use [`generateImageMetadata`](/docs/app/api-reference/functions/generate-image-metadata), the function will also receive an `id` prop that is a promise resolving to the `id` value from one of the items returned by `generateImageMetadata`.
|
||||
|
||||
```tsx filename="app/shop/[slug]/icon.tsx" switcher
|
||||
export default async function Icon({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ slug: string }>
|
||||
}) {
|
||||
const { slug } = await params
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/shop/[slug]/icon.js" switcher
|
||||
export default async function Icon({ params }) {
|
||||
const { slug } = await params
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
| Route | URL | `params` |
|
||||
| ------------------------------- | ----------- | ---------------------------------- |
|
||||
| `app/shop/icon.js` | `/shop` | `undefined` |
|
||||
| `app/shop/[slug]/icon.js` | `/shop/1` | `Promise<{ slug: '1' }>` |
|
||||
| `app/shop/[tag]/[item]/icon.js` | `/shop/1/2` | `Promise<{ tag: '1', item: '2' }>` |
|
||||
|
||||
### Returns
|
||||
|
||||
The default export function should return a `Blob` | `ArrayBuffer` | `TypedArray` | `DataView` | `ReadableStream` | `Response`.
|
||||
|
||||
> **Good to know**: `ImageResponse` satisfies this return type.
|
||||
|
||||
### Config exports
|
||||
|
||||
You can optionally configure the icon's metadata by exporting `size` and `contentType` variables from the `icon` or `apple-icon` route.
|
||||
|
||||
| Option | Type |
|
||||
| ----------------------------- | --------------------------------------------------------------------------------------------------------------- |
|
||||
| [`size`](#size) | `{ width: number; height: number }` |
|
||||
| [`contentType`](#contenttype) | `string` - [image MIME type](https://developer.mozilla.org/docs/Web/HTTP/Basics_of_HTTP/MIME_types#image_types) |
|
||||
|
||||
#### `size`
|
||||
|
||||
```tsx filename="icon.tsx | apple-icon.tsx" switcher
|
||||
export const size = { width: 32, height: 32 }
|
||||
|
||||
export default function Icon() {}
|
||||
```
|
||||
|
||||
```jsx filename="icon.js | apple-icon.js" switcher
|
||||
export const size = { width: 32, height: 32 }
|
||||
|
||||
export default function Icon() {}
|
||||
```
|
||||
|
||||
```html filename="<head> output"
|
||||
<link rel="icon" sizes="32x32" />
|
||||
```
|
||||
|
||||
#### `contentType`
|
||||
|
||||
```tsx filename="icon.tsx | apple-icon.tsx" switcher
|
||||
export const contentType = 'image/png'
|
||||
|
||||
export default function Icon() {}
|
||||
```
|
||||
|
||||
```jsx filename="icon.js | apple-icon.js" switcher
|
||||
export const contentType = 'image/png'
|
||||
|
||||
export default function Icon() {}
|
||||
```
|
||||
|
||||
```html filename="<head> output"
|
||||
<link rel="icon" type="image/png" />
|
||||
```
|
||||
|
||||
#### Route Segment Config
|
||||
|
||||
`icon` and `apple-icon` are specialized [Route Handlers](/docs/app/api-reference/file-conventions/route) that can use the same [route segment configuration](/docs/app/api-reference/file-conventions/route-segment-config) options as Pages and Layouts.
|
||||
|
||||
## Version History
|
||||
|
||||
| Version | Changes |
|
||||
| --------- | ---------------------------------------------------- |
|
||||
| `v16.0.0` | `params` is now a promise that resolves to an object |
|
||||
| `v13.3.0` | `favicon` `icon` and `apple-icon` introduced |
|
||||
Generated
Vendored
+16
@@ -0,0 +1,16 @@
|
||||
---
|
||||
title: Metadata Files API Reference
|
||||
nav_title: Metadata Files
|
||||
description: API documentation for the metadata file conventions.
|
||||
---
|
||||
|
||||
This section of the docs covers **Metadata file conventions**. File-based metadata can be defined by adding special metadata files to route segments.
|
||||
|
||||
Each file convention can be defined using a static file (e.g. `opengraph-image.jpg`), or a dynamic variant that uses code to generate the file (e.g. `opengraph-image.js`).
|
||||
|
||||
Once a file is defined, Next.js will automatically serve the file (with hashes in production for caching) and update the relevant head elements with the correct metadata, such as the asset's URL, file type, and image size.
|
||||
|
||||
> **Good to know**:
|
||||
>
|
||||
> - Special Route Handlers like [`sitemap.ts`](/docs/app/api-reference/file-conventions/metadata/sitemap), [`opengraph-image.tsx`](/docs/app/api-reference/file-conventions/metadata/opengraph-image), and [`icon.tsx`](/docs/app/api-reference/file-conventions/metadata/app-icons), and other [metadata files](/docs/app/api-reference/file-conventions/metadata) are cached by default.
|
||||
> - If using along with [`proxy.ts`](/docs/app/api-reference/file-conventions/proxy), [configure the matcher](/docs/app/api-reference/file-conventions/proxy#matcher) to exclude the metadata files.
|
||||
Generated
Vendored
+72
@@ -0,0 +1,72 @@
|
||||
---
|
||||
title: manifest.json
|
||||
description: API Reference for manifest.json file.
|
||||
---
|
||||
|
||||
Add or generate a `manifest.(json|webmanifest)` file that matches the [Web Manifest Specification](https://developer.mozilla.org/docs/Web/Manifest) in the **root** of `app` directory to provide information about your web application for the browser.
|
||||
|
||||
## Static Manifest file
|
||||
|
||||
```json filename="app/manifest.json | app/manifest.webmanifest"
|
||||
{
|
||||
"name": "My Next.js Application",
|
||||
"short_name": "Next.js App",
|
||||
"description": "An application built with Next.js",
|
||||
"start_url": "/"
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
## Generate a Manifest file
|
||||
|
||||
Add a `manifest.js` or `manifest.ts` file that returns a [`Manifest` object](#manifest-object).
|
||||
|
||||
> Good to know: `manifest.js` is a special Route Handlers that is cached by default unless it uses a [Request-time API](/docs/app/glossary#request-time-apis) or [dynamic config](/docs/app/guides/caching-without-cache-components#dynamic) option.
|
||||
|
||||
```ts filename="app/manifest.ts" switcher
|
||||
import type { MetadataRoute } from 'next'
|
||||
|
||||
export default function manifest(): MetadataRoute.Manifest {
|
||||
return {
|
||||
name: 'Next.js App',
|
||||
short_name: 'Next.js App',
|
||||
description: 'Next.js App',
|
||||
start_url: '/',
|
||||
display: 'standalone',
|
||||
background_color: '#fff',
|
||||
theme_color: '#fff',
|
||||
icons: [
|
||||
{
|
||||
src: '/favicon.ico',
|
||||
sizes: 'any',
|
||||
type: 'image/x-icon',
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="app/manifest.js" switcher
|
||||
export default function manifest() {
|
||||
return {
|
||||
name: 'Next.js App',
|
||||
short_name: 'Next.js App',
|
||||
description: 'Next.js App',
|
||||
start_url: '/',
|
||||
display: 'standalone',
|
||||
background_color: '#fff',
|
||||
theme_color: '#fff',
|
||||
icons: [
|
||||
{
|
||||
src: '/favicon.ico',
|
||||
sizes: 'any',
|
||||
type: 'image/x-icon',
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Manifest Object
|
||||
|
||||
The manifest object contains an extensive list of options that may be updated due to new web standards. For information on all the current options, refer to the `MetadataRoute.Manifest` type in your code editor if using [TypeScript](/docs/app/api-reference/config/typescript#ide-plugin) or see the [MDN](https://developer.mozilla.org/docs/Web/Manifest) docs.
|
||||
Generated
Vendored
+529
@@ -0,0 +1,529 @@
|
||||
---
|
||||
title: opengraph-image and twitter-image
|
||||
description: API Reference for the Open Graph Image and Twitter Image file conventions.
|
||||
---
|
||||
|
||||
The `opengraph-image` and `twitter-image` file conventions allow you to set Open Graph and Twitter images for a route segment.
|
||||
|
||||
They are useful for setting the images that appear on social networks and messaging apps when a user shares a link to your site.
|
||||
|
||||
There are two ways to set Open Graph and Twitter images:
|
||||
|
||||
- [Using image files (.jpg, .png, .gif)](#image-files-jpg-png-gif)
|
||||
- [Using code to generate images (.js, .ts, .tsx)](#generate-images-using-code-js-ts-tsx)
|
||||
|
||||
## Image files (.jpg, .png, .gif)
|
||||
|
||||
Use an image file to set a route segment's shared image by placing an `opengraph-image` or `twitter-image` image file in the segment.
|
||||
|
||||
Next.js will evaluate the file and automatically add the appropriate tags to your app's `<head>` element.
|
||||
|
||||
| File convention | Supported file types |
|
||||
| ----------------------------------------------- | ------------------------------- |
|
||||
| [`opengraph-image`](#opengraph-image) | `.jpg`, `.jpeg`, `.png`, `.gif` |
|
||||
| [`twitter-image`](#twitter-image) | `.jpg`, `.jpeg`, `.png`, `.gif` |
|
||||
| [`opengraph-image.alt`](#opengraph-imagealttxt) | `.txt` |
|
||||
| [`twitter-image.alt`](#twitter-imagealttxt) | `.txt` |
|
||||
|
||||
> **Good to know**:
|
||||
>
|
||||
> The `twitter-image` file size must not exceed [5MB](https://developer.x.com/en/docs/x-for-websites/cards/overview/summary), and the `opengraph-image` file size must not exceed [8MB](https://developers.facebook.com/docs/sharing/webmasters/images). If the image file size exceeds these limits, the build will fail.
|
||||
|
||||
### `opengraph-image`
|
||||
|
||||
Add an `opengraph-image.(jpg|jpeg|png|gif)` image file to any route segment.
|
||||
|
||||
```html filename="<head> output"
|
||||
<meta property="og:image" content="<generated>" />
|
||||
<meta property="og:image:type" content="<generated>" />
|
||||
<meta property="og:image:width" content="<generated>" />
|
||||
<meta property="og:image:height" content="<generated>" />
|
||||
```
|
||||
|
||||
### `twitter-image`
|
||||
|
||||
Add a `twitter-image.(jpg|jpeg|png|gif)` image file to any route segment.
|
||||
|
||||
```html filename="<head> output"
|
||||
<meta name="twitter:image" content="<generated>" />
|
||||
<meta name="twitter:image:type" content="<generated>" />
|
||||
<meta name="twitter:image:width" content="<generated>" />
|
||||
<meta name="twitter:image:height" content="<generated>" />
|
||||
```
|
||||
|
||||
### `opengraph-image.alt.txt`
|
||||
|
||||
Add an accompanying `opengraph-image.alt.txt` file in the same route segment as the `opengraph-image.(jpg|jpeg|png|gif)` image it's alt text.
|
||||
|
||||
```txt filename="opengraph-image.alt.txt"
|
||||
About Acme
|
||||
```
|
||||
|
||||
```html filename="<head> output"
|
||||
<meta property="og:image:alt" content="About Acme" />
|
||||
```
|
||||
|
||||
### `twitter-image.alt.txt`
|
||||
|
||||
Add an accompanying `twitter-image.alt.txt` file in the same route segment as the `twitter-image.(jpg|jpeg|png|gif)` image it's alt text.
|
||||
|
||||
```txt filename="twitter-image.alt.txt"
|
||||
About Acme
|
||||
```
|
||||
|
||||
```html filename="<head> output"
|
||||
<meta property="twitter:image:alt" content="About Acme" />
|
||||
```
|
||||
|
||||
## Generate images using code (.js, .ts, .tsx)
|
||||
|
||||
In addition to using [literal image files](#image-files-jpg-png-gif), you can programmatically **generate** images using code.
|
||||
|
||||
Generate a route segment's shared image by creating an `opengraph-image` or `twitter-image` route that default exports a function.
|
||||
|
||||
| File convention | Supported file types |
|
||||
| ----------------- | -------------------- |
|
||||
| `opengraph-image` | `.js`, `.ts`, `.tsx` |
|
||||
| `twitter-image` | `.js`, `.ts`, `.tsx` |
|
||||
|
||||
> **Good to know**:
|
||||
>
|
||||
> - By default, generated images are [**statically optimized**](/docs/app/glossary#prerendering) (generated at build time and cached) unless they use [Request-time APIs](/docs/app/glossary#request-time-apis) or uncached data.
|
||||
> - You can generate multiple Images in the same file using [`generateImageMetadata`](/docs/app/api-reference/functions/generate-image-metadata).
|
||||
> - `opengraph-image.js` and `twitter-image.js` are special Route Handlers that is cached by default unless it uses a [Request-time API](/docs/app/glossary#request-time-apis) or [dynamic config](/docs/app/guides/caching-without-cache-components#dynamic) option.
|
||||
|
||||
The easiest way to generate an image is to use the [ImageResponse](/docs/app/api-reference/functions/image-response) API from `next/og`.
|
||||
|
||||
```tsx filename="app/about/opengraph-image.tsx" switcher
|
||||
import { ImageResponse } from 'next/og'
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { join } from 'node:path'
|
||||
|
||||
// Image metadata
|
||||
export const alt = 'About Acme'
|
||||
export const size = {
|
||||
width: 1200,
|
||||
height: 630,
|
||||
}
|
||||
|
||||
export const contentType = 'image/png'
|
||||
|
||||
// Image generation
|
||||
export default async function Image() {
|
||||
// Font loading, process.cwd() is Next.js project directory
|
||||
const interSemiBold = await readFile(
|
||||
join(process.cwd(), 'assets/Inter-SemiBold.ttf')
|
||||
)
|
||||
|
||||
return new ImageResponse(
|
||||
(
|
||||
// ImageResponse JSX element
|
||||
<div
|
||||
style={{
|
||||
fontSize: 128,
|
||||
background: 'white',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
About Acme
|
||||
</div>
|
||||
),
|
||||
// ImageResponse options
|
||||
{
|
||||
// For convenience, we can re-use the exported opengraph-image
|
||||
// size config to also set the ImageResponse's width and height.
|
||||
...size,
|
||||
fonts: [
|
||||
{
|
||||
name: 'Inter',
|
||||
data: interSemiBold,
|
||||
style: 'normal',
|
||||
weight: 400,
|
||||
},
|
||||
],
|
||||
}
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/about/opengraph-image.js" switcher
|
||||
import { ImageResponse } from 'next/og'
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { join } from 'node:path'
|
||||
|
||||
// Image metadata
|
||||
export const alt = 'About Acme'
|
||||
export const size = {
|
||||
width: 1200,
|
||||
height: 630,
|
||||
}
|
||||
|
||||
export const contentType = 'image/png'
|
||||
|
||||
// Image generation
|
||||
export default async function Image() {
|
||||
// Font loading, process.cwd() is Next.js project directory
|
||||
const interSemiBold = await readFile(
|
||||
join(process.cwd(), 'assets/Inter-SemiBold.ttf')
|
||||
)
|
||||
|
||||
return new ImageResponse(
|
||||
(
|
||||
// ImageResponse JSX element
|
||||
<div
|
||||
style={{
|
||||
fontSize: 128,
|
||||
background: 'white',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
About Acme
|
||||
</div>
|
||||
),
|
||||
// ImageResponse options
|
||||
{
|
||||
// For convenience, we can re-use the exported opengraph-image
|
||||
// size config to also set the ImageResponse's width and height.
|
||||
...size,
|
||||
fonts: [
|
||||
{
|
||||
name: 'Inter',
|
||||
data: interSemiBold,
|
||||
style: 'normal',
|
||||
weight: 400,
|
||||
},
|
||||
],
|
||||
}
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```html filename="<head> output"
|
||||
<meta property="og:image" content="<generated>" />
|
||||
<meta property="og:image:alt" content="About Acme" />
|
||||
<meta property="og:image:type" content="image/png" />
|
||||
<meta property="og:image:width" content="1200" />
|
||||
<meta property="og:image:height" content="630" />
|
||||
```
|
||||
|
||||
### Props
|
||||
|
||||
The default export function receives the following props:
|
||||
|
||||
#### `params` (optional)
|
||||
|
||||
A promise that resolves to an object containing the [dynamic route parameters](/docs/app/api-reference/file-conventions/dynamic-routes) object from the root segment down to the segment `opengraph-image` or `twitter-image` is colocated in.
|
||||
|
||||
> **Good to know**: If you use [`generateImageMetadata`](/docs/app/api-reference/functions/generate-image-metadata), the function will also receive an `id` prop that is a promise resolving to the `id` value from one of the items returned by `generateImageMetadata`.
|
||||
|
||||
```tsx filename="app/shop/[slug]/opengraph-image.tsx" switcher
|
||||
export default async function Image({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ slug: string }>
|
||||
}) {
|
||||
const { slug } = await params
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/shop/[slug]/opengraph-image.js" switcher
|
||||
export default async function Image({ params }) {
|
||||
const { slug } = await params
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
| Route | URL | `params` |
|
||||
| ------------------------------------------ | ----------- | ---------------------------------- |
|
||||
| `app/shop/opengraph-image.js` | `/shop` | `undefined` |
|
||||
| `app/shop/[slug]/opengraph-image.js` | `/shop/1` | `Promise<{ slug: '1' }>` |
|
||||
| `app/shop/[tag]/[item]/opengraph-image.js` | `/shop/1/2` | `Promise<{ tag: '1', item: '2' }>` |
|
||||
|
||||
### Returns
|
||||
|
||||
The default export function should return a `Blob` | `ArrayBuffer` | `TypedArray` | `DataView` | `ReadableStream` | `Response`.
|
||||
|
||||
> **Good to know**: `ImageResponse` satisfies this return type.
|
||||
|
||||
### Config exports
|
||||
|
||||
You can optionally configure the image's metadata by exporting `alt`, `size`, and `contentType` variables from `opengraph-image` or `twitter-image` route.
|
||||
|
||||
| Option | Type |
|
||||
| ----------------------------- | --------------------------------------------------------------------------------------------------------------- |
|
||||
| [`alt`](#alt) | `string` |
|
||||
| [`size`](#size) | `{ width: number; height: number }` |
|
||||
| [`contentType`](#contenttype) | `string` - [image MIME type](https://developer.mozilla.org/docs/Web/HTTP/Basics_of_HTTP/MIME_types#image_types) |
|
||||
|
||||
#### `alt`
|
||||
|
||||
```tsx filename="opengraph-image.tsx | twitter-image.tsx" switcher
|
||||
export const alt = 'My images alt text'
|
||||
|
||||
export default function Image() {}
|
||||
```
|
||||
|
||||
```jsx filename="opengraph-image.js | twitter-image.js" switcher
|
||||
export const alt = 'My images alt text'
|
||||
|
||||
export default function Image() {}
|
||||
```
|
||||
|
||||
```html filename="<head> output"
|
||||
<meta property="og:image:alt" content="My images alt text" />
|
||||
```
|
||||
|
||||
#### `size`
|
||||
|
||||
```tsx filename="opengraph-image.tsx | twitter-image.tsx" switcher
|
||||
export const size = { width: 1200, height: 630 }
|
||||
|
||||
export default function Image() {}
|
||||
```
|
||||
|
||||
```jsx filename="opengraph-image.js | twitter-image.js" switcher
|
||||
export const size = { width: 1200, height: 630 }
|
||||
|
||||
export default function Image() {}
|
||||
```
|
||||
|
||||
```html filename="<head> output"
|
||||
<meta property="og:image:width" content="1200" />
|
||||
<meta property="og:image:height" content="630" />
|
||||
```
|
||||
|
||||
#### `contentType`
|
||||
|
||||
```tsx filename="opengraph-image.tsx | twitter-image.tsx" switcher
|
||||
export const contentType = 'image/png'
|
||||
|
||||
export default function Image() {}
|
||||
```
|
||||
|
||||
```jsx filename="opengraph-image.js | twitter-image.js" switcher
|
||||
export const contentType = 'image/png'
|
||||
|
||||
export default function Image() {}
|
||||
```
|
||||
|
||||
```html filename="<head> output"
|
||||
<meta property="og:image:type" content="image/png" />
|
||||
```
|
||||
|
||||
#### Route Segment Config
|
||||
|
||||
`opengraph-image` and `twitter-image` are specialized [Route Handlers](/docs/app/api-reference/file-conventions/route) that can use the same [route segment configuration](/docs/app/api-reference/file-conventions/route-segment-config) options as Pages and Layouts.
|
||||
|
||||
### Examples
|
||||
|
||||
#### Using external data
|
||||
|
||||
This example uses the `params` object and external data to generate the image.
|
||||
|
||||
> **Good to know**:
|
||||
> By default, this generated image will be statically optimized. You can configure the individual `fetch` [`options`](/docs/app/api-reference/functions/fetch) or route segments [options](/docs/app/guides/caching-without-cache-components#route-segment-config-revalidate) to change this behavior.
|
||||
|
||||
```tsx filename="app/posts/[slug]/opengraph-image.tsx" switcher
|
||||
import { ImageResponse } from 'next/og'
|
||||
|
||||
export const alt = 'About Acme'
|
||||
export const size = {
|
||||
width: 1200,
|
||||
height: 630,
|
||||
}
|
||||
export const contentType = 'image/png'
|
||||
|
||||
export default async function Image({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ slug: string }>
|
||||
}) {
|
||||
const { slug } = await params
|
||||
const post = await fetch(`https://.../posts/${slug}`).then((res) =>
|
||||
res.json()
|
||||
)
|
||||
|
||||
return new ImageResponse(
|
||||
(
|
||||
<div
|
||||
style={{
|
||||
fontSize: 48,
|
||||
background: 'white',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
{post.title}
|
||||
</div>
|
||||
),
|
||||
{
|
||||
...size,
|
||||
}
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/posts/[slug]/opengraph-image.js" switcher
|
||||
import { ImageResponse } from 'next/og'
|
||||
|
||||
export const alt = 'About Acme'
|
||||
export const size = {
|
||||
width: 1200,
|
||||
height: 630,
|
||||
}
|
||||
export const contentType = 'image/png'
|
||||
|
||||
export default async function Image({ params }) {
|
||||
const { slug } = await params
|
||||
const post = await fetch(`https://.../posts/${slug}`).then((res) =>
|
||||
res.json()
|
||||
)
|
||||
|
||||
return new ImageResponse(
|
||||
(
|
||||
<div
|
||||
style={{
|
||||
fontSize: 48,
|
||||
background: 'white',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
{post.title}
|
||||
</div>
|
||||
),
|
||||
{
|
||||
...size,
|
||||
}
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
#### Using Node.js runtime with local assets
|
||||
|
||||
These examples use the Node.js runtime to fetch a local image from the file system and pass it to the `<img>` `src` attribute, either as a base64 string or an `ArrayBuffer`. Place the local asset relative to the project root, not the example source file.
|
||||
|
||||
```tsx filename="app/opengraph-image.tsx" switcher
|
||||
import { ImageResponse } from 'next/og'
|
||||
import { join } from 'node:path'
|
||||
import { readFile } from 'node:fs/promises'
|
||||
|
||||
export default async function Image() {
|
||||
const logoData = await readFile(join(process.cwd(), 'logo.png'), 'base64')
|
||||
const logoSrc = `data:image/png;base64,${logoData}`
|
||||
|
||||
return new ImageResponse(
|
||||
(
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<img src={logoSrc} height="100" />
|
||||
</div>
|
||||
)
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/opengraph-image.js" switcher
|
||||
import { ImageResponse } from 'next/og'
|
||||
import { join } from 'node:path'
|
||||
import { readFile } from 'node:fs/promises'
|
||||
|
||||
export default async function Image() {
|
||||
const logoData = await readFile(join(process.cwd(), 'logo.png'), 'base64')
|
||||
const logoSrc = `data:image/png;base64,${logoData}`
|
||||
|
||||
return new ImageResponse(
|
||||
(
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<img src={logoSrc} height="100" />
|
||||
</div>
|
||||
)
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
Passing an `ArrayBuffer` to the `src` attribute of an `<img>` element is not part of the HTML spec. The rendering engine used by `next/og` supports it, but because TypeScript definitions follow the spec, you need a `@ts-expect-error` directive or similar to use this [feature](https://github.com/vercel/satori/issues/606#issuecomment-2144000453).
|
||||
|
||||
```tsx filename="app/opengraph-image.tsx" switcher
|
||||
import { ImageResponse } from 'next/og'
|
||||
import { join } from 'node:path'
|
||||
import { readFile } from 'node:fs/promises'
|
||||
|
||||
export default async function Image() {
|
||||
const logoData = await readFile(join(process.cwd(), 'logo.png'))
|
||||
const logoSrc = Uint8Array.from(logoData).buffer
|
||||
|
||||
return new ImageResponse(
|
||||
(
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
{/* @ts-expect-error Satori accepts ArrayBuffer/typed arrays for <img src> at runtime */}
|
||||
<img src={logoSrc} height="100" />
|
||||
</div>
|
||||
)
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/opengraph-image.js" switcher
|
||||
import { ImageResponse } from 'next/og'
|
||||
import { join } from 'node:path'
|
||||
import { readFile } from 'node:fs/promises'
|
||||
|
||||
export default async function Image() {
|
||||
const logoData = await readFile(join(process.cwd(), 'logo.png'))
|
||||
const logoSrc = Uint8Array.from(logoData).buffer
|
||||
|
||||
return new ImageResponse(
|
||||
(
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<img src={logoSrc} height="100" />
|
||||
</div>
|
||||
)
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Version History
|
||||
|
||||
| Version | Changes |
|
||||
| --------- | ---------------------------------------------------- |
|
||||
| `v16.0.0` | `params` is now a promise that resolves to an object |
|
||||
| `v13.3.0` | `opengraph-image` and `twitter-image` introduced. |
|
||||
Generated
Vendored
+148
@@ -0,0 +1,148 @@
|
||||
---
|
||||
title: robots.txt
|
||||
description: API Reference for robots.txt file.
|
||||
---
|
||||
|
||||
Add or generate a `robots.txt` file that matches the [Robots Exclusion Standard](https://en.wikipedia.org/wiki/Robots.txt#Standard) in the **root** of `app` directory to tell search engine crawlers which URLs they can access on your site.
|
||||
|
||||
## Static `robots.txt`
|
||||
|
||||
```txt filename="app/robots.txt"
|
||||
User-Agent: *
|
||||
Allow: /
|
||||
Disallow: /private/
|
||||
|
||||
Sitemap: https://acme.com/sitemap.xml
|
||||
```
|
||||
|
||||
## Generate a Robots file
|
||||
|
||||
Add a `robots.js` or `robots.ts` file that returns a [`Robots` object](#robots-object).
|
||||
|
||||
> **Good to know**: `robots.js` is a special Route Handler that is cached by default unless it uses a [Request-time API](/docs/app/glossary#request-time-apis) or [dynamic config](/docs/app/guides/caching-without-cache-components#dynamic) option.
|
||||
|
||||
```ts filename="app/robots.ts" switcher
|
||||
import type { MetadataRoute } from 'next'
|
||||
|
||||
export default function robots(): MetadataRoute.Robots {
|
||||
return {
|
||||
rules: {
|
||||
userAgent: '*',
|
||||
allow: '/',
|
||||
disallow: '/private/',
|
||||
},
|
||||
sitemap: 'https://acme.com/sitemap.xml',
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="app/robots.js" switcher
|
||||
export default function robots() {
|
||||
return {
|
||||
rules: {
|
||||
userAgent: '*',
|
||||
allow: '/',
|
||||
disallow: '/private/',
|
||||
},
|
||||
sitemap: 'https://acme.com/sitemap.xml',
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```txt
|
||||
User-Agent: *
|
||||
Allow: /
|
||||
Disallow: /private/
|
||||
|
||||
Sitemap: https://acme.com/sitemap.xml
|
||||
```
|
||||
|
||||
### Customizing specific user agents
|
||||
|
||||
You can customize how individual search engine bots crawl your site by passing an array of user agents to the `rules` property. For example:
|
||||
|
||||
```ts filename="app/robots.ts" switcher
|
||||
import type { MetadataRoute } from 'next'
|
||||
|
||||
export default function robots(): MetadataRoute.Robots {
|
||||
return {
|
||||
rules: [
|
||||
{
|
||||
userAgent: 'Googlebot',
|
||||
allow: ['/'],
|
||||
disallow: '/private/',
|
||||
},
|
||||
{
|
||||
userAgent: ['Applebot', 'Bingbot'],
|
||||
disallow: ['/'],
|
||||
},
|
||||
],
|
||||
sitemap: 'https://acme.com/sitemap.xml',
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="app/robots.js" switcher
|
||||
export default function robots() {
|
||||
return {
|
||||
rules: [
|
||||
{
|
||||
userAgent: 'Googlebot',
|
||||
allow: ['/'],
|
||||
disallow: ['/private/'],
|
||||
},
|
||||
{
|
||||
userAgent: ['Applebot', 'Bingbot'],
|
||||
disallow: ['/'],
|
||||
},
|
||||
],
|
||||
sitemap: 'https://acme.com/sitemap.xml',
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```txt
|
||||
User-Agent: Googlebot
|
||||
Allow: /
|
||||
Disallow: /private/
|
||||
|
||||
User-Agent: Applebot
|
||||
Disallow: /
|
||||
|
||||
User-Agent: Bingbot
|
||||
Disallow: /
|
||||
|
||||
Sitemap: https://acme.com/sitemap.xml
|
||||
```
|
||||
|
||||
### Robots object
|
||||
|
||||
```tsx
|
||||
type Robots = {
|
||||
rules:
|
||||
| {
|
||||
userAgent?: string | string[]
|
||||
allow?: string | string[]
|
||||
disallow?: string | string[]
|
||||
crawlDelay?: number
|
||||
}
|
||||
| Array<{
|
||||
userAgent: string | string[]
|
||||
allow?: string | string[]
|
||||
disallow?: string | string[]
|
||||
crawlDelay?: number
|
||||
}>
|
||||
sitemap?: string | string[]
|
||||
host?: string
|
||||
}
|
||||
```
|
||||
|
||||
## Version History
|
||||
|
||||
| Version | Changes |
|
||||
| --------- | -------------------- |
|
||||
| `v13.3.0` | `robots` introduced. |
|
||||
Generated
Vendored
+426
@@ -0,0 +1,426 @@
|
||||
---
|
||||
title: sitemap.xml
|
||||
description: API Reference for the sitemap.xml file.
|
||||
related:
|
||||
title: Next Steps
|
||||
description: Learn how to use the generateSitemaps function.
|
||||
links:
|
||||
- app/api-reference/functions/generate-sitemaps
|
||||
---
|
||||
|
||||
`sitemap.(xml|js|ts)` is a special file that matches the [Sitemaps XML format](https://www.sitemaps.org/protocol.html) to help search engine crawlers index your site more efficiently.
|
||||
|
||||
### Sitemap files (.xml)
|
||||
|
||||
For smaller applications, you can create a `sitemap.xml` file and place it in the root of your `app` directory.
|
||||
|
||||
```xml filename="app/sitemap.xml"
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
<url>
|
||||
<loc>https://acme.com</loc>
|
||||
<lastmod>2023-04-06T15:02:24.021Z</lastmod>
|
||||
<changefreq>yearly</changefreq>
|
||||
<priority>1</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://acme.com/about</loc>
|
||||
<lastmod>2023-04-06T15:02:24.021Z</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://acme.com/blog</loc>
|
||||
<lastmod>2023-04-06T15:02:24.021Z</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.5</priority>
|
||||
</url>
|
||||
</urlset>
|
||||
```
|
||||
|
||||
### Generating a sitemap using code (.js, .ts)
|
||||
|
||||
You can use the `sitemap.(js|ts)` file convention to programmatically **generate** a sitemap by exporting a default function that returns an array of URLs. If using TypeScript, a [`Sitemap`](#returns) type is available.
|
||||
|
||||
> **Good to know**: `sitemap.js` is a special Route Handler that is cached by default unless it uses a [Request-time API](/docs/app/glossary#request-time-apis) or [dynamic config](/docs/app/guides/caching-without-cache-components#dynamic) option.
|
||||
|
||||
```ts filename="app/sitemap.ts" switcher
|
||||
import type { MetadataRoute } from 'next'
|
||||
|
||||
export default function sitemap(): MetadataRoute.Sitemap {
|
||||
return [
|
||||
{
|
||||
url: 'https://acme.com',
|
||||
lastModified: new Date(),
|
||||
changeFrequency: 'yearly',
|
||||
priority: 1,
|
||||
},
|
||||
{
|
||||
url: 'https://acme.com/about',
|
||||
lastModified: new Date(),
|
||||
changeFrequency: 'monthly',
|
||||
priority: 0.8,
|
||||
},
|
||||
{
|
||||
url: 'https://acme.com/blog',
|
||||
lastModified: new Date(),
|
||||
changeFrequency: 'weekly',
|
||||
priority: 0.5,
|
||||
},
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="app/sitemap.js" switcher
|
||||
export default function sitemap() {
|
||||
return [
|
||||
{
|
||||
url: 'https://acme.com',
|
||||
lastModified: new Date(),
|
||||
changeFrequency: 'yearly',
|
||||
priority: 1,
|
||||
},
|
||||
{
|
||||
url: 'https://acme.com/about',
|
||||
lastModified: new Date(),
|
||||
changeFrequency: 'monthly',
|
||||
priority: 0.8,
|
||||
},
|
||||
{
|
||||
url: 'https://acme.com/blog',
|
||||
lastModified: new Date(),
|
||||
changeFrequency: 'weekly',
|
||||
priority: 0.5,
|
||||
},
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```xml filename="acme.com/sitemap.xml"
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
<url>
|
||||
<loc>https://acme.com</loc>
|
||||
<lastmod>2023-04-06T15:02:24.021Z</lastmod>
|
||||
<changefreq>yearly</changefreq>
|
||||
<priority>1</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://acme.com/about</loc>
|
||||
<lastmod>2023-04-06T15:02:24.021Z</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://acme.com/blog</loc>
|
||||
<lastmod>2023-04-06T15:02:24.021Z</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.5</priority>
|
||||
</url>
|
||||
</urlset>
|
||||
```
|
||||
|
||||
### Image Sitemaps
|
||||
|
||||
You can use `images` property to create image sitemaps. Learn more details in the [Google Developer Docs](https://developers.google.com/search/docs/crawling-indexing/sitemaps/image-sitemaps).
|
||||
|
||||
```ts filename="app/sitemap.ts" switcher
|
||||
import type { MetadataRoute } from 'next'
|
||||
|
||||
export default function sitemap(): MetadataRoute.Sitemap {
|
||||
return [
|
||||
{
|
||||
url: 'https://example.com',
|
||||
lastModified: '2021-01-01',
|
||||
changeFrequency: 'weekly',
|
||||
priority: 0.5,
|
||||
images: ['https://example.com/image.jpg'],
|
||||
},
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```xml filename="acme.com/sitemap.xml"
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset
|
||||
xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
|
||||
xmlns:image="http://www.google.com/schemas/sitemap-image/1.1"
|
||||
>
|
||||
<url>
|
||||
<loc>https://example.com</loc>
|
||||
<image:image>
|
||||
<image:loc>https://example.com/image.jpg</image:loc>
|
||||
</image:image>
|
||||
<lastmod>2021-01-01</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.5</priority>
|
||||
</url>
|
||||
</urlset>
|
||||
```
|
||||
|
||||
### Video Sitemaps
|
||||
|
||||
You can use `videos` property to create video sitemaps. Learn more details in the [Google Developer Docs](https://developers.google.com/search/docs/crawling-indexing/sitemaps/video-sitemaps).
|
||||
|
||||
```ts filename="app/sitemap.ts" switcher
|
||||
import type { MetadataRoute } from 'next'
|
||||
|
||||
export default function sitemap(): MetadataRoute.Sitemap {
|
||||
return [
|
||||
{
|
||||
url: 'https://example.com',
|
||||
lastModified: '2021-01-01',
|
||||
changeFrequency: 'weekly',
|
||||
priority: 0.5,
|
||||
videos: [
|
||||
{
|
||||
title: 'example',
|
||||
thumbnail_loc: 'https://example.com/image.jpg',
|
||||
description: 'this is the description',
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```xml filename="acme.com/sitemap.xml"
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset
|
||||
xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
|
||||
xmlns:video="http://www.google.com/schemas/sitemap-video/1.1"
|
||||
>
|
||||
<url>
|
||||
<loc>https://example.com</loc>
|
||||
<video:video>
|
||||
<video:title>example</video:title>
|
||||
<video:thumbnail_loc>https://example.com/image.jpg</video:thumbnail_loc>
|
||||
<video:description>this is the description</video:description>
|
||||
</video:video>
|
||||
<lastmod>2021-01-01</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.5</priority>
|
||||
</url>
|
||||
</urlset>
|
||||
```
|
||||
|
||||
### Generate a localized Sitemap
|
||||
|
||||
```ts filename="app/sitemap.ts" switcher
|
||||
import type { MetadataRoute } from 'next'
|
||||
|
||||
export default function sitemap(): MetadataRoute.Sitemap {
|
||||
return [
|
||||
{
|
||||
url: 'https://acme.com',
|
||||
lastModified: new Date(),
|
||||
alternates: {
|
||||
languages: {
|
||||
es: 'https://acme.com/es',
|
||||
de: 'https://acme.com/de',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
url: 'https://acme.com/about',
|
||||
lastModified: new Date(),
|
||||
alternates: {
|
||||
languages: {
|
||||
es: 'https://acme.com/es/about',
|
||||
de: 'https://acme.com/de/about',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
url: 'https://acme.com/blog',
|
||||
lastModified: new Date(),
|
||||
alternates: {
|
||||
languages: {
|
||||
es: 'https://acme.com/es/blog',
|
||||
de: 'https://acme.com/de/blog',
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="app/sitemap.js" switcher
|
||||
export default function sitemap() {
|
||||
return [
|
||||
{
|
||||
url: 'https://acme.com',
|
||||
lastModified: new Date(),
|
||||
alternates: {
|
||||
languages: {
|
||||
es: 'https://acme.com/es',
|
||||
de: 'https://acme.com/de',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
url: 'https://acme.com/about',
|
||||
lastModified: new Date(),
|
||||
alternates: {
|
||||
languages: {
|
||||
es: 'https://acme.com/es/about',
|
||||
de: 'https://acme.com/de/about',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
url: 'https://acme.com/blog',
|
||||
lastModified: new Date(),
|
||||
alternates: {
|
||||
languages: {
|
||||
es: 'https://acme.com/es/blog',
|
||||
de: 'https://acme.com/de/blog',
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```xml filename="acme.com/sitemap.xml"
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml">
|
||||
<url>
|
||||
<loc>https://acme.com</loc>
|
||||
<xhtml:link
|
||||
rel="alternate"
|
||||
hreflang="es"
|
||||
href="https://acme.com/es"/>
|
||||
<xhtml:link
|
||||
rel="alternate"
|
||||
hreflang="de"
|
||||
href="https://acme.com/de"/>
|
||||
<lastmod>2023-04-06T15:02:24.021Z</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://acme.com/about</loc>
|
||||
<xhtml:link
|
||||
rel="alternate"
|
||||
hreflang="es"
|
||||
href="https://acme.com/es/about"/>
|
||||
<xhtml:link
|
||||
rel="alternate"
|
||||
hreflang="de"
|
||||
href="https://acme.com/de/about"/>
|
||||
<lastmod>2023-04-06T15:02:24.021Z</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://acme.com/blog</loc>
|
||||
<xhtml:link
|
||||
rel="alternate"
|
||||
hreflang="es"
|
||||
href="https://acme.com/es/blog"/>
|
||||
<xhtml:link
|
||||
rel="alternate"
|
||||
hreflang="de"
|
||||
href="https://acme.com/de/blog"/>
|
||||
<lastmod>2023-04-06T15:02:24.021Z</lastmod>
|
||||
</url>
|
||||
</urlset>
|
||||
```
|
||||
|
||||
### Generating multiple sitemaps
|
||||
|
||||
While a single sitemap will work for most applications. For large web applications, you may need to split a sitemap into multiple files.
|
||||
|
||||
There are two ways you can create multiple sitemaps:
|
||||
|
||||
- By nesting `sitemap.(xml|js|ts)` inside multiple route segments e.g. `app/sitemap.xml` and `app/products/sitemap.xml`.
|
||||
- By using the [`generateSitemaps`](/docs/app/api-reference/functions/generate-sitemaps) function.
|
||||
|
||||
For example, to split a sitemap using `generateSitemaps`, return an array of objects with the sitemap `id`. Then, use the `id` to generate the unique sitemaps.
|
||||
|
||||
```ts filename="app/product/sitemap.ts" switcher
|
||||
import type { MetadataRoute } from 'next'
|
||||
import { BASE_URL } from '@/app/lib/constants'
|
||||
|
||||
export async function generateSitemaps() {
|
||||
// Fetch the total number of products and calculate the number of sitemaps needed
|
||||
return [{ id: 0 }, { id: 1 }, { id: 2 }, { id: 3 }]
|
||||
}
|
||||
|
||||
export default async function sitemap(props: {
|
||||
id: Promise<string>
|
||||
}): Promise<MetadataRoute.Sitemap> {
|
||||
const id = await props.id
|
||||
// Google's limit is 50,000 URLs per sitemap
|
||||
const start = id * 50000
|
||||
const end = start + 50000
|
||||
const products = await getProducts(
|
||||
`SELECT id, date FROM products WHERE id BETWEEN ${start} AND ${end}`
|
||||
)
|
||||
return products.map((product) => ({
|
||||
url: `${BASE_URL}/product/${product.id}`,
|
||||
lastModified: product.date,
|
||||
}))
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="app/product/sitemap.js" switcher
|
||||
import { BASE_URL } from '@/app/lib/constants'
|
||||
|
||||
export async function generateSitemaps() {
|
||||
// Fetch the total number of products and calculate the number of sitemaps needed
|
||||
return [{ id: 0 }, { id: 1 }, { id: 2 }, { id: 3 }]
|
||||
}
|
||||
|
||||
export default async function sitemap(props) {
|
||||
const id = await props.id
|
||||
// Google's limit is 50,000 URLs per sitemap
|
||||
const start = id * 50000
|
||||
const end = start + 50000
|
||||
const products = await getProducts(
|
||||
`SELECT id, date FROM products WHERE id BETWEEN ${start} AND ${end}`
|
||||
)
|
||||
return products.map((product) => ({
|
||||
url: `${BASE_URL}/product/${product.id}`,
|
||||
lastModified: product.date,
|
||||
}))
|
||||
}
|
||||
```
|
||||
|
||||
Your generated sitemaps will be available at `/.../sitemap/[id]`. For example, `/product/sitemap/1.xml`.
|
||||
|
||||
See the [`generateSitemaps` API reference](/docs/app/api-reference/functions/generate-sitemaps) for more information.
|
||||
|
||||
## Returns
|
||||
|
||||
The default function exported from `sitemap.(xml|ts|js)` should return an array of objects with the following properties:
|
||||
|
||||
```tsx
|
||||
type Sitemap = Array<{
|
||||
url: string
|
||||
lastModified?: string | Date
|
||||
changeFrequency?:
|
||||
| 'always'
|
||||
| 'hourly'
|
||||
| 'daily'
|
||||
| 'weekly'
|
||||
| 'monthly'
|
||||
| 'yearly'
|
||||
| 'never'
|
||||
priority?: number
|
||||
alternates?: {
|
||||
languages?: Languages<string>
|
||||
}
|
||||
}>
|
||||
```
|
||||
|
||||
## Version History
|
||||
|
||||
| Version | Changes |
|
||||
| ---------- | ------------------------------------------------------------ |
|
||||
| `v16.0.0` | `id` is now a promise that resolves to a `string`. |
|
||||
| `v14.2.0` | Add localizations support. |
|
||||
| `v13.4.14` | Add `changeFrequency` and `priority` attributes to sitemaps. |
|
||||
| `v13.3.0` | `sitemap` introduced. |
|
||||
Generated
Vendored
+22
@@ -0,0 +1,22 @@
|
||||
---
|
||||
title: dynamicParams
|
||||
description: API reference for the dynamicParams route segment config option.
|
||||
---
|
||||
|
||||
The `dynamicParams` option allows you to control what happens when a dynamic segment is visited that was not generated with [generateStaticParams](/docs/app/api-reference/functions/generate-static-params).
|
||||
|
||||
```tsx filename="layout.tsx | page.tsx" switcher
|
||||
export const dynamicParams = true // true | false
|
||||
```
|
||||
|
||||
```js filename="layout.js | page.js | route.js" switcher
|
||||
export const dynamicParams = true // true | false
|
||||
```
|
||||
|
||||
- **`true`** (default): Dynamic route segments not included in `generateStaticParams` are generated at request time.
|
||||
- **`false`**: Dynamic route segments not included in `generateStaticParams` will return a 404.
|
||||
|
||||
> **Good to know**:
|
||||
>
|
||||
> - This option replaces the `fallback: true | false | blocking` option of `getStaticPaths` in the `pages` directory.
|
||||
> - `dynamicParams` is not available when [Cache Components](/docs/app/api-reference/config/next-config-js/cacheComponents) is enabled.
|
||||
Generated
Vendored
+21
@@ -0,0 +1,21 @@
|
||||
---
|
||||
title: Route Segment Config
|
||||
description: Learn about how to configure options for Next.js route segments.
|
||||
---
|
||||
|
||||
The Route Segment Config options allow you to configure the behavior of a [Page](/docs/app/api-reference/file-conventions/page), [Layout](/docs/app/api-reference/file-conventions/layout), or [Route Handler](/docs/app/api-reference/file-conventions/route) by directly exporting the following variables:
|
||||
|
||||
| Option | Type | Default |
|
||||
| -------------------------------------------------------------------------------------------------- | ---------------------------------------------------- | -------------------------- |
|
||||
| [`dynamicParams`](/docs/app/api-reference/file-conventions/route-segment-config/dynamicParams) | `boolean` | `true` |
|
||||
| [`runtime`](/docs/app/api-reference/file-conventions/route-segment-config/runtime) | `'nodejs' \| 'edge'` | `'nodejs'` |
|
||||
| [`preferredRegion`](/docs/app/api-reference/file-conventions/route-segment-config/preferredRegion) | `'auto' \| 'global' \| 'home' \| string \| string[]` | `'auto'` |
|
||||
| [`maxDuration`](/docs/app/api-reference/file-conventions/route-segment-config/maxDuration) | `number` | Set by deployment platform |
|
||||
|
||||
## Version History
|
||||
|
||||
| Version | |
|
||||
| ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `v16.0.0` | `dynamic`, `dynamicParams`, `revalidate`, and `fetchCache` removed when [Cache Components](/docs/app/api-reference/config/next-config-js/cacheComponents) is enabled. See [Caching and Revalidating (Previous Model)](/docs/app/guides/caching-without-cache-components#route-segment-config). |
|
||||
| `v16.0.0` | `export const experimental_ppr = true` removed. A [codemod](/docs/app/guides/upgrading/codemods#remove-experimental_ppr-route-segment-config-from-app-router-pages-and-layouts) is available. |
|
||||
| `v15.0.0-RC` | `export const runtime = "experimental-edge"` deprecated. A [codemod](/docs/app/guides/upgrading/codemods#transform-app-router-route-segment-config-runtime-value-from-experimental-edge-to-edge) is available. |
|
||||
Generated
Vendored
+138
@@ -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) |
|
||||
Generated
Vendored
+24
@@ -0,0 +1,24 @@
|
||||
---
|
||||
title: maxDuration
|
||||
description: API reference for the maxDuration route segment config option.
|
||||
---
|
||||
|
||||
The `maxDuration` option allows you to set the maximum execution time (in seconds) for server-side logic in a route segment. Deployment platforms can use `maxDuration` from the Next.js build output to add specific execution limits.
|
||||
|
||||
```tsx filename="layout.tsx | page.tsx | route.ts" switcher
|
||||
export const maxDuration = 5
|
||||
```
|
||||
|
||||
```js filename="layout.js | page.js | route.js" switcher
|
||||
export const maxDuration = 5
|
||||
```
|
||||
|
||||
## Server Actions
|
||||
|
||||
If using [Server Actions](/docs/app/getting-started/mutating-data), set the `maxDuration` at the page level to change the default timeout of all Server Actions used on the page.
|
||||
|
||||
## Version History
|
||||
|
||||
| Version | Changes |
|
||||
| ---------- | ------------------------- |
|
||||
| `v13.4.10` | `maxDuration` introduced. |
|
||||
Generated
Vendored
+33
@@ -0,0 +1,33 @@
|
||||
---
|
||||
title: preferredRegion
|
||||
description: API reference for the preferredRegion route segment config option.
|
||||
---
|
||||
|
||||
The `preferredRegion` option allows you to specify the preferred deployment region for a route segment. This value is passed to your deployment platform.
|
||||
|
||||
```tsx filename="layout.tsx | page.tsx | route.ts" switcher
|
||||
export const preferredRegion = // string || string[]
|
||||
```
|
||||
|
||||
```js filename="layout.js | page.js | route.js" switcher
|
||||
export const preferredRegion = // string || string[]
|
||||
```
|
||||
|
||||
- **`string`**: Deploy the route to a specific region. Available region codes are platform-specific. For example, `'iad1'`.
|
||||
- **`string[]`**: Deploy the route to multiple specific regions. The route is deployed to **all** listed regions, not a single one chosen from the list. For example, `['iad1', 'sfo1']`.
|
||||
|
||||
> **Good to know**:
|
||||
>
|
||||
> - If a `preferredRegion` is not specified, it will inherit the option of the nearest parent layout. The root layout defaults to `'auto'`.
|
||||
> - A child segment's value overrides the parent, values are not merged.
|
||||
> - Next.js passes the region values through to the deployment platform. The exact behavior and available region codes are platform-specific. Refer to your deployment platform's documentation for supported values.
|
||||
|
||||
## Vercel
|
||||
|
||||
If deploying Next.js on Vercel, regions are only supported if `export const runtime = 'edge'` is set. The following options can be passed:
|
||||
|
||||
- **`'auto'`** (default): Uses the default region.
|
||||
- **`'global'`**: Prefer deploying the route to all availableregions.
|
||||
- **`'home'`**: Prefer deploying the route to the home region.
|
||||
|
||||
If an unsupported value is passed, an error will be thrown.
|
||||
Generated
Vendored
+24
@@ -0,0 +1,24 @@
|
||||
---
|
||||
title: runtime
|
||||
description: API reference for the runtime route segment config option.
|
||||
---
|
||||
|
||||
The `runtime` option allows you to select the JavaScript runtime used for rendering your route.
|
||||
|
||||
```tsx filename="layout.tsx | page.tsx | route.ts" switcher
|
||||
export const runtime = 'nodejs'
|
||||
// 'nodejs' | 'edge'
|
||||
```
|
||||
|
||||
```js filename="layout.js | page.js | route.js" switcher
|
||||
export const runtime = 'nodejs'
|
||||
// 'nodejs' | 'edge'
|
||||
```
|
||||
|
||||
- **`'nodejs'`** (default)
|
||||
- **`'edge'`**
|
||||
|
||||
> **Good to know**:
|
||||
>
|
||||
> - Using `runtime: 'edge'` is **not supported** for Cache Components.
|
||||
> - This option cannot be used in [Proxy](/docs/app/api-reference/file-conventions/proxy).
|
||||
Generated
Vendored
+66
@@ -0,0 +1,66 @@
|
||||
---
|
||||
title: default.js
|
||||
description: API Reference for the default.js file.
|
||||
related:
|
||||
title: Learn more about Parallel Routes
|
||||
links:
|
||||
- app/api-reference/file-conventions/parallel-routes
|
||||
---
|
||||
|
||||
The `default.js` file is used to render a fallback within [Parallel Routes](/docs/app/api-reference/file-conventions/parallel-routes) when Next.js cannot recover a [slot's](/docs/app/api-reference/file-conventions/parallel-routes#slots) active state after a full-page load.
|
||||
|
||||
During [soft navigation](/docs/app/getting-started/linking-and-navigating#client-side-transitions), Next.js keeps track of the active _state_ (subpage) for each slot. However, for hard navigations (full-page load), Next.js cannot recover the active state. In this case, a `default.js` file can be rendered for subpages that don't match the current URL.
|
||||
|
||||
Consider the following folder structure. The `@team` slot has a `settings` page, but `@analytics` does not.
|
||||
|
||||
<Image
|
||||
alt="Parallel Routes unmatched routes"
|
||||
srcLight="/docs/light/parallel-routes-unmatched-routes.png"
|
||||
srcDark="/docs/dark/parallel-routes-unmatched-routes.png"
|
||||
width="1600"
|
||||
height="930"
|
||||
/>
|
||||
|
||||
When navigating to `/settings`, the `@team` slot will render the `settings` page while maintaining the currently active page for the `@analytics` slot.
|
||||
|
||||
On refresh, Next.js will render a `default.js` for `@analytics`. If `default.js` doesn't exist, an error is returned for named slots (`@team`, `@analytics`, etc) and requires you to define a `default.js` in order to continue. If you want to preserve the old behavior of returning a 404 in these situations, you can create a `default.js` that contains:
|
||||
|
||||
```tsx filename="app/@team/default.js"
|
||||
import { notFound } from 'next/navigation'
|
||||
|
||||
export default function Default() {
|
||||
notFound()
|
||||
}
|
||||
```
|
||||
|
||||
Additionally, since `children` is an implicit slot, you also need to create a `default.js` file to render a fallback for `children` when Next.js cannot recover the active state of the parent page. If you don't create a `default.js` for the `children` slot, it will return a 404 page for the route.
|
||||
|
||||
## Reference
|
||||
|
||||
### `params` (optional)
|
||||
|
||||
A promise that resolves to an object containing the [dynamic route parameters](/docs/app/api-reference/file-conventions/dynamic-routes) from the root segment down to the slot's subpages. For example:
|
||||
|
||||
```tsx filename="app/[artist]/@sidebar/default.js" switcher
|
||||
export default async function Default({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ artist: string }>
|
||||
}) {
|
||||
const { artist } = await params
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/[artist]/@sidebar/default.js" switcher
|
||||
export default async function Default({ params }) {
|
||||
const { artist } = await params
|
||||
}
|
||||
```
|
||||
|
||||
| Example | URL | `params` |
|
||||
| ------------------------------------------ | ------------ | -------------------------------------------- |
|
||||
| `app/[artist]/@sidebar/default.js` | `/zack` | `Promise<{ artist: 'zack' }>` |
|
||||
| `app/[artist]/[album]/@sidebar/default.js` | `/zack/next` | `Promise<{ artist: 'zack', album: 'next' }>` |
|
||||
|
||||
- Since the `params` prop is a promise. You must use `async/await` or React's [`use`](https://react.dev/reference/react/use) function to access the values.
|
||||
- In version 14 and earlier, `params` was a synchronous prop. To help with backwards compatibility, you can still access it synchronously in Next.js 15, but this behavior will be deprecated in the future.
|
||||
Generated
Vendored
+375
@@ -0,0 +1,375 @@
|
||||
---
|
||||
title: Dynamic Route Segments
|
||||
nav_title: Dynamic Segments
|
||||
description: Dynamic Route Segments can be used to programmatically generate route segments from dynamic data.
|
||||
related:
|
||||
title: Next Steps
|
||||
description: For more information on what to do next, we recommend the following sections
|
||||
links:
|
||||
- app/api-reference/functions/generate-static-params
|
||||
---
|
||||
|
||||
When you don't know the exact route segment names ahead of time and want to create routes from dynamic data, you can use Dynamic Segments that are filled in at request time or prerendered at build time.
|
||||
|
||||
## Convention
|
||||
|
||||
A Dynamic Segment can be created by wrapping a folder's name in square brackets: `[folderName]`. For example, a blog could include the following route `app/blog/[slug]/page.js` where `[slug]` is the Dynamic Segment for blog posts.
|
||||
|
||||
```tsx filename="app/blog/[slug]/page.tsx" switcher
|
||||
export default async function Page({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ slug: string }>
|
||||
}) {
|
||||
const { slug } = await params
|
||||
return <div>My Post: {slug}</div>
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/blog/[slug]/page.js" switcher
|
||||
export default async function Page({ params }) {
|
||||
const { slug } = await params
|
||||
return <div>My Post: {slug}</div>
|
||||
}
|
||||
```
|
||||
|
||||
Dynamic Segments are passed as the `params` prop to [`layout`](/docs/app/api-reference/file-conventions/layout), [`page`](/docs/app/api-reference/file-conventions/page), [`route`](/docs/app/api-reference/file-conventions/route), and [`generateMetadata`](/docs/app/api-reference/functions/generate-metadata#generatemetadata-function) functions.
|
||||
|
||||
| Route | Example URL | `params` |
|
||||
| ------------------------- | ----------- | --------------- |
|
||||
| `app/blog/[slug]/page.js` | `/blog/a` | `{ slug: 'a' }` |
|
||||
| `app/blog/[slug]/page.js` | `/blog/b` | `{ slug: 'b' }` |
|
||||
| `app/blog/[slug]/page.js` | `/blog/c` | `{ slug: 'c' }` |
|
||||
|
||||
### In Client Components
|
||||
|
||||
In a Client Component **page**, dynamic segments from props can be accessed using the [`use`](https://react.dev/reference/react/use) API.
|
||||
|
||||
```tsx filename="app/blog/[slug]/page.tsx" switcher
|
||||
'use client'
|
||||
import { use } from 'react'
|
||||
|
||||
export default function BlogPostPage({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ slug: string }>
|
||||
}) {
|
||||
const { slug } = use(params)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>{slug}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/blog/[slug]/page.js" switcher
|
||||
'use client'
|
||||
import { use } from 'react'
|
||||
|
||||
export default function BlogPostPage({ params }) {
|
||||
const { slug } = use(params)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>{slug}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
Alternatively Client Components can use the [`useParams`](/docs/app/api-reference/functions/use-params) hook to access the `params` anywhere in the Client Component tree.
|
||||
|
||||
### Catch-all Segments
|
||||
|
||||
Dynamic Segments can be extended to **catch-all** subsequent segments by adding an ellipsis inside the brackets `[...folderName]`.
|
||||
|
||||
For example, `app/shop/[...slug]/page.js` will match `/shop/clothes`, but also `/shop/clothes/tops`, `/shop/clothes/tops/t-shirts`, and so on.
|
||||
|
||||
| Route | Example URL | `params` |
|
||||
| ---------------------------- | ------------- | --------------------------- |
|
||||
| `app/shop/[...slug]/page.js` | `/shop/a` | `{ slug: ['a'] }` |
|
||||
| `app/shop/[...slug]/page.js` | `/shop/a/b` | `{ slug: ['a', 'b'] }` |
|
||||
| `app/shop/[...slug]/page.js` | `/shop/a/b/c` | `{ slug: ['a', 'b', 'c'] }` |
|
||||
|
||||
### Optional Catch-all Segments
|
||||
|
||||
Catch-all Segments can be made **optional** by including the parameter in double square brackets: `[[...folderName]]`.
|
||||
|
||||
For example, `app/shop/[[...slug]]/page.js` will **also** match `/shop`, in addition to `/shop/clothes`, `/shop/clothes/tops`, `/shop/clothes/tops/t-shirts`.
|
||||
|
||||
The difference between **catch-all** and **optional catch-all** segments is that with optional, the route without the parameter is also matched (`/shop` in the example above).
|
||||
|
||||
| Route | Example URL | `params` |
|
||||
| ------------------------------ | ------------- | --------------------------- |
|
||||
| `app/shop/[[...slug]]/page.js` | `/shop` | `{ slug: undefined }` |
|
||||
| `app/shop/[[...slug]]/page.js` | `/shop/a` | `{ slug: ['a'] }` |
|
||||
| `app/shop/[[...slug]]/page.js` | `/shop/a/b` | `{ slug: ['a', 'b'] }` |
|
||||
| `app/shop/[[...slug]]/page.js` | `/shop/a/b/c` | `{ slug: ['a', 'b', 'c'] }` |
|
||||
|
||||
### TypeScript
|
||||
|
||||
When using TypeScript, you can add types for `params` depending on your configured route segment — use [`PageProps<'/route'>`](/docs/app/api-reference/file-conventions/page#page-props-helper), [`LayoutProps<'/route'>`](/docs/app/api-reference/file-conventions/layout#layout-props-helper), or [`RouteContext<'/route'>`](/docs/app/api-reference/file-conventions/route#route-context-helper) to type `params` in `page`, `layout`, and `route` respectively.
|
||||
|
||||
Route `params` values are typed as `string`, `string[]`, or `undefined` (for optional catch-all segments), because their values aren't known until runtime. Users can enter any URL into the address bar, and these broad types help ensure that your application code handles all these possible cases.
|
||||
|
||||
| Route | `params` Type Definition |
|
||||
| ----------------------------------- | ---------------------------------------- |
|
||||
| `app/blog/[slug]/page.js` | `{ slug: string }` |
|
||||
| `app/shop/[...slug]/page.js` | `{ slug: string[] }` |
|
||||
| `app/shop/[[...slug]]/page.js` | `{ slug?: string[] }` |
|
||||
| `app/[categoryId]/[itemId]/page.js` | `{ categoryId: string, itemId: string }` |
|
||||
|
||||
If you're working on a route where `params` can only have a fixed number of valid values, such as a `[locale]` param with a known set of language codes, you can use runtime validation to handle any invalid params a user may enter, and let the rest of your application work with the narrower type from your known set.
|
||||
|
||||
```tsx filename="/app/[locale]/page.tsx"
|
||||
import { notFound } from 'next/navigation'
|
||||
import type { Locale } from '@i18n/types'
|
||||
import { isValidLocale } from '@i18n/utils'
|
||||
|
||||
function assertValidLocale(value: string): asserts value is Locale {
|
||||
if (!isValidLocale(value)) notFound()
|
||||
}
|
||||
|
||||
export default async function Page(props: PageProps<'/[locale]'>) {
|
||||
const { locale } = await props.params // locale is typed as string
|
||||
assertValidLocale(locale)
|
||||
// locale is now typed as Locale
|
||||
}
|
||||
```
|
||||
|
||||
## Behavior
|
||||
|
||||
- Since the `params` prop is a promise. You must use `async`/`await` or React's use function to access the values.
|
||||
- In version 14 and earlier, `params` was a synchronous prop. To help with backwards compatibility, you can still access it synchronously in Next.js 15, but this behavior will be deprecated in the future.
|
||||
|
||||
### With Cache Components
|
||||
|
||||
When using [Cache Components](/docs/app/getting-started/caching) with dynamic route segments, how you handle params depends on whether you use [`generateStaticParams`](/docs/app/api-reference/functions/generate-static-params).
|
||||
|
||||
Without `generateStaticParams`, param values are unknown during prerendering, making params runtime data. You must wrap param access in `<Suspense>` boundaries to provide fallback UI.
|
||||
|
||||
With `generateStaticParams`, you provide sample param values that can be used at build time. The build process validates that dynamic content and other runtime APIs are correctly handled, then generates static HTML files for the samples. Pages rendered with runtime params are saved to disk after a successful first request.
|
||||
|
||||
The sections below demonstrate both patterns.
|
||||
|
||||
#### Without `generateStaticParams`
|
||||
|
||||
All params are runtime data. Param access must be wrapped by Suspense fallback UI. Next.js generates a static shell at build time, and content loads on each request.
|
||||
|
||||
> **Good to know**: You can also use [`loading.tsx`](/docs/app/api-reference/file-conventions/loading) for page-level fallback UI.
|
||||
|
||||
```tsx filename="app/blog/[slug]/page.tsx"
|
||||
import { Suspense } from 'react'
|
||||
|
||||
export default function Page({ params }: PageProps<'/blog/[slug]'>) {
|
||||
return (
|
||||
<div>
|
||||
<h1>Blog Post</h1>
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
{params.then(({ slug }) => (
|
||||
<Content slug={slug} />
|
||||
))}
|
||||
</Suspense>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
async function Content({ slug }: { slug: string }) {
|
||||
const res = await fetch(`https://api.vercel.app/blog/${slug}`)
|
||||
const post = await res.json()
|
||||
|
||||
return (
|
||||
<article>
|
||||
<h2>{post.title}</h2>
|
||||
<p>{post.content}</p>
|
||||
</article>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
#### With `generateStaticParams`
|
||||
|
||||
Provide params ahead of time to prerender pages at build time. You can prerender all routes or a subset depending on your needs.
|
||||
|
||||
During the build process, the route is executed with each sample param to collect the HTML result. If dynamic content or runtime data are accessed incorrectly, the build will fail.
|
||||
|
||||
```tsx filename="app/blog/[slug]/page.tsx" highlight={3-5,8,19}
|
||||
import { Suspense } from 'react'
|
||||
|
||||
export async function generateStaticParams() {
|
||||
return [{ slug: '1' }, { slug: '2' }, { slug: '3' }]
|
||||
}
|
||||
|
||||
export default async function Page({ params }: PageProps<'/blog/[slug]'>) {
|
||||
const { slug } = await params
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Blog Post</h1>
|
||||
<Content slug={slug} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
async function Content({ slug }: { slug: string }) {
|
||||
const post = await getPost(slug)
|
||||
return (
|
||||
<article>
|
||||
<h2>{post.title}</h2>
|
||||
<p>{post.content}</p>
|
||||
</article>
|
||||
)
|
||||
}
|
||||
|
||||
async function getPost(slug: string) {
|
||||
'use cache'
|
||||
const res = await fetch(`https://api.vercel.app/blog/${slug}`)
|
||||
return res.json()
|
||||
}
|
||||
```
|
||||
|
||||
Build-time validation only covers code paths that execute with the sample params. If your route has conditional logic that accesses runtime APIs for certain param values not in your samples, those branches won't be validated at build time:
|
||||
|
||||
```tsx filename="app/blog/[slug]/page.tsx"
|
||||
import { cookies } from 'next/headers'
|
||||
|
||||
export async function generateStaticParams() {
|
||||
return [{ slug: 'public-post' }, { slug: 'hello-world' }]
|
||||
}
|
||||
|
||||
export default async function Page({ params }: PageProps<'/blog/[slug]'>) {
|
||||
const { slug } = await params
|
||||
|
||||
if (slug.startsWith('private-')) {
|
||||
// This branch is never executed at build time
|
||||
// Runtime requests for 'private-*' slugs will error
|
||||
return <PrivatePost slug={slug} />
|
||||
}
|
||||
|
||||
return <PublicPost slug={slug} />
|
||||
}
|
||||
|
||||
async function PrivatePost({ slug }: { slug: string }) {
|
||||
const token = (await cookies()).get('token')
|
||||
// ... fetch and render private post using token for auth
|
||||
}
|
||||
```
|
||||
|
||||
For runtime params not returned by `generateStaticParams`, validation occurs during the first request. In the example above, requests for slugs starting with `private-` will fail because `PrivatePost` accesses `cookies()` without a Suspense boundary. Other runtime params that don't hit the conditional branch will render successfully and be saved to disk for subsequent requests.
|
||||
|
||||
To fix this, wrap `PrivatePost` with Suspense:
|
||||
|
||||
```tsx filename="app/blog/[slug]/page.tsx" highlight={13-15}
|
||||
import { Suspense } from 'react'
|
||||
import { cookies } from 'next/headers'
|
||||
|
||||
export async function generateStaticParams() {
|
||||
return [{ slug: 'public-post' }, { slug: 'hello-world' }]
|
||||
}
|
||||
|
||||
export default async function Page({ params }: PageProps<'/blog/[slug]'>) {
|
||||
const { slug } = await params
|
||||
|
||||
if (slug.startsWith('private-')) {
|
||||
return (
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
<PrivatePost slug={slug} />
|
||||
</Suspense>
|
||||
)
|
||||
}
|
||||
|
||||
return <PublicPost slug={slug} />
|
||||
}
|
||||
|
||||
async function PrivatePost({ slug }: { slug: string }) {
|
||||
const token = (await cookies()).get('token')
|
||||
// ... fetch and render private post using token for auth
|
||||
}
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### With `generateStaticParams`
|
||||
|
||||
The [`generateStaticParams`](/docs/app/api-reference/functions/generate-static-params) function can be used to [statically generate](/docs/app/glossary#prerendering) routes at build time instead of on-demand at request time.
|
||||
|
||||
```tsx filename="app/blog/[slug]/page.tsx" switcher
|
||||
export async function generateStaticParams() {
|
||||
const posts = await fetch('https://.../posts').then((res) => res.json())
|
||||
|
||||
return posts.map((post) => ({
|
||||
slug: post.slug,
|
||||
}))
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/blog/[slug]/page.js" switcher
|
||||
export async function generateStaticParams() {
|
||||
const posts = await fetch('https://.../posts').then((res) => res.json())
|
||||
|
||||
return posts.map((post) => ({
|
||||
slug: post.slug,
|
||||
}))
|
||||
}
|
||||
```
|
||||
|
||||
When using `fetch` inside the `generateStaticParams` function, the requests are [automatically deduplicated](/docs/app/glossary#memoization). This avoids multiple network calls for the same data Layouts, Pages, and other `generateStaticParams` functions, speeding up build time.
|
||||
|
||||
### Dynamic GET Route Handlers with `generateStaticParams`
|
||||
|
||||
`generateStaticParams` also works with dynamic [Route Handlers](/docs/app/api-reference/file-conventions/route) to statically generate API responses at build time:
|
||||
|
||||
```ts filename="app/api/posts/[id]/route.ts" switcher
|
||||
export async function generateStaticParams() {
|
||||
const posts: { id: number }[] = await fetch(
|
||||
'https://api.vercel.app/blog'
|
||||
).then((res) => res.json())
|
||||
|
||||
return posts.map((post) => ({
|
||||
id: `${post.id}`,
|
||||
}))
|
||||
}
|
||||
|
||||
export async function GET(
|
||||
request: Request,
|
||||
{ params }: RouteContext<'/api/posts/[id]'>
|
||||
) {
|
||||
const { id } = await params
|
||||
const res = await fetch(`https://api.vercel.app/blog/${id}`)
|
||||
|
||||
if (!res.ok) {
|
||||
return Response.json({ error: 'Post not found' }, { status: 404 })
|
||||
}
|
||||
|
||||
const post = await res.json()
|
||||
return Response.json(post)
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="app/api/posts/[id]/route.js" switcher
|
||||
export async function generateStaticParams() {
|
||||
const posts = await fetch('https://api.vercel.app/blog').then((res) =>
|
||||
res.json()
|
||||
)
|
||||
|
||||
return posts.map((post) => ({
|
||||
id: `${post.id}`,
|
||||
}))
|
||||
}
|
||||
|
||||
export async function GET(request, { params }) {
|
||||
const { id } = await params
|
||||
const res = await fetch(`https://api.vercel.app/blog/${id}`)
|
||||
|
||||
if (!res.ok) {
|
||||
return Response.json({ error: 'Post not found' }, { status: 404 })
|
||||
}
|
||||
|
||||
const post = await res.json()
|
||||
return Response.json(post)
|
||||
}
|
||||
```
|
||||
|
||||
In this example, route handlers for all blog post IDs returned by `generateStaticParams` will be statically generated at build time. Requests to other IDs will be handled dynamically at request time.
|
||||
Generated
Vendored
+332
@@ -0,0 +1,332 @@
|
||||
---
|
||||
title: error.js
|
||||
description: API reference for the error.js special file.
|
||||
related:
|
||||
title: Learn more about error handling
|
||||
links:
|
||||
- app/getting-started/error-handling
|
||||
---
|
||||
|
||||
An **error** file allows you to handle unexpected runtime errors and display fallback UI.
|
||||
|
||||
<Image
|
||||
alt="error.js special file"
|
||||
srcLight="/docs/light/error-special-file.png"
|
||||
srcDark="/docs/dark/error-special-file.png"
|
||||
width="1600"
|
||||
height="606"
|
||||
/>
|
||||
|
||||
```tsx filename="app/dashboard/error.tsx" switcher
|
||||
'use client' // Error boundaries must be Client Components
|
||||
|
||||
import { useEffect } from 'react'
|
||||
|
||||
export default function Error({
|
||||
error,
|
||||
unstable_retry,
|
||||
}: {
|
||||
error: Error & { digest?: string }
|
||||
unstable_retry: () => void
|
||||
}) {
|
||||
useEffect(() => {
|
||||
// Log the error to an error reporting service
|
||||
console.error(error)
|
||||
}, [error])
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>Something went wrong!</h2>
|
||||
<button
|
||||
onClick={
|
||||
// Attempt to recover by re-fetching and re-rendering the segment
|
||||
() => unstable_retry()
|
||||
}
|
||||
>
|
||||
Try again
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/dashboard/error.js" switcher
|
||||
'use client' // Error boundaries must be Client Components
|
||||
|
||||
import { useEffect } from 'react'
|
||||
|
||||
export default function Error({ error, unstable_retry }) {
|
||||
useEffect(() => {
|
||||
// Log the error to an error reporting service
|
||||
console.error(error)
|
||||
}, [error])
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>Something went wrong!</h2>
|
||||
<button
|
||||
onClick={
|
||||
// Attempt to recover by re-fetching and re-rendering the segment
|
||||
() => unstable_retry()
|
||||
}
|
||||
>
|
||||
Try again
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
`error.js` wraps a route segment and its nested children in a [React Error Boundary](https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary). When an error throws within the boundary, the `error` component shows as the fallback UI.
|
||||
|
||||
<Image
|
||||
alt="How error.js works"
|
||||
srcLight="/docs/light/error-overview.png"
|
||||
srcDark="/docs/dark/error-overview.png"
|
||||
width="1600"
|
||||
height="903"
|
||||
/>
|
||||
|
||||
> **Good to know**:
|
||||
>
|
||||
> - The [React DevTools](https://react.dev/learn/react-developer-tools) allow you to toggle error boundaries to test error states.
|
||||
> - If you want errors to bubble up to the parent error boundary, you can `throw` when rendering the `error` component.
|
||||
> - For component-level error recovery that aren't tied to route segments like [`error.js`](/docs/app/api-reference/file-conventions/error), use the [`unstable_catchError`](/docs/app/api-reference/functions/catchError) function.
|
||||
|
||||
In the [component hierarchy](/docs/app/getting-started/project-structure#component-hierarchy), `error.js` wraps `loading.js`, `not-found.js`, `page.js`, and nested `layout.js` files in a React error boundary. It does **not** wrap the `layout.js` or `template.js` above it in the same segment. To handle errors in the root layout, use [`global-error.js`](/docs/app/api-reference/file-conventions/error#global-error).
|
||||
|
||||
## Reference
|
||||
|
||||
### Props
|
||||
|
||||
#### `error`
|
||||
|
||||
An instance of an [`Error`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error) object forwarded to the `error.js` Client Component.
|
||||
|
||||
> **Good to know:** During development, the `Error` object forwarded to the client will be serialized and include the `message` of the original error for easier debugging. However, **this behavior is different in production** to avoid leaking potentially sensitive details included in the error to the client.
|
||||
|
||||
#### `error.message`
|
||||
|
||||
- Errors forwarded from Client Components show the original `Error` message.
|
||||
- Errors forwarded from Server Components show a generic message with an identifier. This is to prevent leaking sensitive details. You can use the identifier, under `errors.digest`, to match the corresponding server-side logs.
|
||||
|
||||
#### `error.digest`
|
||||
|
||||
An automatically generated hash of the error thrown. It can be used to match the corresponding error in server-side logs.
|
||||
|
||||
#### `unstable_retry`
|
||||
|
||||
The cause of an error can sometimes be temporary. In these cases, trying again might resolve the issue.
|
||||
|
||||
An error component can use the `unstable_retry()` function to prompt the user to attempt to recover from the error. When executed, the function will try to re-fetch and re-render the error boundary's children. If successful, the fallback error component is replaced with the result of the re-render.
|
||||
|
||||
```tsx filename="app/dashboard/error.tsx" switcher
|
||||
'use client' // Error boundaries must be Client Components
|
||||
|
||||
export default function Error({
|
||||
error,
|
||||
unstable_retry,
|
||||
}: {
|
||||
error: Error & { digest?: string }
|
||||
unstable_retry: () => void
|
||||
}) {
|
||||
return (
|
||||
<div>
|
||||
<h2>Something went wrong!</h2>
|
||||
<button onClick={() => unstable_retry()}>Try again</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/dashboard/error.js" switcher
|
||||
'use client' // Error boundaries must be Client Components
|
||||
|
||||
export default function Error({ error, unstable_retry }) {
|
||||
return (
|
||||
<div>
|
||||
<h2>Something went wrong!</h2>
|
||||
<button onClick={() => unstable_retry()}>Try again</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
#### `reset`
|
||||
|
||||
In most cases, you should use [`unstable_retry()`](#unstable_retry) instead. However, if you have a specific reason to clear the error state and re-render the error boundary's children without re-fetching the contents, you can use the `reset()` function.
|
||||
|
||||
## Examples
|
||||
|
||||
### Global Error
|
||||
|
||||
While less common, you can handle errors in the root layout or template using `global-error.jsx`, located in the root app directory, even when leveraging [internationalization](/docs/app/guides/internationalization). Global error UI must define its own `<html>` and `<body>` tags, global styles, fonts, or other dependencies that your error page requires. This file replaces the root layout or template when active.
|
||||
|
||||
> **Good to know**: Error boundaries must be [Client Components](/docs/app/getting-started/server-and-client-components#using-client-components), which means that [`metadata` and `generateMetadata`](/docs/app/getting-started/metadata-and-og-images) exports are not supported in `global-error.jsx`. As an alternative, you can use the React [`<title>`](https://react.dev/reference/react-dom/components/title) component.
|
||||
|
||||
```tsx filename="app/global-error.tsx" switcher
|
||||
'use client' // Error boundaries must be Client Components
|
||||
|
||||
export default function GlobalError({
|
||||
error,
|
||||
unstable_retry,
|
||||
}: {
|
||||
error: Error & { digest?: string }
|
||||
unstable_retry: () => void
|
||||
}) {
|
||||
return (
|
||||
// global-error must include html and body tags
|
||||
<html>
|
||||
<body>
|
||||
<h2>Something went wrong!</h2>
|
||||
<button onClick={() => unstable_retry()}>Try again</button>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/global-error.js" switcher
|
||||
'use client' // Error boundaries must be Client Components
|
||||
|
||||
export default function GlobalError({ error, unstable_retry }) {
|
||||
return (
|
||||
// global-error must include html and body tags
|
||||
<html>
|
||||
<body>
|
||||
<h2>Something went wrong!</h2>
|
||||
<button onClick={() => unstable_retry()}>Try again</button>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Graceful error recovery with a custom error boundary
|
||||
|
||||
When rendering fails on the client, it can be useful to show the last known server rendered UI for a better user experience.
|
||||
|
||||
The `GracefullyDegradingErrorBoundary` is an example of a custom error boundary that captures and preserves the current HTML before an error occurs. If a rendering error happens, it re-renders the captured HTML and displays a persistent notification bar to inform the user.
|
||||
|
||||
```tsx filename="app/dashboard/error.tsx" switcher
|
||||
'use client'
|
||||
|
||||
import React, { Component, ErrorInfo, ReactNode } from 'react'
|
||||
|
||||
interface ErrorBoundaryProps {
|
||||
children: ReactNode
|
||||
onError?: (error: Error, errorInfo: ErrorInfo) => void
|
||||
}
|
||||
|
||||
interface ErrorBoundaryState {
|
||||
hasError: boolean
|
||||
}
|
||||
|
||||
export class GracefullyDegradingErrorBoundary extends Component<
|
||||
ErrorBoundaryProps,
|
||||
ErrorBoundaryState
|
||||
> {
|
||||
private contentRef: React.RefObject<HTMLDivElement | null>
|
||||
|
||||
constructor(props: ErrorBoundaryProps) {
|
||||
super(props)
|
||||
this.state = { hasError: false }
|
||||
this.contentRef = React.createRef()
|
||||
}
|
||||
|
||||
static getDerivedStateFromError(_: Error): ErrorBoundaryState {
|
||||
return { hasError: true }
|
||||
}
|
||||
|
||||
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
|
||||
if (this.props.onError) {
|
||||
this.props.onError(error, errorInfo)
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.hasError) {
|
||||
// Render the current HTML content without hydration
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
ref={this.contentRef}
|
||||
suppressHydrationWarning
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: this.contentRef.current?.innerHTML || '',
|
||||
}}
|
||||
/>
|
||||
<div className="fixed bottom-0 left-0 right-0 bg-red-600 text-white py-4 px-6 text-center">
|
||||
<p className="font-semibold">
|
||||
An error occurred during page rendering
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
return <div ref={this.contentRef}>{this.props.children}</div>
|
||||
}
|
||||
}
|
||||
|
||||
export default GracefullyDegradingErrorBoundary
|
||||
```
|
||||
|
||||
```jsx filename="app/dashboard/error.js" switcher
|
||||
'use client'
|
||||
|
||||
import React, { Component, createRef } from 'react'
|
||||
|
||||
class GracefullyDegradingErrorBoundary extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = { hasError: false }
|
||||
this.contentRef = createRef()
|
||||
}
|
||||
|
||||
static getDerivedStateFromError(_) {
|
||||
return { hasError: true }
|
||||
}
|
||||
|
||||
componentDidCatch(error, errorInfo) {
|
||||
if (this.props.onError) {
|
||||
this.props.onError(error, errorInfo)
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.hasError) {
|
||||
// Render the current HTML content without hydration
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
ref={this.contentRef}
|
||||
suppressHydrationWarning
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: this.contentRef.current?.innerHTML || '',
|
||||
}}
|
||||
/>
|
||||
<div className="fixed bottom-0 left-0 right-0 bg-red-600 text-white py-4 px-6 text-center">
|
||||
<p className="font-semibold">
|
||||
An error occurred during page rendering
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
return <div ref={this.contentRef}>{this.props.children}</div>
|
||||
}
|
||||
}
|
||||
|
||||
export default GracefullyDegradingErrorBoundary
|
||||
```
|
||||
|
||||
## Version History
|
||||
|
||||
| Version | Changes |
|
||||
| --------- | ------------------------------------------- |
|
||||
| `v16.2.0` | `unstable_retry` prop added. |
|
||||
| `v15.2.0` | Also display `global-error` in development. |
|
||||
| `v13.1.0` | `global-error` introduced. |
|
||||
| `v13.0.0` | `error` introduced. |
|
||||
Generated
Vendored
+50
@@ -0,0 +1,50 @@
|
||||
---
|
||||
title: forbidden.js
|
||||
description: API reference for the forbidden.js special file.
|
||||
related:
|
||||
links:
|
||||
- app/api-reference/functions/forbidden
|
||||
version: experimental
|
||||
---
|
||||
|
||||
The **forbidden** file is used to render UI when the [`forbidden`](/docs/app/api-reference/functions/forbidden) function is invoked during authentication. Along with allowing you to customize the UI, Next.js will return a `403` status code.
|
||||
|
||||
```tsx filename="app/forbidden.tsx" switcher
|
||||
import Link from 'next/link'
|
||||
|
||||
export default function Forbidden() {
|
||||
return (
|
||||
<div>
|
||||
<h2>Forbidden</h2>
|
||||
<p>You are not authorized to access this resource.</p>
|
||||
<Link href="/">Return Home</Link>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/forbidden.jsx" switcher
|
||||
import Link from 'next/link'
|
||||
|
||||
export default function Forbidden() {
|
||||
return (
|
||||
<div>
|
||||
<h2>Forbidden</h2>
|
||||
<p>You are not authorized to access this resource.</p>
|
||||
<Link href="/">Return Home</Link>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Reference
|
||||
|
||||
### Props
|
||||
|
||||
`forbidden.js` components do not accept any props.
|
||||
|
||||
## Version History
|
||||
|
||||
| Version | Changes |
|
||||
| --------- | -------------------------- |
|
||||
| `v15.1.0` | `forbidden.js` introduced. |
|
||||
Generated
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
---
|
||||
title: File-system conventions
|
||||
description: API Reference for Next.js file-system conventions.
|
||||
---
|
||||
Generated
Vendored
+224
@@ -0,0 +1,224 @@
|
||||
---
|
||||
title: instrumentation-client.js
|
||||
description: Learn how to add client-side instrumentation to track and monitor your Next.js application's frontend performance.
|
||||
---
|
||||
|
||||
The `instrumentation-client.js|ts` file allows you to add monitoring, analytics code, and other side-effects that run before your application becomes interactive. This is useful for setting up performance tracking, error monitoring, polyfills, or any other client-side observability tools.
|
||||
|
||||
To use it, place the file in the **root** of your application or inside a `src` folder.
|
||||
|
||||
## Usage
|
||||
|
||||
Unlike [server-side instrumentation](/docs/app/guides/instrumentation), you do not need to export any specific functions. You can write your monitoring code directly in the file:
|
||||
|
||||
```ts filename="instrumentation-client.ts" switcher
|
||||
// Set up performance monitoring
|
||||
performance.mark('app-init')
|
||||
|
||||
// Initialize analytics
|
||||
console.log('Analytics initialized')
|
||||
|
||||
// Set up error tracking
|
||||
window.addEventListener('error', (event) => {
|
||||
// Send to your error tracking service
|
||||
reportError(event.error)
|
||||
})
|
||||
```
|
||||
|
||||
```js filename="instrumentation-client.js" switcher
|
||||
// Set up performance monitoring
|
||||
performance.mark('app-init')
|
||||
|
||||
// Initialize analytics
|
||||
console.log('Analytics initialized')
|
||||
|
||||
// Set up error tracking
|
||||
window.addEventListener('error', (event) => {
|
||||
// Send to your error tracking service
|
||||
reportError(event.error)
|
||||
})
|
||||
```
|
||||
|
||||
**Error handling:** Implement try-catch blocks around your instrumentation code to ensure robust monitoring. This prevents individual tracking failures from affecting other instrumentation features.
|
||||
|
||||
## Router navigation tracking
|
||||
|
||||
You can export an `onRouterTransitionStart` function to receive notifications when navigation begins:
|
||||
|
||||
```ts filename="instrumentation-client.ts" switcher
|
||||
performance.mark('app-init')
|
||||
|
||||
export function onRouterTransitionStart(
|
||||
url: string,
|
||||
navigationType: 'push' | 'replace' | 'traverse'
|
||||
) {
|
||||
console.log(`Navigation started: ${navigationType} to ${url}`)
|
||||
performance.mark(`nav-start-${Date.now()}`)
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="instrumentation-client.js" switcher
|
||||
performance.mark('app-init')
|
||||
|
||||
export function onRouterTransitionStart(url, navigationType) {
|
||||
console.log(`Navigation started: ${navigationType} to ${url}`)
|
||||
performance.mark(`nav-start-${Date.now()}`)
|
||||
}
|
||||
```
|
||||
|
||||
The `onRouterTransitionStart` function receives two parameters:
|
||||
|
||||
- `url: string` - The URL being navigated to
|
||||
- `navigationType: 'push' | 'replace' | 'traverse'` - The type of navigation
|
||||
|
||||
## Performance considerations
|
||||
|
||||
Keep instrumentation code lightweight.
|
||||
|
||||
Next.js monitors initialization time in development and will log warnings if it takes longer than 16ms, which could impact smooth page loading.
|
||||
|
||||
## Execution timing
|
||||
|
||||
The `instrumentation-client.js` file executes at a specific point in the application lifecycle:
|
||||
|
||||
1. **After** the HTML document is loaded
|
||||
2. **Before** React hydration begins
|
||||
3. **Before** user interactions are possible
|
||||
|
||||
This timing makes it ideal for setting up error tracking, analytics, and performance monitoring that needs to capture early application lifecycle events.
|
||||
|
||||
## Examples
|
||||
|
||||
### Error tracking
|
||||
|
||||
Initialize error tracking before React starts and add navigation breadcrumbs for better debugging context.
|
||||
|
||||
```ts filename="instrumentation-client.ts" switcher
|
||||
import Monitor from './lib/monitoring'
|
||||
|
||||
Monitor.initialize()
|
||||
|
||||
export function onRouterTransitionStart(url: string) {
|
||||
Monitor.pushEvent({
|
||||
message: `Navigation to ${url}`,
|
||||
category: 'navigation',
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="instrumentation-client.js" switcher
|
||||
import Monitor from './lib/monitoring'
|
||||
|
||||
Monitor.initialize()
|
||||
|
||||
export function onRouterTransitionStart(url) {
|
||||
Monitor.pushEvent({
|
||||
message: `Navigation to ${url}`,
|
||||
category: 'navigation',
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
### Analytics tracking
|
||||
|
||||
Initialize analytics and track navigation events with detailed metadata for user behavior analysis.
|
||||
|
||||
```ts filename="instrumentation-client.ts" switcher
|
||||
import { analytics } from './lib/analytics'
|
||||
|
||||
analytics.init()
|
||||
|
||||
export function onRouterTransitionStart(url: string, navigationType: string) {
|
||||
analytics.track('page_navigation', {
|
||||
url,
|
||||
type: navigationType,
|
||||
timestamp: Date.now(),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="instrumentation-client.js" switcher
|
||||
import { analytics } from './lib/analytics'
|
||||
|
||||
analytics.init()
|
||||
|
||||
export function onRouterTransitionStart(url, navigationType) {
|
||||
analytics.track('page_navigation', {
|
||||
url,
|
||||
type: navigationType,
|
||||
timestamp: Date.now(),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
### Performance monitoring
|
||||
|
||||
Track Time to Interactive and navigation performance using the Performance Observer API and performance marks.
|
||||
|
||||
```ts filename="instrumentation-client.ts" switcher
|
||||
const startTime = performance.now()
|
||||
|
||||
const observer = new PerformanceObserver(
|
||||
(list: PerformanceObserverEntryList) => {
|
||||
for (const entry of list.getEntries()) {
|
||||
if (entry instanceof PerformanceNavigationTiming) {
|
||||
console.log('Time to Interactive:', entry.loadEventEnd - startTime)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
observer.observe({ entryTypes: ['navigation'] })
|
||||
|
||||
export function onRouterTransitionStart(url: string) {
|
||||
performance.mark(`nav-start-${url}`)
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="instrumentation-client.js" switcher
|
||||
const startTime = performance.now()
|
||||
|
||||
const observer = new PerformanceObserver((list) => {
|
||||
for (const entry of list.getEntries()) {
|
||||
if (entry instanceof PerformanceNavigationTiming) {
|
||||
console.log('Time to Interactive:', entry.loadEventEnd - startTime)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
observer.observe({ entryTypes: ['navigation'] })
|
||||
|
||||
export function onRouterTransitionStart(url) {
|
||||
performance.mark(`nav-start-${url}`)
|
||||
}
|
||||
```
|
||||
|
||||
### Polyfills
|
||||
|
||||
Load polyfills before application code runs. Use static imports for immediate loading and dynamic imports for conditional loading based on feature detection.
|
||||
|
||||
```ts filename="instrumentation-client.ts" switcher
|
||||
import './lib/polyfills'
|
||||
|
||||
if (!window.ResizeObserver) {
|
||||
import('./lib/polyfills/resize-observer').then((mod) => {
|
||||
window.ResizeObserver = mod.default
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="instrumentation-client.js" switcher
|
||||
import './lib/polyfills'
|
||||
|
||||
if (!window.ResizeObserver) {
|
||||
import('./lib/polyfills/resize-observer').then((mod) => {
|
||||
window.ResizeObserver = mod.default
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## Version history
|
||||
|
||||
| Version | Changes |
|
||||
| ------- | ----------------------------------- |
|
||||
| `v15.3` | `instrumentation-client` introduced |
|
||||
Generated
Vendored
+139
@@ -0,0 +1,139 @@
|
||||
---
|
||||
title: instrumentation.js
|
||||
description: API reference for the instrumentation.js file.
|
||||
related:
|
||||
title: Learn more about Instrumentation
|
||||
links:
|
||||
- app/guides/instrumentation
|
||||
---
|
||||
|
||||
The `instrumentation.js|ts` file is used to integrate observability tools into your application, allowing you to track the performance and behavior, and to debug issues in production.
|
||||
|
||||
To use it, place the file in the **root** of your application or inside a [`src` folder](/docs/app/api-reference/file-conventions/src-folder) if using one.
|
||||
|
||||
## Exports
|
||||
|
||||
### `register` (optional)
|
||||
|
||||
The file exports a `register` function that is called **once** when a new Next.js server instance is initiated, and must complete before the server is ready to handle requests. `register` can be an async function.
|
||||
|
||||
```ts filename="instrumentation.ts" switcher
|
||||
import { registerOTel } from '@vercel/otel'
|
||||
|
||||
export function register() {
|
||||
registerOTel('next-app')
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="instrumentation.js" switcher
|
||||
import { registerOTel } from '@vercel/otel'
|
||||
|
||||
export function register() {
|
||||
registerOTel('next-app')
|
||||
}
|
||||
```
|
||||
|
||||
### `onRequestError` (optional)
|
||||
|
||||
You can optionally export an `onRequestError` function to track **server** errors to any custom observability provider.
|
||||
|
||||
- If you're running any async tasks in `onRequestError`, make sure they're awaited. `onRequestError` will be triggered when the Next.js server captures the error.
|
||||
- The `error` instance might not be the original error instance thrown, as it may be processed by React if encountered during Server Components rendering. If this happens, you can use `digest` property on an error to identify the actual error type.
|
||||
|
||||
```ts filename="instrumentation.ts" switcher
|
||||
import { type Instrumentation } from 'next'
|
||||
|
||||
export const onRequestError: Instrumentation.onRequestError = async (
|
||||
err,
|
||||
request,
|
||||
context
|
||||
) => {
|
||||
await fetch('https://.../report-error', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
message: err.message,
|
||||
request,
|
||||
context,
|
||||
}),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="instrumentation.js" switcher
|
||||
export async function onRequestError(err, request, context) {
|
||||
await fetch('https://.../report-error', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
message: err.message,
|
||||
request,
|
||||
context,
|
||||
}),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
#### Parameters
|
||||
|
||||
The function accepts three parameters: `error`, `request`, and `context`.
|
||||
|
||||
```ts filename="Types"
|
||||
export function onRequestError(
|
||||
error: { digest: string } & Error,
|
||||
request: {
|
||||
path: string // resource path, e.g. /blog?name=foo
|
||||
method: string // request method. e.g. GET, POST, etc
|
||||
headers: { [key: string]: string | string[] }
|
||||
},
|
||||
context: {
|
||||
routerKind: 'Pages Router' | 'App Router' // the router type
|
||||
routePath: string // the route file path, e.g. /app/blog/[dynamic]
|
||||
routeType: 'render' | 'route' | 'action' | 'proxy' // the context in which the error occurred
|
||||
renderSource:
|
||||
| 'react-server-components'
|
||||
| 'react-server-components-payload'
|
||||
| 'server-rendering'
|
||||
revalidateReason: 'on-demand' | 'stale' | undefined // undefined is a normal request without revalidation
|
||||
renderType: 'dynamic' | 'dynamic-resume' // 'dynamic-resume' for PPR
|
||||
}
|
||||
): void | Promise<void>
|
||||
```
|
||||
|
||||
- `error`: The caught error itself (type is always `Error`), and a `digest` property which is the unique ID of the error.
|
||||
- `request`: Read-only request information associated with the error.
|
||||
- `context`: The context in which the error occurred. This can be the type of router (App or Pages Router), and/or (Server Components (`'render'`), Route Handlers (`'route'`), Server Actions (`'action'`), or Proxy (`'proxy'`)).
|
||||
|
||||
### Specifying the runtime
|
||||
|
||||
The `instrumentation.js` file works in both the Node.js and Edge runtime, however, you can use `process.env.NEXT_RUNTIME` to target a specific runtime.
|
||||
|
||||
```js filename="instrumentation.js"
|
||||
export function register() {
|
||||
if (process.env.NEXT_RUNTIME === 'edge') {
|
||||
return require('./register.edge')
|
||||
} else {
|
||||
return require('./register.node')
|
||||
}
|
||||
}
|
||||
|
||||
export function onRequestError() {
|
||||
if (process.env.NEXT_RUNTIME === 'edge') {
|
||||
return require('./on-request-error.edge')
|
||||
} else {
|
||||
return require('./on-request-error.node')
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Version History
|
||||
|
||||
| Version | Changes |
|
||||
| --------- | ------------------------------------------------------- |
|
||||
| `v15.0.0` | `onRequestError` introduced, `instrumentation` stable |
|
||||
| `v14.0.4` | Turbopack support for `instrumentation` |
|
||||
| `v13.2.0` | `instrumentation` introduced as an experimental feature |
|
||||
Generated
Vendored
+83
@@ -0,0 +1,83 @@
|
||||
---
|
||||
title: Intercepting Routes
|
||||
description: Use intercepting routes to load a new route within the current layout while masking the browser URL, useful for advanced routing patterns such as modals.
|
||||
related:
|
||||
title: Next Steps
|
||||
description: Learn how to create modals with Intercepted and Parallel Routes.
|
||||
links:
|
||||
- app/api-reference/file-conventions/parallel-routes
|
||||
---
|
||||
|
||||
Intercepting routes allows you to load a route from another part of your application within the current layout. This routing paradigm can be useful when you want to display the content of a route without the user switching to a different context.
|
||||
|
||||
For example, when clicking on a photo in a feed, you can display the photo in a modal, overlaying the feed. In this case, Next.js intercepts the `/photo/123` route, masks the URL, and overlays it over `/feed`.
|
||||
|
||||
<Image
|
||||
alt="Intercepting routes soft navigation"
|
||||
srcLight="/docs/light/intercepting-routes-soft-navigate.png"
|
||||
srcDark="/docs/dark/intercepting-routes-soft-navigate.png"
|
||||
width="1600"
|
||||
height="617"
|
||||
/>
|
||||
|
||||
However, when navigating to the photo by clicking a shareable URL or by refreshing the page, the entire photo page should render instead of the modal. No route interception should occur.
|
||||
|
||||
<Image
|
||||
alt="Intercepting routes hard navigation"
|
||||
srcLight="/docs/light/intercepting-routes-hard-navigate.png"
|
||||
srcDark="/docs/dark/intercepting-routes-hard-navigate.png"
|
||||
width="1600"
|
||||
height="604"
|
||||
/>
|
||||
|
||||
## Convention
|
||||
|
||||
Intercepting routes can be defined with the `(..)` convention, which is similar to relative path convention `../` but for route segments.
|
||||
|
||||
You can use:
|
||||
|
||||
- `(.)` to match segments on the **same level**
|
||||
- `(..)` to match segments **one level above**
|
||||
- `(..)(..)` to match segments **two levels above**
|
||||
- `(...)` to match segments from the **root** `app` directory
|
||||
|
||||
For example, you can intercept the `photo` segment from within the `feed` segment by creating a `(..)photo` directory.
|
||||
|
||||
<Image
|
||||
alt="Intercepting routes folder structure"
|
||||
srcLight="/docs/light/intercepted-routes-files.png"
|
||||
srcDark="/docs/dark/intercepted-routes-files.png"
|
||||
width="1600"
|
||||
height="604"
|
||||
/>
|
||||
|
||||
> **Good to know:** The `(..)` convention is based on _route segments_, not the file-system. For example, it does not consider `@slot` folders in [Parallel Routes](/docs/app/api-reference/file-conventions/parallel-routes).
|
||||
|
||||
## Examples
|
||||
|
||||
### Modals
|
||||
|
||||
Intercepting Routes can be used together with [Parallel Routes](/docs/app/api-reference/file-conventions/parallel-routes) to create modals. This allows you to solve common challenges when building modals, such as:
|
||||
|
||||
- Making the modal content **shareable through a URL**.
|
||||
- **Preserving context** when the page is refreshed, instead of closing the modal.
|
||||
- **Closing the modal on backwards navigation** rather than going to the previous route.
|
||||
- **Reopening the modal on forwards navigation**.
|
||||
|
||||
Consider the following UI pattern, where a user can open a photo modal from a gallery using client-side navigation, or navigate to the photo page directly from a shareable URL:
|
||||
|
||||
<Image
|
||||
alt="Intercepting routes modal example"
|
||||
srcLight="/docs/light/intercepted-routes-modal-example.png"
|
||||
srcDark="/docs/dark/intercepted-routes-modal-example.png"
|
||||
width="1600"
|
||||
height="976"
|
||||
/>
|
||||
|
||||
In the above example, the path to the `photo` segment can use the `(..)` matcher since `@modal` is a slot and **not** a segment. This means that the `photo` route is only one segment level higher, despite being two file-system levels higher.
|
||||
|
||||
See the [Parallel Routes](/docs/app/api-reference/file-conventions/parallel-routes#modals) documentation for a step-by-step example, or see our [image gallery example](https://github.com/vercel-labs/nextgram).
|
||||
|
||||
> **Good to know:**
|
||||
>
|
||||
> - Other examples could include opening a login modal in a top navbar while also having a dedicated `/login` page, or opening a shopping cart in a side modal.
|
||||
Generated
Vendored
+731
@@ -0,0 +1,731 @@
|
||||
---
|
||||
title: layout.js
|
||||
description: API reference for the layout.js file.
|
||||
---
|
||||
|
||||
The `layout` file is used to define a layout in your Next.js application.
|
||||
|
||||
```tsx filename="app/dashboard/layout.tsx" switcher
|
||||
export default function DashboardLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return <section>{children}</section>
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/dashboard/layout.js" switcher
|
||||
export default function DashboardLayout({ children }) {
|
||||
return <section>{children}</section>
|
||||
}
|
||||
```
|
||||
|
||||
In the [component hierarchy](/docs/app/getting-started/project-structure#component-hierarchy), `layout.js` is the outermost component in a route segment. It wraps `template.js`, `error.js`, `loading.js`, `not-found.js`, and `page.js`.
|
||||
|
||||
A **root layout** is the top-most layout in the root `app` directory. It is used to define the `<html>` and `<body>` tags and other globally shared UI.
|
||||
|
||||
```tsx filename="app/layout.tsx" switcher
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/layout.js" switcher
|
||||
export default function RootLayout({ children }) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Reference
|
||||
|
||||
### Props
|
||||
|
||||
#### `children` (required)
|
||||
|
||||
Layout components should accept and use a `children` prop. During rendering, `children` will be populated with the route segments the layout is wrapping. These will primarily be the component of a child [Layout](/docs/app/api-reference/file-conventions/page) (if it exists) or [Page](/docs/app/api-reference/file-conventions/page), but could also be other special files like [Loading](/docs/app/api-reference/file-conventions/loading) or [Error](/docs/app/getting-started/error-handling) when applicable.
|
||||
|
||||
#### `params` (optional)
|
||||
|
||||
A promise that resolves to an object containing the [dynamic route parameters](/docs/app/api-reference/file-conventions/dynamic-routes) object from the root segment down to that layout.
|
||||
|
||||
```tsx filename="app/dashboard/[team]/layout.tsx" switcher
|
||||
export default async function Layout({
|
||||
children,
|
||||
params,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
params: Promise<{ team: string }>
|
||||
}) {
|
||||
const { team } = await params
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/dashboard/[team]/layout.js" switcher
|
||||
export default async function Layout({ children, params }) {
|
||||
const { team } = await params
|
||||
}
|
||||
```
|
||||
|
||||
| Example Route | URL | `params` |
|
||||
| --------------------------------- | -------------- | ---------------------------------- |
|
||||
| `app/dashboard/[team]/layout.js` | `/dashboard/1` | `Promise<{ team: '1' }>` |
|
||||
| `app/shop/[tag]/[item]/layout.js` | `/shop/1/2` | `Promise<{ tag: '1', item: '2' }>` |
|
||||
| `app/blog/[...slug]/layout.js` | `/blog/1/2` | `Promise<{ slug: ['1', '2'] }>` |
|
||||
|
||||
- Since the `params` prop is a promise. You must use `async/await` or React's [`use`](https://react.dev/reference/react/use) function to access the values.
|
||||
- In version 14 and earlier, `params` was a synchronous prop. To help with backwards compatibility, you can still access it synchronously in Next.js 15, but this behavior will be deprecated in the future.
|
||||
|
||||
### Layout Props Helper
|
||||
|
||||
You can type layouts with `LayoutProps` to get a strongly typed `params` and named slots inferred from your directory structure. `LayoutProps` is a globally available helper.
|
||||
|
||||
```tsx filename="app/dashboard/layout.tsx"
|
||||
export default function Layout(props: LayoutProps<'/dashboard'>) {
|
||||
return (
|
||||
<section>
|
||||
{props.children}
|
||||
{/* If you have app/dashboard/@analytics, it appears as a typed slot: */}
|
||||
{/* {props.analytics} */}
|
||||
</section>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
> **Good to know**:
|
||||
>
|
||||
> - Types are generated during `next dev`, `next build` or `next typegen`.
|
||||
> - After type generation, the `LayoutProps` helper is globally available. It doesn't need to be imported.
|
||||
|
||||
### Root Layout
|
||||
|
||||
The `app` directory **must** include a **root layout**, which is the top-most layout in the root `app` directory. Typically, the root layout is `app/layout.js`.
|
||||
|
||||
```tsx filename="app/layout.tsx" switcher
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<html>
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/layout.js" switcher
|
||||
export default function RootLayout({ children }) {
|
||||
return (
|
||||
<html>
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
- The root layout **must** define `<html>` and `<body>` tags.
|
||||
- You should **not** manually add `<head>` tags such as `<title>` and `<meta>` to root layouts. Instead, you should use the [Metadata API](/docs/app/getting-started/metadata-and-og-images) which automatically handles advanced requirements such as streaming and de-duplicating `<head>` elements.
|
||||
- You can create **multiple root layouts**. Any layout without a `layout.js` above it is a root layout. Two common approaches:
|
||||
- Using [route groups](/docs/app/api-reference/file-conventions/route-groups) like `app/(shop)/layout.js` and `app/(marketing)/layout.js`
|
||||
- Omitting `app/layout.js` so layouts in subdirectories like `app/dashboard/layout.js` and `app/blog/layout.js` each become root layouts for their respective directories.
|
||||
- Navigating **across multiple root layouts** will cause a **full page load** (as opposed to a client-side navigation).
|
||||
- The root layout can be under a **dynamic segment**, for example when implementing [internationalization](/docs/app/guides/internationalization) with `app/[lang]/layout.js`.
|
||||
|
||||
## Caveats
|
||||
|
||||
### Request Object
|
||||
|
||||
Layouts are cached in the client during navigation to avoid unnecessary server requests.
|
||||
|
||||
[Layouts](/docs/app/api-reference/file-conventions/layout) do not rerender. They can be cached and reused to avoid unnecessary computation when navigating between pages. By restricting layouts from accessing the raw request, Next.js can prevent the execution of potentially slow or expensive user code within the layout, which could negatively impact performance.
|
||||
|
||||
To access the request object, you can use [`headers`](/docs/app/api-reference/functions/headers) and [`cookies`](/docs/app/api-reference/functions/cookies) APIs in [Server Components](/docs/app/getting-started/server-and-client-components) and Functions.
|
||||
|
||||
```tsx filename="app/shop/layout.tsx" switcher
|
||||
import { cookies } from 'next/headers'
|
||||
|
||||
export default async function Layout({ children }) {
|
||||
const cookieStore = await cookies()
|
||||
const theme = cookieStore.get('theme')
|
||||
return '...'
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/shop/layout.js" switcher
|
||||
import { cookies } from 'next/headers'
|
||||
|
||||
export default async function Layout({ children }) {
|
||||
const cookieStore = await cookies()
|
||||
const theme = cookieStore.get('theme')
|
||||
return '...'
|
||||
}
|
||||
```
|
||||
|
||||
### Query params
|
||||
|
||||
Layouts do not rerender on navigation, so they cannot access search params which would otherwise become stale.
|
||||
|
||||
To access updated query parameters, you can use the Page [`searchParams`](/docs/app/api-reference/file-conventions/page#searchparams-optional) prop, or read them inside a Client Component using the [`useSearchParams`](/docs/app/api-reference/functions/use-search-params) hook. Since Client Components re-render on navigation, they have access to the latest query parameters.
|
||||
|
||||
```tsx filename="app/ui/search.tsx" switcher
|
||||
'use client'
|
||||
|
||||
import { useSearchParams } from 'next/navigation'
|
||||
|
||||
export default function Search() {
|
||||
const searchParams = useSearchParams()
|
||||
|
||||
const search = searchParams.get('search')
|
||||
|
||||
return '...'
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/ui/search.js" switcher
|
||||
'use client'
|
||||
|
||||
import { useSearchParams } from 'next/navigation'
|
||||
|
||||
export default function Search() {
|
||||
const searchParams = useSearchParams()
|
||||
|
||||
const search = searchParams.get('search')
|
||||
|
||||
return '...'
|
||||
}
|
||||
```
|
||||
|
||||
```tsx filename="app/shop/layout.tsx" switcher
|
||||
import Search from '@/app/ui/search'
|
||||
|
||||
export default function Layout({ children }) {
|
||||
return (
|
||||
<>
|
||||
<Search />
|
||||
{children}
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/shop/layout.js" switcher
|
||||
import Search from '@/app/ui/search'
|
||||
|
||||
export default function Layout({ children }) {
|
||||
return (
|
||||
<>
|
||||
<Search />
|
||||
{children}
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Pathname
|
||||
|
||||
Layouts do not re-render on navigation, so they do not access pathname which would otherwise become stale.
|
||||
|
||||
To access the current pathname, you can read it inside a Client Component using the [`usePathname`](/docs/app/api-reference/functions/use-pathname) hook. Since Client Components re-render during navigation, they have access to the latest pathname.
|
||||
|
||||
```tsx filename="app/ui/breadcrumbs.tsx" switcher
|
||||
'use client'
|
||||
|
||||
import { usePathname } from 'next/navigation'
|
||||
|
||||
// Simplified breadcrumbs logic
|
||||
export default function Breadcrumbs() {
|
||||
const pathname = usePathname()
|
||||
const segments = pathname.split('/')
|
||||
|
||||
return (
|
||||
<nav>
|
||||
{segments.map((segment, index) => (
|
||||
<span key={index}>
|
||||
{' > '}
|
||||
{segment}
|
||||
</span>
|
||||
))}
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/ui/breadcrumbs.js" switcher
|
||||
'use client'
|
||||
|
||||
import { usePathname } from 'next/navigation'
|
||||
|
||||
// Simplified breadcrumbs logic
|
||||
export default function Breadcrumbs() {
|
||||
const pathname = usePathname()
|
||||
const segments = pathname.split('/')
|
||||
|
||||
return (
|
||||
<nav>
|
||||
{segments.map((segment, index) => (
|
||||
<span key={index}>
|
||||
{' > '}
|
||||
{segment}
|
||||
</span>
|
||||
))}
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```tsx filename="app/docs/layout.tsx" switcher
|
||||
import { Breadcrumbs } from '@/app/ui/Breadcrumbs'
|
||||
|
||||
export default function Layout({ children }) {
|
||||
return (
|
||||
<>
|
||||
<Breadcrumbs />
|
||||
<main>{children}</main>
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/docs/layout.js" switcher
|
||||
import { Breadcrumbs } from '@/app/ui/Breadcrumbs'
|
||||
|
||||
export default function Layout({ children }) {
|
||||
return (
|
||||
<>
|
||||
<Breadcrumbs />
|
||||
<main>{children}</main>
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Interaction with `loading.js`
|
||||
|
||||
Because `loading.js` sits below `layout.js` in the [component hierarchy](/docs/app/getting-started/project-structure#component-hierarchy), it cannot show a fallback for uncached or runtime data access in the layout itself, such as calling [`cookies()`](/docs/app/api-reference/functions/cookies), [`headers()`](/docs/app/api-reference/functions/headers), or making uncached fetches.
|
||||
|
||||
The behavior depends on whether [Cache Components](/docs/app/getting-started/caching) is enabled:
|
||||
|
||||
- **Without Cache Components:** The navigation will block until the layout finishes rendering, and the `loading.js` fallback will not be shown.
|
||||
- **With Cache Components:** `loading.js` is treated as a regular `<Suspense>` boundary rather than a special prefetch marker. Uncached or runtime data access in the layout must be explicitly wrapped in its own `<Suspense>` boundary, otherwise Next.js guides you with a build-time error. The static shell streams immediately, and the uncached content swaps in as it resolves.
|
||||
|
||||
In both cases, to ensure instant navigation, either:
|
||||
|
||||
- Wrap runtime data access in your layout in its own `<Suspense>` boundary with a fallback.
|
||||
- Move uncached data fetching from `layout.js` into `page.js` where `loading.js` can show a fallback.
|
||||
|
||||
```tsx filename="app/dashboard/layout.tsx" switcher highlight={8-10}
|
||||
import { Suspense } from 'react'
|
||||
import { NavSkeleton } from './nav-skeleton'
|
||||
import { DashboardNav } from './dashboard-nav'
|
||||
|
||||
export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<>
|
||||
<Suspense fallback={<NavSkeleton />}>
|
||||
<DashboardNav />
|
||||
</Suspense>
|
||||
<main>{children}</main>
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/dashboard/layout.js" switcher highlight={8-10}
|
||||
import { Suspense } from 'react'
|
||||
import { NavSkeleton } from './nav-skeleton'
|
||||
import { DashboardNav } from './dashboard-nav'
|
||||
|
||||
export default function Layout({ children }) {
|
||||
return (
|
||||
<>
|
||||
<Suspense fallback={<NavSkeleton />}>
|
||||
<DashboardNav />
|
||||
</Suspense>
|
||||
<main>{children}</main>
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Fetching Data
|
||||
|
||||
Layouts cannot pass data to their `children`. However, you can fetch the same data in a route more than once, and use React [`cache`](https://react.dev/reference/react/cache) to dedupe the requests without affecting performance.
|
||||
|
||||
Alternatively, when using [`fetch`](/docs/app/api-reference/functions/fetch)in Next.js, requests are automatically deduped.
|
||||
|
||||
```tsx filename="app/lib/data.ts" switcher
|
||||
export async function getUser(id: string) {
|
||||
const res = await fetch(`https://.../users/${id}`)
|
||||
return res.json()
|
||||
}
|
||||
```
|
||||
|
||||
```tsx filename="app/dashboard/layout.tsx" switcher
|
||||
import { getUser } from '@/app/lib/data'
|
||||
import { UserName } from '@/app/ui/user-name'
|
||||
|
||||
export default async function Layout({ children }) {
|
||||
const user = await getUser('1')
|
||||
|
||||
return (
|
||||
<>
|
||||
<nav>
|
||||
{/* ... */}
|
||||
<UserName user={user.name} />
|
||||
</nav>
|
||||
{children}
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/dashboard/layout.js" switcher
|
||||
import { getUser } from '@/app/lib/data'
|
||||
import { UserName } from '@/app/ui/user-name'
|
||||
|
||||
export default async function Layout({ children }) {
|
||||
const user = await getUser('1')
|
||||
|
||||
return (
|
||||
<>
|
||||
<nav>
|
||||
{/* ... */}
|
||||
<UserName user={user.name} />
|
||||
</nav>
|
||||
{children}
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```tsx filename="app/dashboard/page.tsx" switcher
|
||||
import { getUser } from '@/app/lib/data'
|
||||
import { UserName } from '@/app/ui/user-name'
|
||||
|
||||
export default async function Page() {
|
||||
const user = await getUser('1')
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Welcome {user.name}</h1>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/dashboard/page.js" switcher
|
||||
import { getUser } from '@/app/lib/data'
|
||||
import { UserName } from '@/app/ui/user-name'
|
||||
|
||||
export default async function Page() {
|
||||
const user = await getUser('1')
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Welcome {user.name}</h1>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Accessing child segments
|
||||
|
||||
Layouts do not have access to the route segments below itself. To access all route segments, you can use [`useSelectedLayoutSegment`](/docs/app/api-reference/functions/use-selected-layout-segment) or [`useSelectedLayoutSegments`](/docs/app/api-reference/functions/use-selected-layout-segments) in a Client Component.
|
||||
|
||||
```tsx filename="app/ui/nav-link.tsx" switcher
|
||||
'use client'
|
||||
|
||||
import Link from 'next/link'
|
||||
import { useSelectedLayoutSegment } from 'next/navigation'
|
||||
|
||||
export default function NavLink({
|
||||
slug,
|
||||
children,
|
||||
}: {
|
||||
slug: string
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
const segment = useSelectedLayoutSegment()
|
||||
const isActive = slug === segment
|
||||
|
||||
return (
|
||||
<Link
|
||||
href={`/blog/${slug}`}
|
||||
// Change style depending on whether the link is active
|
||||
style={{ fontWeight: isActive ? 'bold' : 'normal' }}
|
||||
>
|
||||
{children}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/ui/nav-link.js" switcher
|
||||
'use client'
|
||||
|
||||
import Link from 'next/link'
|
||||
import { useSelectedLayoutSegment } from 'next/navigation'
|
||||
|
||||
export default function NavLinks({ slug, children }) {
|
||||
const segment = useSelectedLayoutSegment()
|
||||
const isActive = slug === segment
|
||||
|
||||
return (
|
||||
<Link
|
||||
href={`/blog/${slug}`}
|
||||
style={{ fontWeight: isActive ? 'bold' : 'normal' }}
|
||||
>
|
||||
{children}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```tsx filename="app/blog/layout.tsx" switcher
|
||||
import { NavLink } from './nav-link'
|
||||
import getPosts from './get-posts'
|
||||
|
||||
export default async function Layout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
const featuredPosts = await getPosts()
|
||||
return (
|
||||
<div>
|
||||
{featuredPosts.map((post) => (
|
||||
<div key={post.id}>
|
||||
<NavLink slug={post.slug}>{post.title}</NavLink>
|
||||
</div>
|
||||
))}
|
||||
<div>{children}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/blog/layout.js" switcher
|
||||
import { NavLink } from './nav-link'
|
||||
import getPosts from './get-posts'
|
||||
|
||||
export default async function Layout({ children }) {
|
||||
const featuredPosts = await getPosts()
|
||||
return (
|
||||
<div>
|
||||
{featuredPosts.map((post) => (
|
||||
<div key={post.id}>
|
||||
<NavLink slug={post.slug}>{post.title}</NavLink>
|
||||
</div>
|
||||
))}
|
||||
<div>{children}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Metadata
|
||||
|
||||
You can modify the `<head>` HTML elements such as `title` and `meta` using the [`metadata` object](/docs/app/api-reference/functions/generate-metadata#the-metadata-object) or [`generateMetadata` function](/docs/app/api-reference/functions/generate-metadata#generatemetadata-function).
|
||||
|
||||
```tsx filename="app/layout.tsx" switcher
|
||||
import type { Metadata } from 'next'
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Next.js',
|
||||
}
|
||||
|
||||
export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
return '...'
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/layout.js" switcher
|
||||
export const metadata = {
|
||||
title: 'Next.js',
|
||||
}
|
||||
|
||||
export default function Layout({ children }) {
|
||||
return '...'
|
||||
}
|
||||
```
|
||||
|
||||
> **Good to know**: You should **not** manually add `<head>` tags such as `<title>` and `<meta>` to root layouts. Instead, use the [Metadata APIs](/docs/app/api-reference/functions/generate-metadata) which automatically handles advanced requirements such as streaming and de-duplicating `<head>` elements.
|
||||
|
||||
### Active Nav Links
|
||||
|
||||
You can use the [`usePathname`](/docs/app/api-reference/functions/use-pathname) hook to determine if a nav link is active.
|
||||
|
||||
Since `usePathname` is a client hook, you need to extract the nav links into a Client Component, which can be imported into your layout:
|
||||
|
||||
```tsx filename="app/ui/nav-links.tsx" switcher
|
||||
'use client'
|
||||
|
||||
import { usePathname } from 'next/navigation'
|
||||
import Link from 'next/link'
|
||||
|
||||
export function NavLinks() {
|
||||
const pathname = usePathname()
|
||||
|
||||
return (
|
||||
<nav>
|
||||
<Link className={`link ${pathname === '/' ? 'active' : ''}`} href="/">
|
||||
Home
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
className={`link ${pathname === '/about' ? 'active' : ''}`}
|
||||
href="/about"
|
||||
>
|
||||
About
|
||||
</Link>
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/ui/nav-links.js" switcher
|
||||
'use client'
|
||||
|
||||
import { usePathname } from 'next/navigation'
|
||||
import Link from 'next/link'
|
||||
|
||||
export function Links() {
|
||||
const pathname = usePathname()
|
||||
|
||||
return (
|
||||
<nav>
|
||||
<Link className={`link ${pathname === '/' ? 'active' : ''}`} href="/">
|
||||
Home
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
className={`link ${pathname === '/about' ? 'active' : ''}`}
|
||||
href="/about"
|
||||
>
|
||||
About
|
||||
</Link>
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```tsx filename="app/layout.tsx" switcher
|
||||
import { NavLinks } from '@/app/ui/nav-links'
|
||||
|
||||
export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>
|
||||
<NavLinks />
|
||||
<main>{children}</main>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/layout.js" switcher
|
||||
import { NavLinks } from '@/app/ui/nav-links'
|
||||
|
||||
export default function Layout({ children }) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>
|
||||
<NavLinks />
|
||||
<main>{children}</main>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Displaying content based on `params`
|
||||
|
||||
Using [dynamic route segments](/docs/app/api-reference/file-conventions/dynamic-routes), you can display or fetch specific content based on the `params` prop.
|
||||
|
||||
```tsx filename="app/dashboard/layout.tsx" switcher
|
||||
export default async function DashboardLayout({
|
||||
children,
|
||||
params,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
params: Promise<{ team: string }>
|
||||
}) {
|
||||
const { team } = await params
|
||||
|
||||
return (
|
||||
<section>
|
||||
<header>
|
||||
<h1>Welcome to {team}'s Dashboard</h1>
|
||||
</header>
|
||||
<main>{children}</main>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/dashboard/layout.js" switcher
|
||||
export default async function DashboardLayout({ children, params }) {
|
||||
const { team } = await params
|
||||
|
||||
return (
|
||||
<section>
|
||||
<header>
|
||||
<h1>Welcome to {team}'s Dashboard</h1>
|
||||
</header>
|
||||
<main>{children}</main>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Reading `params` in Client Components
|
||||
|
||||
To use `params` in a Client Component (which cannot be `async`), you can use React's [`use`](https://react.dev/reference/react/use) function to read the promise:
|
||||
|
||||
```tsx filename="app/page.tsx" switcher
|
||||
'use client'
|
||||
|
||||
import { use } from 'react'
|
||||
|
||||
export default function Page({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ slug: string }>
|
||||
}) {
|
||||
const { slug } = use(params)
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="app/page.js" switcher
|
||||
'use client'
|
||||
|
||||
import { use } from 'react'
|
||||
|
||||
export default function Page({ params }) {
|
||||
const { slug } = use(params)
|
||||
}
|
||||
```
|
||||
|
||||
## Version History
|
||||
|
||||
| Version | Changes |
|
||||
| ------------ | --------------------------------------------------------------------------------------------- |
|
||||
| `v15.0.0-RC` | `params` is now a promise. A [codemod](/docs/app/guides/upgrading/codemods#150) is available. |
|
||||
| `v13.0.0` | `layout` introduced. |
|
||||
Generated
Vendored
+196
@@ -0,0 +1,196 @@
|
||||
---
|
||||
title: loading.js
|
||||
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.
|
||||
|
||||
<Image
|
||||
alt="Loading UI"
|
||||
srcLight="/docs/light/loading-ui.png"
|
||||
srcDark="/docs/dark/loading-ui.png"
|
||||
width="1600"
|
||||
height="691"
|
||||
/>
|
||||
|
||||
```tsx filename="app/feed/loading.tsx" switcher
|
||||
export default function Loading() {
|
||||
// Or a custom loading skeleton component
|
||||
return <p>Loading...</p>
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/feed/loading.js" switcher
|
||||
export default function Loading() {
|
||||
// Or a custom loading skeleton component
|
||||
return <p>Loading...</p>
|
||||
}
|
||||
```
|
||||
|
||||
Inside the `loading.js` file, you can add any light-weight loading UI. You may find it helpful to use the [React Developer Tools](https://react.dev/learn/react-developer-tools) to manually toggle Suspense boundaries.
|
||||
|
||||
By default, this file is a [Server Component](/docs/app/getting-started/server-and-client-components) - but can also be used as a Client Component through the `"use client"` directive.
|
||||
|
||||
## Reference
|
||||
|
||||
### Parameters
|
||||
|
||||
Loading UI components do not accept any parameters.
|
||||
|
||||
## Behavior
|
||||
|
||||
### Navigation
|
||||
|
||||
- The Fallback UI is [prefetched](/docs/app/getting-started/linking-and-navigating#prefetching), making navigation immediate unless prefetching hasn't completed.
|
||||
- Navigation is interruptible, meaning changing routes does not need to wait for the content of the route to fully load before navigating to another route.
|
||||
- Shared layouts remain interactive while new route segments load.
|
||||
|
||||
### Instant Loading States
|
||||
|
||||
An instant loading state is fallback UI that is shown immediately upon navigation. You can prerender loading indicators such as skeletons and spinners, or a small but meaningful part of future screens such as a cover photo, title, etc. This helps users understand the app is responding and provides a better user experience.
|
||||
|
||||
Create a loading state by adding a `loading.js` file inside a folder.
|
||||
|
||||
<Image
|
||||
alt="loading.js special file"
|
||||
srcLight="/docs/light/loading-special-file.png"
|
||||
srcDark="/docs/dark/loading-special-file.png"
|
||||
width="1600"
|
||||
height="606"
|
||||
/>
|
||||
|
||||
```tsx filename="app/dashboard/loading.tsx" switcher
|
||||
export default function Loading() {
|
||||
// You can add any UI inside Loading, including a Skeleton.
|
||||
return <LoadingSkeleton />
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/dashboard/loading.js" switcher
|
||||
export default function Loading() {
|
||||
// You can add any UI inside Loading, including a Skeleton.
|
||||
return <LoadingSkeleton />
|
||||
}
|
||||
```
|
||||
|
||||
In the same folder, `loading.js` will be nested inside `layout.js`. It will automatically wrap the `page.js` file and any children below in a `<Suspense>` boundary.
|
||||
|
||||
<Image
|
||||
alt="loading.js overview"
|
||||
srcLight="/docs/light/loading-overview.png"
|
||||
srcDark="/docs/dark/loading-overview.png"
|
||||
width="1600"
|
||||
height="768"
|
||||
/>
|
||||
|
||||
In the [component hierarchy](/docs/app/getting-started/project-structure#component-hierarchy), `loading.js` wraps `not-found.js`, `page.js`, and nested `layout.js` files in a `<Suspense>` boundary. It does **not** wrap the `layout.js`, `template.js`, or `error.js` in the same segment.
|
||||
|
||||
> **Good to know**: If the layout accesses uncached or runtime data (e.g. `cookies()`, `headers()`, or uncached fetches), `loading.js` will not show a fallback for it.
|
||||
>
|
||||
> - **Without [Cache Components](/docs/app/getting-started/caching):** Navigation blocks until the layout finishes rendering.
|
||||
> - **With [Cache Components](/docs/app/getting-started/caching):** Uncached or runtime data access in the layout must be explicitly wrapped in `<Suspense>`, otherwise Next.js guides you with a build-time error. The static shell streams first, and the uncached content fills in.
|
||||
>
|
||||
> To ensure instant navigation, move uncached data fetching from `layout.js` into `page.js`, or wrap the runtime data access in your layout in its own `<Suspense>` boundary. See [layout.js Caveats](/docs/app/api-reference/file-conventions/layout#interaction-with-loadingjs) for details and examples.
|
||||
|
||||
### SEO
|
||||
|
||||
- For bots that only scrape static HTML, and cannot execute JavaScript like a full browser, such as Twitterbot, Next.js resolves [`generateMetadata`](/docs/app/api-reference/functions/generate-metadata) before streaming UI, and metadata is placed in the `<head>` of the initial HTML.
|
||||
- Otherwise, [streaming metadata](/docs/app/api-reference/functions/generate-metadata#streaming-metadata) may be used. Next.js automatically detects user agents to choose between blocking and streaming behavior.
|
||||
- Since streaming is server-rendered, it does not impact SEO. You can use the [Rich Results Test](https://search.google.com/test/rich-results) tool from Google to see how your page appears to Google's web crawlers and view the serialized HTML ([source](https://web.dev/rendering-on-the-web/#seo-considerations)).
|
||||
|
||||
### Status Codes
|
||||
|
||||
When streaming, a `200` status code will be returned to signal that the request was successful.
|
||||
|
||||
The server can still communicate errors or issues to the client within the streamed content itself, for example, when using [`redirect`](/docs/app/api-reference/functions/redirect) or [`notFound`](/docs/app/api-reference/functions/not-found). Because the response headers have already been sent to the client, the status code of the response cannot be updated.
|
||||
|
||||
For example, when a 404 page is streamed to the client, Next.js includes a `<meta name="robots" content="noindex">` tag in the streamed HTML. This prevents search engines from indexing that URL even if the HTTP status is 200. See Google’s guidance on the [`robots` meta tag](https://developers.google.com/search/docs/crawling-indexing/robots-meta-tag).
|
||||
|
||||
Some crawlers may label these responses as “soft 404s”. In the streaming case, this does not lead to indexation because the page is explicitly marked `noindex` in the HTML.
|
||||
|
||||
If you need a 404 status, for compliance or analytics, ensure the resource exists before the response body is streamed, so that the server can set the HTTP status code.
|
||||
|
||||
You can run this check in [`proxy`](/docs/app/api-reference/file-conventions/proxy) to rewrite missing slugs to a not-found route, or [produce a 404 response](/docs/app/api-reference/file-conventions/proxy#producing-a-response). Keep proxy checks fast, and avoid fetching full content there.
|
||||
|
||||
<details>
|
||||
<summary>When is the response body streamed?</summary>
|
||||
|
||||
The response body starts streaming when a Suspense fallback renders (for example, a `loading.tsx`) or when a Server Component suspends under a `Suspense` boundary. Place `notFound()` before those boundaries and before any `await` that may suspend.
|
||||
|
||||
To start streaming, the response headers must be set. This is why it is not possible to change the status code after streaming started.
|
||||
|
||||
</details>
|
||||
|
||||
### Browser limits
|
||||
|
||||
[Some browsers](https://bugs.webkit.org/show_bug.cgi?id=252413) buffer a streaming response. You may not see the streamed response until the response exceeds 1024 bytes. This typically only affects “hello world” applications, but not real applications.
|
||||
|
||||
## Platform Support
|
||||
|
||||
| Deployment Option | Supported |
|
||||
| ------------------------------------------------------------------- | ----------------- |
|
||||
| [Node.js server](/docs/app/getting-started/deploying#nodejs-server) | Yes |
|
||||
| [Docker container](/docs/app/getting-started/deploying#docker) | Yes |
|
||||
| [Static export](/docs/app/getting-started/deploying#static-export) | No |
|
||||
| [Adapters](/docs/app/getting-started/deploying#adapters) | Platform-specific |
|
||||
|
||||
Learn how to [configure streaming](/docs/app/guides/self-hosting#streaming-and-suspense) when self-hosting Next.js.
|
||||
|
||||
## Examples
|
||||
|
||||
### Streaming with Suspense
|
||||
|
||||
In addition to `loading.js`, you can also manually create Suspense Boundaries for your own UI components. The App Router supports streaming with [Suspense](https://react.dev/reference/react/Suspense). See the [Streaming guide](/docs/app/guides/streaming) for more on how streaming works, including granular Suspense patterns, Route Handler streaming, and infrastructure considerations.
|
||||
|
||||
`<Suspense>` works by wrapping a component that performs an asynchronous action (e.g. fetch data), showing fallback UI (e.g. skeleton, spinner) while it's happening, and then swapping in your component once the action completes.
|
||||
|
||||
```tsx filename="app/dashboard/page.tsx" switcher
|
||||
import { Suspense } from 'react'
|
||||
import { PostFeed, Weather } from './Components'
|
||||
|
||||
export default function Posts() {
|
||||
return (
|
||||
<section>
|
||||
<Suspense fallback={<p>Loading feed...</p>}>
|
||||
<PostFeed />
|
||||
</Suspense>
|
||||
<Suspense fallback={<p>Loading weather...</p>}>
|
||||
<Weather />
|
||||
</Suspense>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/dashboard/page.js" switcher
|
||||
import { Suspense } from 'react'
|
||||
import { PostFeed, Weather } from './Components'
|
||||
|
||||
export default function Posts() {
|
||||
return (
|
||||
<section>
|
||||
<Suspense fallback={<p>Loading feed...</p>}>
|
||||
<PostFeed />
|
||||
</Suspense>
|
||||
<Suspense fallback={<p>Loading weather...</p>}>
|
||||
<Weather />
|
||||
</Suspense>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
By using Suspense, you get the benefits of:
|
||||
|
||||
1. **Streaming Server Rendering** - Progressively rendering HTML from the server to the client.
|
||||
2. **Selective Hydration** - React prioritizes what components to make interactive first based on user interaction.
|
||||
|
||||
For more Suspense examples and use cases, please see the [React Documentation](https://react.dev/reference/react/Suspense).
|
||||
|
||||
## Version History
|
||||
|
||||
| Version | Changes |
|
||||
| --------- | --------------------- |
|
||||
| `v13.0.0` | `loading` introduced. |
|
||||
Generated
Vendored
+60
@@ -0,0 +1,60 @@
|
||||
---
|
||||
title: mdx-components.js
|
||||
description: API reference for the mdx-components.js file.
|
||||
related:
|
||||
title: Learn more about MDX Components
|
||||
links:
|
||||
- app/guides/mdx
|
||||
---
|
||||
|
||||
The `mdx-components.js|tsx` file is **required** to use [`@next/mdx` with App Router](/docs/app/guides/mdx) and will not work without it. Additionally, you can use it to [customize styles](/docs/app/guides/mdx#using-custom-styles-and-components).
|
||||
|
||||
Use the file `mdx-components.tsx` (or `.js`) in the root of your project to define MDX Components. For example, at the same level as `pages` or `app`, or inside `src` if applicable.
|
||||
|
||||
```tsx filename="mdx-components.tsx" switcher
|
||||
import type { MDXComponents } from 'mdx/types'
|
||||
|
||||
const components: MDXComponents = {}
|
||||
|
||||
export function useMDXComponents(): MDXComponents {
|
||||
return components
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="mdx-components.js" switcher
|
||||
const components = {}
|
||||
|
||||
export function useMDXComponents() {
|
||||
return components
|
||||
}
|
||||
```
|
||||
|
||||
## Exports
|
||||
|
||||
### `useMDXComponents` function
|
||||
|
||||
The file must export a single function named `useMDXComponents`. This function does not accept any arguments.
|
||||
|
||||
```tsx filename="mdx-components.tsx" switcher
|
||||
import type { MDXComponents } from 'mdx/types'
|
||||
|
||||
const components: MDXComponents = {}
|
||||
|
||||
export function useMDXComponents(): MDXComponents {
|
||||
return components
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="mdx-components.js" switcher
|
||||
const components = {}
|
||||
|
||||
export function useMDXComponents() {
|
||||
return components
|
||||
}
|
||||
```
|
||||
|
||||
## Version History
|
||||
|
||||
| Version | Changes |
|
||||
| --------- | -------------------- |
|
||||
| `v13.1.2` | MDX Components added |
|
||||
Generated
Vendored
+235
@@ -0,0 +1,235 @@
|
||||
---
|
||||
title: not-found.js
|
||||
description: API reference for the not-found.js file.
|
||||
---
|
||||
|
||||
Next.js provides two conventions to handle not found cases:
|
||||
|
||||
- **`not-found.js`**: Used when you call the [`notFound`](/docs/app/api-reference/functions/not-found) function in a route segment.
|
||||
- **`global-not-found.js`**: Used to define a global 404 page for unmatched routes across your entire app. This is handled at the routing level and doesn't depend on rendering a layout or page.
|
||||
|
||||
## `not-found.js`
|
||||
|
||||
The **not-found** file is used to render UI when the [`notFound`](/docs/app/api-reference/functions/not-found) function is thrown within a route segment. Along with serving a custom UI, Next.js will return a `200` HTTP status code for streamed responses, and `404` for non-streamed responses (see [Status Codes](/docs/app/api-reference/file-conventions/loading#status-codes) for details about SEO).
|
||||
|
||||
```tsx filename="app/not-found.tsx" switcher
|
||||
import Link from 'next/link'
|
||||
|
||||
export default function NotFound() {
|
||||
return (
|
||||
<div>
|
||||
<h2>Not Found</h2>
|
||||
<p>Could not find requested resource</p>
|
||||
<Link href="/">Return Home</Link>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/blog/not-found.js" switcher
|
||||
import Link from 'next/link'
|
||||
|
||||
export default function NotFound() {
|
||||
return (
|
||||
<div>
|
||||
<h2>Not Found</h2>
|
||||
<p>Could not find requested resource</p>
|
||||
<Link href="/">Return Home</Link>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
In the [component hierarchy](/docs/app/getting-started/project-structure#component-hierarchy), `not-found.js` renders between `loading.js` and `page.js`. It is wrapped by the `<Suspense>` boundary from `loading.js` and the error boundary from `error.js` in the same segment.
|
||||
|
||||
## `global-not-found.js` (experimental)
|
||||
|
||||
The `global-not-found.js` file lets you define a 404 page for your entire application. Unlike `not-found.js`, which works at the route level, this is used when a requested URL doesn't match any route at all. Next.js **skips rendering** and directly returns this global page.
|
||||
|
||||
The `global-not-found.js` file bypasses your app's normal rendering, which means you'll need to import any global styles, fonts, or other dependencies that your 404 page requires.
|
||||
|
||||
> **Good to know**: A smaller version of your global styles, and a simpler font family could improve performance of this page.
|
||||
|
||||
`global-not-found.js` is useful when you can't build a 404 page using a combination of `layout.js` and `not-found.js`. This can happen in two cases:
|
||||
|
||||
- Your app has multiple root layouts (e.g. `app/(admin)/layout.tsx` and `app/(shop)/layout.tsx`), so there's no single layout to compose a global 404 from.
|
||||
- Your root layout is defined using top-level dynamic segments (e.g. `app/[country]/layout.tsx`), which makes composing a consistent 404 page harder.
|
||||
|
||||
To enable it, add the `globalNotFound` flag in `next.config.ts`:
|
||||
|
||||
```tsx filename="next.config.ts"
|
||||
import type { NextConfig } from 'next'
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
experimental: {
|
||||
globalNotFound: true,
|
||||
},
|
||||
}
|
||||
|
||||
export default nextConfig
|
||||
```
|
||||
|
||||
Then, create a file in the root of the `app` directory: `app/global-not-found.js`:
|
||||
|
||||
```tsx filename="app/global-not-found.tsx" switcher
|
||||
// Import global styles and fonts
|
||||
import './globals.css'
|
||||
import { Inter } from 'next/font/google'
|
||||
import type { Metadata } from 'next'
|
||||
|
||||
const inter = Inter({ subsets: ['latin'] })
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: '404 - Page Not Found',
|
||||
description: 'The page you are looking for does not exist.',
|
||||
}
|
||||
|
||||
export default function GlobalNotFound() {
|
||||
return (
|
||||
<html lang="en" className={inter.className}>
|
||||
<body>
|
||||
<h1>404 - Page Not Found</h1>
|
||||
<p>This page does not exist.</p>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/global-not-found.js" switcher
|
||||
// Import global styles and fonts
|
||||
import './globals.css'
|
||||
import { Inter } from 'next/font/google'
|
||||
|
||||
const inter = Inter({ subsets: ['latin'] })
|
||||
|
||||
export const metadata = {
|
||||
title: '404 - Page Not Found',
|
||||
description: 'The page you are looking for does not exist.',
|
||||
}
|
||||
|
||||
export default function GlobalNotFound() {
|
||||
return (
|
||||
<html lang="en" className={inter.className}>
|
||||
<body>
|
||||
<h1>404 - Page Not Found</h1>
|
||||
<p>This page does not exist.</p>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
Unlike `not-found.js`, this file must return a full HTML document, including `<html>` and `<body>` tags.
|
||||
|
||||
## Reference
|
||||
|
||||
### Props
|
||||
|
||||
`not-found.js` or `global-not-found.js` components do not accept any props.
|
||||
|
||||
> **Good to know**: In addition to catching expected `notFound()` errors, the root `app/not-found.js` and `app/global-not-found.js` files handle any unmatched URLs for your whole application. This means users that visit a URL that is not handled by your app will be shown the exported UI.
|
||||
|
||||
## Examples
|
||||
|
||||
### Data Fetching
|
||||
|
||||
By default, `not-found` is a Server Component. You can mark it as `async` to fetch and display data:
|
||||
|
||||
```tsx filename="app/not-found.tsx" switcher
|
||||
import Link from 'next/link'
|
||||
import { headers } from 'next/headers'
|
||||
|
||||
export default async function NotFound() {
|
||||
const headersList = await headers()
|
||||
const domain = headersList.get('host')
|
||||
const data = await getSiteData(domain)
|
||||
return (
|
||||
<div>
|
||||
<h2>Not Found: {data.name}</h2>
|
||||
<p>Could not find requested resource</p>
|
||||
<p>
|
||||
View <Link href="/blog">all posts</Link>
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/not-found.jsx" switcher
|
||||
import Link from 'next/link'
|
||||
import { headers } from 'next/headers'
|
||||
|
||||
export default async function NotFound() {
|
||||
const headersList = await headers()
|
||||
const domain = headersList.get('host')
|
||||
const data = await getSiteData(domain)
|
||||
return (
|
||||
<div>
|
||||
<h2>Not Found: {data.name}</h2>
|
||||
<p>Could not find requested resource</p>
|
||||
<p>
|
||||
View <Link href="/blog">all posts</Link>
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
If you need to use Client Component hooks like `usePathname` to display content based on the path, you must fetch data on the client-side instead.
|
||||
|
||||
### Metadata
|
||||
|
||||
For `global-not-found.js`, you can export a `metadata` object or a [`generateMetadata`](/docs/app/api-reference/functions/generate-metadata) function to customize the `<title>`, `<meta>`, and other head tags for your 404 page:
|
||||
|
||||
> **Good to know**: Next.js automatically injects `<meta name="robots" content="noindex" />` for pages that return a 404 status code, including `global-not-found.js` pages.
|
||||
|
||||
```tsx filename="app/global-not-found.tsx" switcher
|
||||
import type { Metadata } from 'next'
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Not Found',
|
||||
description: 'The page you are looking for does not exist.',
|
||||
}
|
||||
|
||||
export default function GlobalNotFound() {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>
|
||||
<div>
|
||||
<h1>Not Found</h1>
|
||||
<p>The page you are looking for does not exist.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/global-not-found.js" switcher
|
||||
export const metadata = {
|
||||
title: 'Not Found',
|
||||
description: 'The page you are looking for does not exist.',
|
||||
}
|
||||
|
||||
export default function GlobalNotFound() {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>
|
||||
<div>
|
||||
<h1>Not Found</h1>
|
||||
<p>The page you are looking for does not exist.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Version History
|
||||
|
||||
| Version | Changes |
|
||||
| --------- | --------------------------------------------------- |
|
||||
| `v15.4.0` | `global-not-found.js` introduced (experimental). |
|
||||
| `v13.3.0` | Root `app/not-found` handles global unmatched URLs. |
|
||||
| `v13.0.0` | `not-found` introduced. |
|
||||
Generated
Vendored
+240
@@ -0,0 +1,240 @@
|
||||
---
|
||||
title: page.js
|
||||
description: API reference for the page.js file.
|
||||
---
|
||||
|
||||
The `page` file allows you to define UI that is **unique** to a route. You can create a page by default exporting a component from the file:
|
||||
|
||||
```tsx filename="app/blog/[slug]/page.tsx" switcher
|
||||
export default function Page({
|
||||
params,
|
||||
searchParams,
|
||||
}: {
|
||||
params: Promise<{ slug: string }>
|
||||
searchParams: Promise<{ [key: string]: string | string[] | undefined }>
|
||||
}) {
|
||||
return <h1>My Page</h1>
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/blog/[slug]/page.js" switcher
|
||||
export default function Page({ params, searchParams }) {
|
||||
return <h1>My Page</h1>
|
||||
}
|
||||
```
|
||||
|
||||
## Good to know
|
||||
|
||||
- The `.js`, `.jsx`, or `.tsx` file extensions can be used for `page`.
|
||||
- A `page` is always the **leaf** of the route subtree.
|
||||
- A `page` file is required to make a route segment **publicly accessible**.
|
||||
- Pages are [Server Components](https://react.dev/reference/rsc/server-components) by default, but can be set to a [Client Component](https://react.dev/reference/rsc/use-client).
|
||||
- In the [component hierarchy](/docs/app/getting-started/project-structure#component-hierarchy), `page.js` is the innermost file convention. It is wrapped by `loading.js` (Suspense boundary), `error.js` (error boundary), `template.js`, and `layout.js` in the same segment.
|
||||
|
||||
## Reference
|
||||
|
||||
### Props
|
||||
|
||||
#### `params` (optional)
|
||||
|
||||
A promise that resolves to an object containing the [dynamic route parameters](/docs/app/api-reference/file-conventions/dynamic-routes) from the root segment down to that page.
|
||||
|
||||
```tsx filename="app/shop/[slug]/page.tsx" switcher
|
||||
export default async function Page({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ slug: string }>
|
||||
}) {
|
||||
const { slug } = await params
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/shop/[slug]/page.js" switcher
|
||||
export default async function Page({ params }) {
|
||||
const { slug } = await params
|
||||
}
|
||||
```
|
||||
|
||||
| Example Route | URL | `params` |
|
||||
| ------------------------------------ | ----------- | --------------------------------------- |
|
||||
| `app/shop/[slug]/page.js` | `/shop/1` | `Promise<{ slug: '1' }>` |
|
||||
| `app/shop/[category]/[item]/page.js` | `/shop/1/2` | `Promise<{ category: '1', item: '2' }>` |
|
||||
| `app/shop/[...slug]/page.js` | `/shop/1/2` | `Promise<{ slug: ['1', '2'] }>` |
|
||||
|
||||
- Since the `params` prop is a promise, you must use `async/await` or React's [`use`](https://react.dev/reference/react/use) function to access the values.
|
||||
- In version 14 and earlier, `params` was a synchronous prop. To help with backwards compatibility, you can still access it synchronously in Next.js 15, but this behavior will be deprecated in the future.
|
||||
|
||||
#### `searchParams` (optional)
|
||||
|
||||
A promise that resolves to an object containing the [search parameters](https://developer.mozilla.org/docs/Learn/Common_questions/What_is_a_URL#parameters) of the current URL. For example:
|
||||
|
||||
```tsx filename="app/shop/page.tsx" switcher
|
||||
export default async function Page({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: Promise<{ [key: string]: string | string[] | undefined }>
|
||||
}) {
|
||||
const filters = (await searchParams).filters
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/shop/page.js" switcher
|
||||
export default async function Page({ searchParams }) {
|
||||
const filters = (await searchParams).filters
|
||||
}
|
||||
```
|
||||
|
||||
Client Component **pages** can also access `searchParams` using React’s [`use`](https://react.dev/reference/react/use) hook:
|
||||
|
||||
```tsx filename="app/shop/page.tsx" switcher
|
||||
'use client'
|
||||
import { use } from 'react'
|
||||
|
||||
export default function Page({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: Promise<{ [key: string]: string | string[] | undefined }>
|
||||
}) {
|
||||
const filters = use(searchParams).filters
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/page.jsx" switcher
|
||||
'use client'
|
||||
import { use } from 'react'
|
||||
|
||||
export default function Page({ searchParams }) {
|
||||
const filters = use(searchParams).filters
|
||||
}
|
||||
```
|
||||
|
||||
| Example URL | `searchParams` |
|
||||
| --------------- | ----------------------------- |
|
||||
| `/shop?a=1` | `Promise<{ a: '1' }>` |
|
||||
| `/shop?a=1&b=2` | `Promise<{ a: '1', b: '2' }>` |
|
||||
| `/shop?a=1&a=2` | `Promise<{ a: ['1', '2'] }>` |
|
||||
|
||||
- Since the `searchParams` prop is a promise. You must use `async/await` or React's [`use`](https://react.dev/reference/react/use) function to access the values.
|
||||
- In version 14 and earlier, `searchParams` was a synchronous prop. To help with backwards compatibility, you can still access it synchronously in Next.js 15, but this behavior will be deprecated in the future.
|
||||
- `searchParams` is a **[Request-time API](/docs/app/glossary#request-time-apis)** whose values cannot be known ahead of time. Using it will opt the page into **[dynamic rendering](/docs/app/glossary#dynamic-rendering)** at request time.
|
||||
- `searchParams` is a plain JavaScript object, not a `URLSearchParams` instance.
|
||||
|
||||
### Page Props Helper
|
||||
|
||||
You can type pages with `PageProps` to get strongly typed `params` and `searchParams` from the route literal. `PageProps` is a globally available helper.
|
||||
|
||||
```tsx filename="app/blog/[slug]/page.tsx"
|
||||
export default async function Page(props: PageProps<'/blog/[slug]'>) {
|
||||
const { slug } = await props.params
|
||||
const query = await props.searchParams
|
||||
return <h1>Blog Post: {slug}</h1>
|
||||
}
|
||||
```
|
||||
|
||||
> **Good to know**
|
||||
>
|
||||
> - Using a literal route (e.g. `'/blog/[slug]'`) enables autocomplete and strict keys for `params`.
|
||||
> - Static routes resolve `params` to `{}`.
|
||||
> - Types are generated during `next dev`, `next build`, or with `next typegen`.
|
||||
> - After type generation, the `PageProps` helper is globally available. It doesn't need to be imported.
|
||||
|
||||
## Examples
|
||||
|
||||
### Displaying content based on `params`
|
||||
|
||||
Using [dynamic route segments](/docs/app/api-reference/file-conventions/dynamic-routes), you can display or fetch specific content for the page based on the `params` prop.
|
||||
|
||||
```tsx filename="app/blog/[slug]/page.tsx" switcher
|
||||
export default async function Page({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ slug: string }>
|
||||
}) {
|
||||
const { slug } = await params
|
||||
return <h1>Blog Post: {slug}</h1>
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/blog/[slug]/page.js" switcher
|
||||
export default async function Page({ params }) {
|
||||
const { slug } = await params
|
||||
return <h1>Blog Post: {slug}</h1>
|
||||
}
|
||||
```
|
||||
|
||||
### Handling filtering with `searchParams`
|
||||
|
||||
You can use the `searchParams` prop to handle filtering, pagination, or sorting based on the query string of the URL.
|
||||
|
||||
```tsx filename="app/shop/page.tsx" switcher
|
||||
export default async function Page({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: Promise<{ [key: string]: string | string[] | undefined }>
|
||||
}) {
|
||||
const { page = '1', sort = 'asc', query = '' } = await searchParams
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Product Listing</h1>
|
||||
<p>Search query: {query}</p>
|
||||
<p>Current page: {page}</p>
|
||||
<p>Sort order: {sort}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/shop/page.js" switcher
|
||||
export default async function Page({ searchParams }) {
|
||||
const { page = '1', sort = 'asc', query = '' } = await searchParams
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Product Listing</h1>
|
||||
<p>Search query: {query}</p>
|
||||
<p>Current page: {page}</p>
|
||||
<p>Sort order: {sort}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Reading `searchParams` and `params` in Client Components
|
||||
|
||||
To use `searchParams` and `params` in a Client Component (which cannot be `async`), you can use React's [`use`](https://react.dev/reference/react/use) function to read the promise:
|
||||
|
||||
```tsx filename="app/page.tsx" switcher
|
||||
'use client'
|
||||
|
||||
import { use } from 'react'
|
||||
|
||||
export default function Page({
|
||||
params,
|
||||
searchParams,
|
||||
}: {
|
||||
params: Promise<{ slug: string }>
|
||||
searchParams: Promise<{ [key: string]: string | string[] | undefined }>
|
||||
}) {
|
||||
const { slug } = use(params)
|
||||
const { query } = use(searchParams)
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="app/page.js" switcher
|
||||
'use client'
|
||||
|
||||
import { use } from 'react'
|
||||
|
||||
export default function Page({ params, searchParams }) {
|
||||
const { slug } = use(params)
|
||||
const { query } = use(searchParams)
|
||||
}
|
||||
```
|
||||
|
||||
## Version History
|
||||
|
||||
| Version | Changes |
|
||||
| ------------ | ---------------------------------------------------------------------------------------------------------------- |
|
||||
| `v15.0.0-RC` | `params` and `searchParams` are now promises. A [codemod](/docs/app/guides/upgrading/codemods#150) is available. |
|
||||
| `v13.0.0` | `page` introduced. |
|
||||
Generated
Vendored
+480
@@ -0,0 +1,480 @@
|
||||
---
|
||||
title: Parallel Routes
|
||||
description: Simultaneously render one or more pages in the same view that can be navigated independently. A pattern for highly dynamic applications.
|
||||
related:
|
||||
links:
|
||||
- app/api-reference/file-conventions/default
|
||||
---
|
||||
|
||||
Parallel Routes allows you to simultaneously or conditionally render one or more pages within the same layout. They are useful for highly dynamic sections of an app, such as dashboards and feeds on social sites.
|
||||
|
||||
For example, considering a dashboard, you can use parallel routes to simultaneously render the `team` and `analytics` pages:
|
||||
|
||||
<Image
|
||||
alt="Parallel Routes Diagram"
|
||||
srcLight="/docs/light/parallel-routes.png"
|
||||
srcDark="/docs/dark/parallel-routes.png"
|
||||
width="1600"
|
||||
height="942"
|
||||
/>
|
||||
|
||||
## Convention
|
||||
|
||||
### Slots
|
||||
|
||||
Parallel routes are created using named **slots**. Slots are defined with the `@folder` convention. For example, the following file structure defines two slots: `@analytics` and `@team`:
|
||||
|
||||
<Image
|
||||
alt="Parallel Routes File-system Structure"
|
||||
srcLight="/docs/light/parallel-routes-file-system.png"
|
||||
srcDark="/docs/dark/parallel-routes-file-system.png"
|
||||
width="1600"
|
||||
height="687"
|
||||
/>
|
||||
|
||||
Slots are passed as props to the shared parent layout. For the example above, the component in `app/layout.js` now accepts the `@analytics` and `@team` slots props, and can render them in parallel alongside the `children` prop:
|
||||
|
||||
```tsx filename="app/layout.tsx" switcher
|
||||
export default function Layout({
|
||||
children,
|
||||
team,
|
||||
analytics,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
analytics: React.ReactNode
|
||||
team: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
{children}
|
||||
{team}
|
||||
{analytics}
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/layout.js" switcher
|
||||
export default function Layout({ children, team, analytics }) {
|
||||
return (
|
||||
<>
|
||||
{children}
|
||||
{team}
|
||||
{analytics}
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
However, slots are **not** route segments and do not affect the URL structure. For example, for `/@analytics/views`, the URL will be `/views` since `@analytics` is a slot. Slots are combined with the regular [Page](/docs/app/api-reference/file-conventions/page) component to form the final page associated with the route segment. Because of this, you cannot have separate [prerendered](/docs/app/glossary#prerendering) and [dynamically rendered](/docs/app/glossary#dynamic-rendering) slots at the same route segment level. If one slot is dynamic, all slots at that level must be dynamic.
|
||||
|
||||
> **Good to know**:
|
||||
>
|
||||
> - The `children` prop is an implicit slot that does not need to be mapped to a folder. This means `app/page.js` is equivalent to `app/@children/page.js`.
|
||||
|
||||
### `default.js`
|
||||
|
||||
You can define a `default.js` file to render as a fallback for unmatched slots during the initial load or full-page reload.
|
||||
|
||||
Consider the following folder structure. The `@team` slot has a `/settings` page, but `@analytics` does not.
|
||||
|
||||
<Image
|
||||
alt="Parallel Routes unmatched routes"
|
||||
srcLight="/docs/light/parallel-routes-unmatched-routes.png"
|
||||
srcDark="/docs/dark/parallel-routes-unmatched-routes.png"
|
||||
width="1600"
|
||||
height="930"
|
||||
/>
|
||||
|
||||
When navigating to `/settings`, the `@team` slot will render the `/settings` page while maintaining the currently active page for the `@analytics` slot.
|
||||
|
||||
On refresh, Next.js will render a `default.js` for `@analytics`. If `default.js` doesn't exist, a `404` is rendered instead.
|
||||
|
||||
Additionally, since `children` is an implicit slot, you also need to create a `default.js` file to render a fallback for `children` when Next.js cannot recover the active state of the parent page.
|
||||
|
||||
## Behavior
|
||||
|
||||
By default, Next.js keeps track of the active _state_ (or subpage) for each slot. However, the content rendered within a slot will depend on the type of navigation:
|
||||
|
||||
- [**Soft Navigation**](/docs/app/getting-started/linking-and-navigating#client-side-transitions): During client-side navigation, Next.js will perform a [partial render](/docs/app/getting-started/linking-and-navigating#client-side-transitions), changing the subpage within the slot, while maintaining the other slot's active subpages, even if they don't match the current URL.
|
||||
- **Hard Navigation**: After a full-page load (browser refresh), Next.js cannot determine the active state for the slots that don't match the current URL. Instead, it will render a [`default.js`](#defaultjs) file for the unmatched slots, or `404` if `default.js` doesn't exist.
|
||||
|
||||
> **Good to know**:
|
||||
>
|
||||
> - The `404` for unmatched routes helps ensure that you don't accidentally render a parallel route on a page that it was not intended for.
|
||||
|
||||
## Examples
|
||||
|
||||
### With `useSelectedLayoutSegment(s)`
|
||||
|
||||
Both [`useSelectedLayoutSegment`](/docs/app/api-reference/functions/use-selected-layout-segment) and [`useSelectedLayoutSegments`](/docs/app/api-reference/functions/use-selected-layout-segments) accept a `parallelRoutesKey` parameter, which allows you to read the active route segment within a slot.
|
||||
|
||||
```tsx filename="app/layout.tsx" switcher
|
||||
'use client'
|
||||
|
||||
import { useSelectedLayoutSegment } from 'next/navigation'
|
||||
|
||||
export default function Layout({ auth }: { auth: React.ReactNode }) {
|
||||
const loginSegment = useSelectedLayoutSegment('auth')
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/layout.js" switcher
|
||||
'use client'
|
||||
|
||||
import { useSelectedLayoutSegment } from 'next/navigation'
|
||||
|
||||
export default function Layout({ auth }) {
|
||||
const loginSegment = useSelectedLayoutSegment('auth')
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
When a user navigates to `app/@auth/login` (or `/login` in the URL bar), `loginSegment` will be equal to the string `"login"`.
|
||||
|
||||
### Conditional Routes
|
||||
|
||||
You can use Parallel Routes to conditionally render routes based on certain conditions, such as user role. For example, to render a different dashboard page for the `/admin` or `/user` roles:
|
||||
|
||||
<Image
|
||||
alt="Conditional routes diagram"
|
||||
srcLight="/docs/light/conditional-routes-ui.png"
|
||||
srcDark="/docs/dark/conditional-routes-ui.png"
|
||||
width="1600"
|
||||
height="898"
|
||||
/>
|
||||
|
||||
```tsx filename="app/dashboard/layout.tsx" switcher
|
||||
import { checkUserRole } from '@/lib/auth'
|
||||
|
||||
export default function Layout({
|
||||
user,
|
||||
admin,
|
||||
}: {
|
||||
user: React.ReactNode
|
||||
admin: React.ReactNode
|
||||
}) {
|
||||
const role = checkUserRole()
|
||||
return role === 'admin' ? admin : user
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/dashboard/layout.js" switcher
|
||||
import { checkUserRole } from '@/lib/auth'
|
||||
|
||||
export default function Layout({ user, admin }) {
|
||||
const role = checkUserRole()
|
||||
return role === 'admin' ? admin : user
|
||||
}
|
||||
```
|
||||
|
||||
### Tab Groups
|
||||
|
||||
You can add a `layout` inside a slot to allow users to navigate the slot independently. This is useful for creating tabs.
|
||||
|
||||
For example, the `@analytics` slot has two subpages: `/page-views` and `/visitors`.
|
||||
|
||||
<Image
|
||||
alt="Analytics slot with two subpages and a layout"
|
||||
srcLight="/docs/light/parallel-routes-tab-groups.png"
|
||||
srcDark="/docs/dark/parallel-routes-tab-groups.png"
|
||||
width="1600"
|
||||
height="768"
|
||||
/>
|
||||
|
||||
Within `@analytics`, create a [`layout`](/docs/app/api-reference/file-conventions/layout) file to share the tabs between the two pages:
|
||||
|
||||
```tsx filename="app/@analytics/layout.tsx" switcher
|
||||
import Link from 'next/link'
|
||||
|
||||
export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<>
|
||||
<nav>
|
||||
<Link href="/page-views">Page Views</Link>
|
||||
<Link href="/visitors">Visitors</Link>
|
||||
</nav>
|
||||
<div>{children}</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/@analytics/layout.js" switcher
|
||||
import Link from 'next/link'
|
||||
|
||||
export default function Layout({ children }) {
|
||||
return (
|
||||
<>
|
||||
<nav>
|
||||
<Link href="/page-views">Page Views</Link>
|
||||
<Link href="/visitors">Visitors</Link>
|
||||
</nav>
|
||||
<div>{children}</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Modals
|
||||
|
||||
Parallel Routes can be used together with [Intercepting Routes](/docs/app/api-reference/file-conventions/intercepting-routes) to create modals that support deep linking. This allows you to solve common challenges when building modals, such as:
|
||||
|
||||
- Making the modal content **shareable through a URL**.
|
||||
- **Preserving context** when the page is refreshed, instead of closing the modal.
|
||||
- **Closing the modal on backwards navigation** rather than going to the previous route.
|
||||
- **Reopening the modal on forwards navigation**.
|
||||
|
||||
Consider the following UI pattern, where a user can open a login modal from a layout using client-side navigation, or access a separate `/login` page:
|
||||
|
||||
<Image
|
||||
alt="Parallel Routes Diagram"
|
||||
srcLight="/docs/light/parallel-routes-auth-modal.png"
|
||||
srcDark="/docs/dark/parallel-routes-auth-modal.png"
|
||||
width="1600"
|
||||
height="687"
|
||||
/>
|
||||
|
||||
To implement this pattern, start by creating a `/login` route that renders your **main** login page.
|
||||
|
||||
<Image
|
||||
alt="Parallel Routes Diagram"
|
||||
srcLight="/docs/light/parallel-routes-modal-login-page.png"
|
||||
srcDark="/docs/dark/parallel-routes-modal-login-page.png"
|
||||
width="1600"
|
||||
height="768"
|
||||
/>
|
||||
|
||||
```tsx filename="app/login/page.tsx" switcher
|
||||
import { Login } from '@/app/ui/login'
|
||||
|
||||
export default function Page() {
|
||||
return <Login />
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/login/page.js" switcher
|
||||
import { Login } from '@/app/ui/login'
|
||||
|
||||
export default function Page() {
|
||||
return <Login />
|
||||
}
|
||||
```
|
||||
|
||||
Then, inside the `@auth` slot, add [`default.js`](/docs/app/api-reference/file-conventions/default) file that returns `null`. This ensures that the modal is not rendered when it's not active.
|
||||
|
||||
```tsx filename="app/@auth/default.tsx" switcher
|
||||
export default function Default() {
|
||||
return null
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/@auth/default.js" switcher
|
||||
export default function Default() {
|
||||
return null
|
||||
}
|
||||
```
|
||||
|
||||
Inside your `@auth` slot, intercept the `/login` route by importing the `<Modal>` component and its children into the `@auth/(.)login/page.tsx` file, and updating the folder name to `/@auth/(.)login/page.tsx`.
|
||||
|
||||
```tsx filename="app/@auth/(.)login/page.tsx" switcher
|
||||
import { Modal } from '@/app/ui/modal'
|
||||
import { Login } from '@/app/ui/login'
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<Modal>
|
||||
<Login />
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/@auth/(.)login/page.js" switcher
|
||||
import { Modal } from '@/app/ui/modal'
|
||||
import { Login } from '@/app/ui/login'
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<Modal>
|
||||
<Login />
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
> **Good to know:**
|
||||
>
|
||||
> - The convention `(.)` is used for intercepting routes. See [Intercepting Routes](/docs/app/api-reference/file-conventions/intercepting-routes#convention) docs for more information.
|
||||
> - By separating the `<Modal>` functionality from the modal content (`<Login>`), you can ensure any content inside the modal, e.g. [forms](/docs/app/guides/forms), are Server Components. See [Interleaving Client and Server Components](/docs/app/getting-started/server-and-client-components#interleaving-server-and-client-components) for more information.
|
||||
|
||||
#### Opening the modal
|
||||
|
||||
Now, you can leverage the Next.js router to open and close the modal. This ensures the URL is correctly updated when the modal is open, and when navigating backwards and forwards.
|
||||
|
||||
To open the modal, pass the `@auth` slot as a prop to the parent layout and render it alongside the `children` prop.
|
||||
|
||||
```tsx filename="app/layout.tsx" switcher
|
||||
import Link from 'next/link'
|
||||
|
||||
export default function Layout({
|
||||
auth,
|
||||
children,
|
||||
}: {
|
||||
auth: React.ReactNode
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
<nav>
|
||||
<Link href="/login">Open modal</Link>
|
||||
</nav>
|
||||
<div>{auth}</div>
|
||||
<div>{children}</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/layout.js" switcher
|
||||
import Link from 'next/link'
|
||||
|
||||
export default function Layout({ auth, children }) {
|
||||
return (
|
||||
<>
|
||||
<nav>
|
||||
<Link href="/login">Open modal</Link>
|
||||
</nav>
|
||||
<div>{auth}</div>
|
||||
<div>{children}</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
When the user clicks the `<Link>`, the modal will open instead of navigating to the `/login` page. However, on refresh or initial load, navigating to `/login` will take the user to the main login page.
|
||||
|
||||
#### Closing the modal
|
||||
|
||||
You can close the modal by calling `router.back()` or by using the `Link` component.
|
||||
|
||||
```tsx filename="app/ui/modal.tsx" switcher
|
||||
'use client'
|
||||
|
||||
import { useRouter } from 'next/navigation'
|
||||
|
||||
export function Modal({ children }: { children: React.ReactNode }) {
|
||||
const router = useRouter()
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
onClick={() => {
|
||||
router.back()
|
||||
}}
|
||||
>
|
||||
Close modal
|
||||
</button>
|
||||
<div>{children}</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/ui/modal.js" switcher
|
||||
'use client'
|
||||
|
||||
import { useRouter } from 'next/navigation'
|
||||
|
||||
export function Modal({ children }) {
|
||||
const router = useRouter()
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
onClick={() => {
|
||||
router.back()
|
||||
}}
|
||||
>
|
||||
Close modal
|
||||
</button>
|
||||
<div>{children}</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
When using the `Link` component to navigate away from a page that shouldn't render the `@auth` slot anymore, we need to make sure the parallel route matches to a component that returns `null`. For example, when navigating back to the root page, we create a `@auth/page.tsx` component:
|
||||
|
||||
```tsx filename="app/ui/modal.tsx" switcher
|
||||
import Link from 'next/link'
|
||||
|
||||
export function Modal({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<>
|
||||
<Link href="/">Close modal</Link>
|
||||
<div>{children}</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/ui/modal.js" switcher
|
||||
import Link from 'next/link'
|
||||
|
||||
export function Modal({ children }) {
|
||||
return (
|
||||
<>
|
||||
<Link href="/">Close modal</Link>
|
||||
<div>{children}</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```tsx filename="app/@auth/page.tsx" switcher
|
||||
export default function Page() {
|
||||
return null
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/@auth/page.js" switcher
|
||||
export default function Page() {
|
||||
return null
|
||||
}
|
||||
```
|
||||
|
||||
Or if navigating to any other page (such as `/foo`, `/foo/bar`, etc), you can use a catch-all slot:
|
||||
|
||||
```tsx filename="app/@auth/[...catchAll]/page.tsx" switcher
|
||||
export default function CatchAll() {
|
||||
return null
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/@auth/[...catchAll]/page.js" switcher
|
||||
export default function CatchAll() {
|
||||
return null
|
||||
}
|
||||
```
|
||||
|
||||
> **Good to know:**
|
||||
>
|
||||
> - We use a catch-all route in our `@auth` slot to close the modal because of how parallel routes behave. Since client-side navigations to a route that no longer match the slot will remain visible, we need to match the slot to a route that returns `null` to close the modal.
|
||||
> - Other examples could include opening a photo modal in a gallery while also having a dedicated `/photo/[id]` page, or opening a shopping cart in a side modal.
|
||||
> - [View an example](https://github.com/vercel-labs/nextgram) of modals with Intercepted and Parallel Routes.
|
||||
|
||||
### Loading and Error UI
|
||||
|
||||
Parallel Routes can be streamed independently, allowing you to define independent error and loading states for each route:
|
||||
|
||||
<Image
|
||||
alt="Parallel routes enable custom error and loading states"
|
||||
srcLight="/docs/light/parallel-routes-cinematic-universe.png"
|
||||
srcDark="/docs/dark/parallel-routes-cinematic-universe.png"
|
||||
width="1600"
|
||||
height="1218"
|
||||
/>
|
||||
|
||||
See the [Loading UI](/docs/app/api-reference/file-conventions/loading) and [Error Handling](/docs/app/getting-started/error-handling) documentation for more information.
|
||||
Generated
Vendored
+777
@@ -0,0 +1,777 @@
|
||||
---
|
||||
title: proxy.js
|
||||
description: API reference for the proxy.js file.
|
||||
related:
|
||||
title: Learn more about Proxy
|
||||
links:
|
||||
- app/api-reference/functions/next-request
|
||||
- app/api-reference/functions/next-response
|
||||
---
|
||||
|
||||
> **Note**: The `middleware` file convention is deprecated and has been renamed to `proxy`. See [Migration to Proxy](#migration-to-proxy) for more details.
|
||||
|
||||
The `proxy.js|ts` file is used to write [Proxy](/docs/app/getting-started/proxy) and run code on the server before a request is completed. Then, based on the incoming request, you can modify the response by rewriting, redirecting, modifying the request or response headers, or responding directly.
|
||||
|
||||
Proxy executes before routes are rendered. It's particularly useful for implementing custom server-side logic like authentication, logging, or handling redirects.
|
||||
|
||||
> **Good to know**:
|
||||
>
|
||||
> Proxy is meant to be invoked separately of your render code and in optimized cases deployed to your CDN for fast redirect/rewrite handling, you should not attempt relying on shared modules or globals.
|
||||
>
|
||||
> To pass information from Proxy to your application, use [headers](#setting-headers), [cookies](#using-cookies), [rewrites](/docs/app/api-reference/functions/next-response#rewrite), [redirects](/docs/app/api-reference/functions/next-response#redirect), or the URL.
|
||||
|
||||
Create a `proxy.ts` (or `.js`) file in the project root, or inside `src` if applicable, so that it is located at the same level as `pages` or `app`.
|
||||
|
||||
If you’ve customized [`pageExtensions`](/docs/app/api-reference/config/next-config-js/pageExtensions), for example to `.page.ts` or `.page.js`, name your file `proxy.page.ts` or `proxy.page.js` accordingly.
|
||||
|
||||
```tsx filename="proxy.ts" switcher
|
||||
import { NextResponse } from 'next/server'
|
||||
import type { NextRequest } from 'next/server'
|
||||
|
||||
// This function can be marked `async` if using `await` inside
|
||||
export function proxy(request: NextRequest) {
|
||||
return NextResponse.redirect(new URL('/home', request.url))
|
||||
}
|
||||
|
||||
export const config = {
|
||||
matcher: '/about/:path*',
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="proxy.js" switcher
|
||||
import { NextResponse } from 'next/server'
|
||||
|
||||
// This function can be marked `async` if using `await` inside
|
||||
export function proxy(request) {
|
||||
return NextResponse.redirect(new URL('/home', request.url))
|
||||
}
|
||||
|
||||
export const config = {
|
||||
matcher: '/about/:path*',
|
||||
}
|
||||
```
|
||||
|
||||
## Exports
|
||||
|
||||
### Proxy function
|
||||
|
||||
The file must export a single function, either as a default export or named `proxy`. Note that multiple proxy from the same file are not supported.
|
||||
|
||||
```js filename="proxy.js"
|
||||
// Example of default export
|
||||
export default function proxy(request) {
|
||||
// Proxy logic
|
||||
}
|
||||
```
|
||||
|
||||
### Config object (optional)
|
||||
|
||||
Optionally, a config object can be exported alongside the Proxy function. This object includes the [matcher](#matcher) to specify paths where the Proxy applies.
|
||||
|
||||
### Matcher
|
||||
|
||||
The `matcher` option allows you to target specific paths for the Proxy to run on. You can specify these paths in several ways:
|
||||
|
||||
- For a single path: Directly use a string to define the path, like `'/about'`.
|
||||
- For multiple paths: Use an array to list multiple paths, such as `matcher: ['/about', '/contact']`, which applies the Proxy to both `/about` and `/contact`.
|
||||
|
||||
```js filename="proxy.js"
|
||||
export const config = {
|
||||
matcher: ['/about/:path*', '/dashboard/:path*'],
|
||||
}
|
||||
```
|
||||
|
||||
Additionally, the `matcher` option supports complex path specifications using regular expressions. For example, you can exclude certain paths with a regular expression matcher:
|
||||
|
||||
```js filename="proxy.js"
|
||||
export const config = {
|
||||
matcher: [
|
||||
// Exclude API routes, static files, image optimizations, and .png files
|
||||
'/((?!api|_next/static|_next/image|.*\\.png$).*)',
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
This enables precise control over which paths to include or exclude.
|
||||
|
||||
The `matcher` option accepts an array of objects with the following keys:
|
||||
|
||||
- `source`: The path or pattern used to match the request paths. It can be a string for direct path matching or a pattern for more complex matching.
|
||||
- `locale` (optional): A boolean that, when set to `false`, ignores locale-based routing in path matching.
|
||||
- `has` (optional): Specifies conditions based on the presence of specific request elements such as headers, query parameters, or cookies.
|
||||
- `missing` (optional): Focuses on conditions where certain request elements are absent, like missing headers or cookies.
|
||||
|
||||
```js filename="proxy.js"
|
||||
export const config = {
|
||||
matcher: [
|
||||
{
|
||||
source: '/api/:path*',
|
||||
locale: false,
|
||||
has: [
|
||||
{ type: 'header', key: 'Authorization', value: 'Bearer Token' },
|
||||
{ type: 'query', key: 'userId', value: '123' },
|
||||
],
|
||||
missing: [{ type: 'cookie', key: 'session', value: 'active' }],
|
||||
},
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
The `source` path patterns:
|
||||
|
||||
1. MUST start with `/`
|
||||
2. Can include named parameters: `/about/:path` matches `/about/a` and `/about/b` but not `/about/a/c`
|
||||
3. Can have modifiers on named parameters (starting with `:`): `/about/:path*` matches `/about/a/b/c` because `*` is _zero or more_. `?` is _zero or one_ and `+` _one or more_
|
||||
4. Can use regular expression enclosed in parenthesis: `/about/(.*)` is the same as `/about/:path*`
|
||||
5. Are anchored to the start of the path: `/about` matches `/about` and `/about/team` but not `/blog/about`
|
||||
|
||||
Read more details on [path-to-regexp](https://github.com/pillarjs/path-to-regexp#path-to-regexp-1) documentation.
|
||||
|
||||
> **Good to know**:
|
||||
>
|
||||
> - The `matcher` values need to be constants so they can be statically analyzed at build-time. Dynamic values such as variables will be ignored.
|
||||
> - For backward compatibility, Next.js always considers `/public` as `/public/index`. Therefore, a matcher of `/public/:path` will match.
|
||||
|
||||
## Params
|
||||
|
||||
### `request`
|
||||
|
||||
When defining Proxy, the default export function accepts a single parameter, `request`. This parameter is an instance of `NextRequest`, which represents the incoming HTTP request.
|
||||
|
||||
```tsx filename="proxy.ts" switcher
|
||||
import type { NextRequest } from 'next/server'
|
||||
|
||||
export function proxy(request: NextRequest) {
|
||||
// Proxy logic goes here
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="proxy.js" switcher
|
||||
export function proxy(request) {
|
||||
// Proxy logic goes here
|
||||
}
|
||||
```
|
||||
|
||||
If you prefer a shorthand, you can use the `NextProxy` type. It infers the parameter types for both `request` (`NextRequest`) and `event` (`NextFetchEvent`) automatically:
|
||||
|
||||
```tsx filename="proxy.ts"
|
||||
import type { NextProxy } from 'next/server'
|
||||
|
||||
export const proxy: NextProxy = (request, event) => {
|
||||
event.waitUntil(Promise.resolve())
|
||||
return Response.json({ pathname: request.nextUrl.pathname })
|
||||
}
|
||||
```
|
||||
|
||||
> **Good to know**:
|
||||
>
|
||||
> - `NextRequest` is a type that represents incoming HTTP requests in Next.js Proxy, whereas [`NextResponse`](#nextresponse) is a class used to manipulate and send back HTTP responses.
|
||||
|
||||
## NextResponse
|
||||
|
||||
The `NextResponse` API allows you to:
|
||||
|
||||
- `redirect` the incoming request to a different URL
|
||||
- `rewrite` the response by displaying a given URL
|
||||
- Set request headers for API Routes, `getServerSideProps`, and `rewrite` destinations
|
||||
- Set response cookies
|
||||
- Set response headers
|
||||
|
||||
<AppOnly>
|
||||
|
||||
To produce a response from Proxy, you can:
|
||||
|
||||
1. `rewrite` to a route ([Page](/docs/app/api-reference/file-conventions/page) or [Route Handler](/docs/app/api-reference/file-conventions/route)) that produces a response
|
||||
2. return a `NextResponse` directly. See [Producing a Response](#producing-a-response)
|
||||
|
||||
> **Good to know**: For redirects, you can also use `Response.redirect` instead of `NextResponse.redirect`.
|
||||
|
||||
</AppOnly>
|
||||
|
||||
<PagesOnly>
|
||||
|
||||
To produce a response from Proxy, you can:
|
||||
|
||||
1. `rewrite` to a route ([Page](/docs/pages/building-your-application/routing/pages-and-layouts) or [Edge API Route](/docs/pages/building-your-application/routing/api-routes)) that produces a response
|
||||
2. return a `NextResponse` directly. See [Producing a Response](#producing-a-response)
|
||||
|
||||
</PagesOnly>
|
||||
|
||||
## Execution order
|
||||
|
||||
Proxy will be invoked for **every route in your project**. Given this, it's crucial to use [matchers](#matcher) to precisely target or exclude specific routes. The following is the execution order:
|
||||
|
||||
1. `headers` from `next.config.js`
|
||||
2. `redirects` from `next.config.js`
|
||||
3. Proxy (`rewrites`, `redirects`, etc.)
|
||||
4. `beforeFiles` (`rewrites`) from `next.config.js`
|
||||
5. Filesystem routes (`public/`, `_next/static/`, `pages/`, `app/`, etc.)
|
||||
6. `afterFiles` (`rewrites`) from `next.config.js`
|
||||
7. Dynamic Routes (`/blog/[slug]`)
|
||||
8. `fallback` (`rewrites`) from `next.config.js`
|
||||
|
||||
> **Good to know:** [Server Functions](/docs/app/api-reference/directives/use-server) are not separate routes in this chain. They are handled as POST requests to the route where they are used, so a Proxy matcher that excludes a path will also skip Server Function calls on that path.
|
||||
>
|
||||
> A matcher change or a refactor that moves a Server Function to a different route can silently remove Proxy coverage. Always verify authentication and authorization inside each Server Function rather than relying on Proxy alone. See the [Data Security guide](/docs/app/guides/data-security#authentication-and-authorization) for recommended patterns.
|
||||
|
||||
## Runtime
|
||||
|
||||
Proxy defaults to using the Node.js runtime. The [`runtime`](/docs/app/api-reference/file-conventions/route-segment-config/runtime) config option is not available in Proxy files. Setting the `runtime` config option in Proxy will throw an error.
|
||||
|
||||
## Advanced Proxy flags
|
||||
|
||||
In `v13.1` of Next.js two additional flags were introduced for proxy, `skipProxyUrlNormalize` (formerly `skipMiddlewareUrlNormalize`) and `skipTrailingSlashRedirect` to handle advanced use cases.
|
||||
|
||||
`skipTrailingSlashRedirect` disables Next.js redirects for adding or removing trailing slashes. This allows custom handling inside proxy to maintain the trailing slash for some paths but not others, which can make incremental migrations easier.
|
||||
|
||||
```js filename="next.config.js"
|
||||
module.exports = {
|
||||
skipTrailingSlashRedirect: true,
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="proxy.js"
|
||||
const legacyPrefixes = ['/docs', '/blog']
|
||||
|
||||
export default async function proxy(req) {
|
||||
const { pathname } = req.nextUrl
|
||||
|
||||
if (legacyPrefixes.some((prefix) => pathname.startsWith(prefix))) {
|
||||
return NextResponse.next()
|
||||
}
|
||||
|
||||
// apply trailing slash handling
|
||||
if (
|
||||
!pathname.endsWith('/') &&
|
||||
!pathname.match(/((?!\.well-known(?:\/.*)?)(?:[^/]+\/)*[^/]+\.\w+)/)
|
||||
) {
|
||||
return NextResponse.redirect(
|
||||
new URL(`${req.nextUrl.pathname}/`, req.nextUrl)
|
||||
)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`skipProxyUrlNormalize` allows for disabling the URL normalization in Next.js to make handling direct visits and client-transitions the same. In some advanced cases, this option provides full control by using the original URL.
|
||||
|
||||
```js filename="next.config.js"
|
||||
module.exports = {
|
||||
skipProxyUrlNormalize: true,
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="proxy.js"
|
||||
export default async function proxy(req) {
|
||||
const { pathname } = req.nextUrl
|
||||
|
||||
// GET /_next/data/build-id/hello.json
|
||||
|
||||
console.log(pathname)
|
||||
// with the flag this now /_next/data/build-id/hello.json
|
||||
// without the flag this would be normalized to /hello
|
||||
}
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Conditional Statements
|
||||
|
||||
```ts filename="proxy.ts" switcher
|
||||
import { NextResponse } from 'next/server'
|
||||
import type { NextRequest } from 'next/server'
|
||||
|
||||
export function proxy(request: NextRequest) {
|
||||
if (request.nextUrl.pathname.startsWith('/about')) {
|
||||
return NextResponse.rewrite(new URL('/about-2', request.url))
|
||||
}
|
||||
|
||||
if (request.nextUrl.pathname.startsWith('/dashboard')) {
|
||||
return NextResponse.rewrite(new URL('/dashboard/user', request.url))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="proxy.js" switcher
|
||||
import { NextResponse } from 'next/server'
|
||||
|
||||
export function proxy(request) {
|
||||
if (request.nextUrl.pathname.startsWith('/about')) {
|
||||
return NextResponse.rewrite(new URL('/about-2', request.url))
|
||||
}
|
||||
|
||||
if (request.nextUrl.pathname.startsWith('/dashboard')) {
|
||||
return NextResponse.rewrite(new URL('/dashboard/user', request.url))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Using Cookies
|
||||
|
||||
Cookies are regular headers. On a `Request`, they are stored in the `Cookie` header. On a `Response` they are in the `Set-Cookie` header. Next.js provides a convenient way to access and manipulate these cookies through the `cookies` extension on `NextRequest` and `NextResponse`.
|
||||
|
||||
1. For incoming requests, `cookies` comes with the following methods: `get`, `getAll`, `set`, and `delete` cookies. You can check for the existence of a cookie with `has` or remove all cookies with `clear`.
|
||||
2. For outgoing responses, `cookies` have the following methods `get`, `getAll`, `set`, and `delete`.
|
||||
|
||||
```ts filename="proxy.ts" switcher
|
||||
import { NextResponse } from 'next/server'
|
||||
import type { NextRequest } from 'next/server'
|
||||
|
||||
export function proxy(request: NextRequest) {
|
||||
// Assume a "Cookie:nextjs=fast" header to be present on the incoming request
|
||||
// Getting cookies from the request using the `RequestCookies` API
|
||||
let cookie = request.cookies.get('nextjs')
|
||||
console.log(cookie) // => { name: 'nextjs', value: 'fast', Path: '/' }
|
||||
const allCookies = request.cookies.getAll()
|
||||
console.log(allCookies) // => [{ name: 'nextjs', value: 'fast' }]
|
||||
|
||||
request.cookies.has('nextjs') // => true
|
||||
request.cookies.delete('nextjs')
|
||||
request.cookies.has('nextjs') // => false
|
||||
|
||||
// Setting cookies on the response using the `ResponseCookies` API
|
||||
const response = NextResponse.next()
|
||||
response.cookies.set('vercel', 'fast')
|
||||
response.cookies.set({
|
||||
name: 'vercel',
|
||||
value: 'fast',
|
||||
path: '/',
|
||||
})
|
||||
cookie = response.cookies.get('vercel')
|
||||
console.log(cookie) // => { name: 'vercel', value: 'fast', Path: '/' }
|
||||
// The outgoing response will have a `Set-Cookie:vercel=fast;path=/` header.
|
||||
|
||||
return response
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="proxy.js" switcher
|
||||
import { NextResponse } from 'next/server'
|
||||
|
||||
export function proxy(request) {
|
||||
// Assume a "Cookie:nextjs=fast" header to be present on the incoming request
|
||||
// Getting cookies from the request using the `RequestCookies` API
|
||||
let cookie = request.cookies.get('nextjs')
|
||||
console.log(cookie) // => { name: 'nextjs', value: 'fast', Path: '/' }
|
||||
const allCookies = request.cookies.getAll()
|
||||
console.log(allCookies) // => [{ name: 'nextjs', value: 'fast' }]
|
||||
|
||||
request.cookies.has('nextjs') // => true
|
||||
request.cookies.delete('nextjs')
|
||||
request.cookies.has('nextjs') // => false
|
||||
|
||||
// Setting cookies on the response using the `ResponseCookies` API
|
||||
const response = NextResponse.next()
|
||||
response.cookies.set('vercel', 'fast')
|
||||
response.cookies.set({
|
||||
name: 'vercel',
|
||||
value: 'fast',
|
||||
path: '/',
|
||||
})
|
||||
cookie = response.cookies.get('vercel')
|
||||
console.log(cookie) // => { name: 'vercel', value: 'fast', Path: '/' }
|
||||
// The outgoing response will have a `Set-Cookie:vercel=fast;path=/` header.
|
||||
|
||||
return response
|
||||
}
|
||||
```
|
||||
|
||||
### Setting Headers
|
||||
|
||||
You can set request and response headers using the `NextResponse` API (setting _request_ headers is available since Next.js v13.0.0).
|
||||
|
||||
```ts filename="proxy.ts" switcher
|
||||
import { NextResponse } from 'next/server'
|
||||
import type { NextRequest } from 'next/server'
|
||||
|
||||
export function proxy(request: NextRequest) {
|
||||
// Clone the request headers and set a new header `x-hello-from-proxy1`
|
||||
const requestHeaders = new Headers(request.headers)
|
||||
requestHeaders.set('x-hello-from-proxy1', 'hello')
|
||||
|
||||
// You can also set request headers in NextResponse.next
|
||||
const response = NextResponse.next({
|
||||
request: {
|
||||
// New request headers
|
||||
headers: requestHeaders,
|
||||
},
|
||||
})
|
||||
|
||||
// Set a new response header `x-hello-from-proxy2`
|
||||
response.headers.set('x-hello-from-proxy2', 'hello')
|
||||
return response
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="proxy.js" switcher
|
||||
import { NextResponse } from 'next/server'
|
||||
|
||||
export function proxy(request) {
|
||||
// Clone the request headers and set a new header `x-hello-from-proxy1`
|
||||
const requestHeaders = new Headers(request.headers)
|
||||
requestHeaders.set('x-hello-from-proxy1', 'hello')
|
||||
|
||||
// You can also set request headers in NextResponse.next
|
||||
const response = NextResponse.next({
|
||||
request: {
|
||||
// New request headers
|
||||
headers: requestHeaders,
|
||||
},
|
||||
})
|
||||
|
||||
// Set a new response header `x-hello-from-proxy2`
|
||||
response.headers.set('x-hello-from-proxy2', 'hello')
|
||||
return response
|
||||
}
|
||||
```
|
||||
|
||||
Note that the snippet uses:
|
||||
|
||||
- `NextResponse.next({ request: { headers: requestHeaders } })` to make `requestHeaders` available upstream
|
||||
- **NOT** `NextResponse.next({ headers: requestHeaders })` which makes `requestHeaders` available to clients
|
||||
|
||||
Learn more in [NextResponse headers in Proxy](/docs/app/api-reference/functions/next-response#next).
|
||||
|
||||
> **Good to know**: Avoid setting large headers as it might cause [431 Request Header Fields Too Large](https://developer.mozilla.org/docs/Web/HTTP/Status/431) error depending on your backend web server configuration.
|
||||
|
||||
#### RSC requests and rewrites
|
||||
|
||||
During RSC requests, Next.js strips internal Flight headers from the `request` instance in Proxy. For example, headers like `rsc`, `next-router-state-tree`, and `next-router-prefetch` are not exposed through `request.headers`. This is to prevent accidentally handling an RSC request differently than the HTML request as both need to align.
|
||||
|
||||
When you use `NextResponse.rewrite()`, Next.js automatically propagates the required RSC rewrite headers upstream.
|
||||
|
||||
If you implement custom rewrite logic with `fetch()` instead of `NextResponse.rewrite()`, you can run into missing RSC headers unless you forward them manually.
|
||||
|
||||
For custom `fetch` rewrite setups, you can also enable `skipProxyUrlNormalize` in `next.config.js` so your rewrite logic can receive the necessary URL shape and RSC headers from the provided request object:
|
||||
|
||||
```js filename="next.config.js"
|
||||
module.exports = {
|
||||
skipProxyUrlNormalize: true,
|
||||
}
|
||||
```
|
||||
|
||||
### CORS
|
||||
|
||||
You can set CORS headers in Proxy to allow cross-origin requests, including [simple](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests) and [preflighted](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#preflighted_requests) requests.
|
||||
|
||||
```tsx filename="proxy.ts" switcher
|
||||
import { NextResponse } from 'next/server'
|
||||
import type { NextRequest } from 'next/server'
|
||||
|
||||
const allowedOrigins = ['https://acme.com', 'https://my-app.org']
|
||||
|
||||
const corsOptions = {
|
||||
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
|
||||
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
|
||||
}
|
||||
|
||||
export function proxy(request: NextRequest) {
|
||||
// Check the origin from the request
|
||||
const origin = request.headers.get('origin') ?? ''
|
||||
const isAllowedOrigin = allowedOrigins.includes(origin)
|
||||
|
||||
// Handle preflighted requests
|
||||
const isPreflight = request.method === 'OPTIONS'
|
||||
|
||||
if (isPreflight) {
|
||||
const preflightHeaders = {
|
||||
...(isAllowedOrigin && { 'Access-Control-Allow-Origin': origin }),
|
||||
...corsOptions,
|
||||
}
|
||||
return NextResponse.json({}, { headers: preflightHeaders })
|
||||
}
|
||||
|
||||
// Handle simple requests
|
||||
const response = NextResponse.next()
|
||||
|
||||
if (isAllowedOrigin) {
|
||||
response.headers.set('Access-Control-Allow-Origin', origin)
|
||||
}
|
||||
|
||||
Object.entries(corsOptions).forEach(([key, value]) => {
|
||||
response.headers.set(key, value)
|
||||
})
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
export const config = {
|
||||
matcher: '/api/:path*',
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="proxy.js" switcher
|
||||
import { NextResponse } from 'next/server'
|
||||
|
||||
const allowedOrigins = ['https://acme.com', 'https://my-app.org']
|
||||
|
||||
const corsOptions = {
|
||||
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
|
||||
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
|
||||
}
|
||||
|
||||
export function proxy(request) {
|
||||
// Check the origin from the request
|
||||
const origin = request.headers.get('origin') ?? ''
|
||||
const isAllowedOrigin = allowedOrigins.includes(origin)
|
||||
|
||||
// Handle preflighted requests
|
||||
const isPreflight = request.method === 'OPTIONS'
|
||||
|
||||
if (isPreflight) {
|
||||
const preflightHeaders = {
|
||||
...(isAllowedOrigin && { 'Access-Control-Allow-Origin': origin }),
|
||||
...corsOptions,
|
||||
}
|
||||
return NextResponse.json({}, { headers: preflightHeaders })
|
||||
}
|
||||
|
||||
// Handle simple requests
|
||||
const response = NextResponse.next()
|
||||
|
||||
if (isAllowedOrigin) {
|
||||
response.headers.set('Access-Control-Allow-Origin', origin)
|
||||
}
|
||||
|
||||
Object.entries(corsOptions).forEach(([key, value]) => {
|
||||
response.headers.set(key, value)
|
||||
})
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
export const config = {
|
||||
matcher: '/api/:path*',
|
||||
}
|
||||
```
|
||||
|
||||
<AppOnly>
|
||||
|
||||
> **Good to know:** You can configure CORS headers for individual routes in [Route Handlers](/docs/app/api-reference/file-conventions/route#cors).
|
||||
|
||||
</AppOnly>
|
||||
|
||||
### Producing a response
|
||||
|
||||
You can respond from Proxy directly by returning a `Response` or `NextResponse` instance. (This is available since [Next.js v13.1.0](https://nextjs.org/blog/next-13-1#nextjs-advanced-proxy))
|
||||
|
||||
```ts filename="proxy.ts" switcher
|
||||
import type { NextRequest } from 'next/server'
|
||||
import { isAuthenticated } from '@lib/auth'
|
||||
|
||||
// Limit the proxy to paths starting with `/api/`
|
||||
export const config = {
|
||||
matcher: '/api/:function*',
|
||||
}
|
||||
|
||||
export function proxy(request: NextRequest) {
|
||||
// Call our authentication function to check the request
|
||||
if (!isAuthenticated(request)) {
|
||||
// Respond with JSON indicating an error message
|
||||
return Response.json(
|
||||
{ success: false, message: 'authentication failed' },
|
||||
{ status: 401 }
|
||||
)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="proxy.js" switcher
|
||||
import { isAuthenticated } from '@lib/auth'
|
||||
|
||||
// Limit the proxy to paths starting with `/api/`
|
||||
export const config = {
|
||||
matcher: '/api/:function*',
|
||||
}
|
||||
|
||||
export function proxy(request) {
|
||||
// Call our authentication function to check the request
|
||||
if (!isAuthenticated(request)) {
|
||||
// Respond with JSON indicating an error message
|
||||
return Response.json(
|
||||
{ success: false, message: 'authentication failed' },
|
||||
{ status: 401 }
|
||||
)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Negative matching
|
||||
|
||||
The `matcher` config allows full regex so matching like negative lookaheads or character matching is supported. An example of a negative lookahead to match all except specific paths can be seen here:
|
||||
|
||||
```js filename="proxy.js"
|
||||
export const config = {
|
||||
matcher: [
|
||||
/*
|
||||
* Match all request paths except for the ones starting with:
|
||||
* - api (API routes)
|
||||
* - _next/static (static files)
|
||||
* - _next/image (image optimization files)
|
||||
* - favicon.ico, sitemap.xml, robots.txt (metadata files)
|
||||
*/
|
||||
'/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)',
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
You can also bypass Proxy for certain requests by using the `missing` or `has` arrays, or a combination of both:
|
||||
|
||||
```js filename="proxy.js"
|
||||
export const config = {
|
||||
matcher: [
|
||||
/*
|
||||
* Match all request paths except for the ones starting with:
|
||||
* - api (API routes)
|
||||
* - _next/static (static files)
|
||||
* - _next/image (image optimization files)
|
||||
* - favicon.ico, sitemap.xml, robots.txt (metadata files)
|
||||
*/
|
||||
{
|
||||
source:
|
||||
'/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)',
|
||||
missing: [
|
||||
{ type: 'header', key: 'next-router-prefetch' },
|
||||
{ type: 'header', key: 'purpose', value: 'prefetch' },
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
source:
|
||||
'/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)',
|
||||
has: [
|
||||
{ type: 'header', key: 'next-router-prefetch' },
|
||||
{ type: 'header', key: 'purpose', value: 'prefetch' },
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
source:
|
||||
'/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)',
|
||||
has: [{ type: 'header', key: 'x-present' }],
|
||||
missing: [{ type: 'header', key: 'x-missing', value: 'prefetch' }],
|
||||
},
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
> **Good to know**:
|
||||
>
|
||||
> Even when `_next/data` is excluded in a negative matcher pattern, proxy will still be invoked for `_next/data` routes. This is intentional behavior to prevent accidental security issues where you might protect a page but forget to protect the corresponding data route.
|
||||
|
||||
```js filename="proxy.js"
|
||||
export const config = {
|
||||
matcher:
|
||||
'/((?!api|_next/data|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)',
|
||||
}
|
||||
|
||||
// Proxy will still run for /_next/data/* routes despite being excluded
|
||||
```
|
||||
|
||||
### `waitUntil` and `NextFetchEvent`
|
||||
|
||||
The `NextFetchEvent` object extends the native [`FetchEvent`](https://developer.mozilla.org/docs/Web/API/FetchEvent) object, and includes the [`waitUntil()`](https://developer.mozilla.org/docs/Web/API/ExtendableEvent/waitUntil) method.
|
||||
|
||||
The `waitUntil()` method takes a promise as an argument, and extends the lifetime of the Proxy until the promise settles. This is useful for performing work in the background.
|
||||
|
||||
```ts filename="proxy.ts"
|
||||
import { NextResponse } from 'next/server'
|
||||
import type { NextFetchEvent, NextRequest } from 'next/server'
|
||||
|
||||
export function proxy(req: NextRequest, event: NextFetchEvent) {
|
||||
event.waitUntil(
|
||||
fetch('https://my-analytics-platform.com', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ pathname: req.nextUrl.pathname }),
|
||||
})
|
||||
)
|
||||
|
||||
return NextResponse.next()
|
||||
}
|
||||
```
|
||||
|
||||
### Unit testing (experimental)
|
||||
|
||||
Starting in Next.js 15.1, the `next/experimental/testing/server` package contains utilities to help unit test proxy files. Unit testing proxy can help ensure that it's only run on desired paths and that custom routing logic works as intended before code reaches production.
|
||||
|
||||
The `unstable_doesProxyMatch` function can be used to assert whether proxy will run for the provided URL, headers, and cookies.
|
||||
|
||||
```js
|
||||
import { unstable_doesProxyMatch } from 'next/experimental/testing/server'
|
||||
|
||||
expect(
|
||||
unstable_doesProxyMatch({
|
||||
config,
|
||||
nextConfig,
|
||||
url: '/test',
|
||||
})
|
||||
).toEqual(false)
|
||||
```
|
||||
|
||||
The entire proxy function can also be tested.
|
||||
|
||||
```js
|
||||
import { isRewrite, getRewrittenUrl } from 'next/experimental/testing/server'
|
||||
|
||||
const request = new NextRequest('https://nextjs.org/docs')
|
||||
const response = await proxy(request)
|
||||
expect(isRewrite(response)).toEqual(true)
|
||||
expect(getRewrittenUrl(response)).toEqual('https://other-domain.com/docs')
|
||||
// getRedirectUrl could also be used if the response were a redirect
|
||||
```
|
||||
|
||||
## Platform support
|
||||
|
||||
| Deployment Option | Supported |
|
||||
| ------------------------------------------------------------------- | ----------------- |
|
||||
| [Node.js server](/docs/app/getting-started/deploying#nodejs-server) | Yes |
|
||||
| [Docker container](/docs/app/getting-started/deploying#docker) | Yes |
|
||||
| [Static export](/docs/app/getting-started/deploying#static-export) | No |
|
||||
| [Adapters](/docs/app/getting-started/deploying#adapters) | Platform-specific |
|
||||
|
||||
Learn how to [configure Proxy](/docs/app/guides/self-hosting#proxy) when self-hosting Next.js.
|
||||
|
||||
## Migration to Proxy
|
||||
|
||||
### Why the Change
|
||||
|
||||
The reason behind the renaming of `middleware` is that the term "middleware" can often be confused with Express.js middleware, leading to a misinterpretation of its purpose. Also, Middleware is highly capable, so it may encourage the usage; however, this feature is recommended to be used as a last resort.
|
||||
|
||||
Next.js is moving forward to provide better APIs with better ergonomics so that developers can achieve their goals without Middleware. This is the reason behind the renaming of `middleware`.
|
||||
|
||||
### Why "Proxy"
|
||||
|
||||
The name Proxy clarifies what Middleware is capable of. The term "proxy" implies that it has a network boundary in front of the app, which is the behavior of Middleware. Also, Middleware defaults to run at the [Edge Runtime](/docs/app/api-reference/edge), which can run closer to the client, separated from the app's region. These behaviors align better with the term "proxy" and provide a clearer purpose of the feature.
|
||||
|
||||
### How to Migrate
|
||||
|
||||
We recommend users avoid relying on Middleware unless no other options exist. Our goal is to give them APIs with better ergonomics so they can achieve their goals without Middleware.
|
||||
|
||||
The term “middleware” often confuses users with Express.js middleware, which can encourage misuse. To clarify our direction, we are renaming the file convention to “proxy.” This highlights that we are moving away from Middleware, breaking down its overloaded features, and making the Proxy clear in its purpose.
|
||||
|
||||
Next.js provides a codemod to migrate from `middleware.ts` to `proxy.ts`. You can run the following command to migrate:
|
||||
|
||||
```bash
|
||||
npx @next/codemod@canary middleware-to-proxy .
|
||||
```
|
||||
|
||||
The codemod will rename the file and the function name from `middleware` to `proxy`.
|
||||
|
||||
```diff
|
||||
// middleware.ts -> proxy.ts
|
||||
|
||||
- export function middleware() {
|
||||
+ export function proxy() {
|
||||
```
|
||||
|
||||
## Version history
|
||||
|
||||
| Version | Changes |
|
||||
| --------- | --------------------------------------------------------------------------------------------- |
|
||||
| `v16.0.0` | Middleware is deprecated and renamed to Proxy |
|
||||
| `v15.5.0` | Middleware can now use the Node.js runtime (stable) |
|
||||
| `v15.2.0` | Middleware can now use the Node.js runtime (experimental) |
|
||||
| `v13.1.0` | Advanced Middleware flags added |
|
||||
| `v13.0.0` | Middleware can modify request headers, response headers, and send responses |
|
||||
| `v12.2.0` | Middleware is stable, please see the [upgrade guide](/docs/messages/middleware-upgrade-guide) |
|
||||
| `v12.0.9` | Enforce absolute URLs in Edge Runtime ([PR](https://github.com/vercel/next.js/pull/33410)) |
|
||||
| `v12.0.0` | Middleware (Beta) added |
|
||||
Generated
Vendored
+45
@@ -0,0 +1,45 @@
|
||||
---
|
||||
title: public Folder
|
||||
nav_title: public
|
||||
description: Next.js allows you to serve static files, like images, in the public directory. You can learn how it works here.
|
||||
---
|
||||
|
||||
{/* The content of this doc is shared between the app and pages router. You can use the `<PagesOnly>Content</PagesOnly>` component to add content that is specific to the Pages Router. Any shared content should not be wrapped in a component. */}
|
||||
|
||||
Next.js can serve static files, like images, under a folder called `public` in the root directory. Files inside `public` can then be referenced by your code starting from the base URL (`/`).
|
||||
|
||||
For example, the file `public/avatars/me.png` can be viewed by visiting the `/avatars/me.png` path. The code to display that image might look like:
|
||||
|
||||
```jsx filename="avatar.js"
|
||||
import Image from 'next/image'
|
||||
|
||||
export function Avatar({ id, alt }) {
|
||||
return <Image src={`/avatars/${id}.png`} alt={alt} width="64" height="64" />
|
||||
}
|
||||
|
||||
export function AvatarOfMe() {
|
||||
return <Avatar id="me" alt="A portrait of me" />
|
||||
}
|
||||
```
|
||||
|
||||
## Caching
|
||||
|
||||
Next.js cannot safely cache assets in the `public` folder because they may change. The default caching headers applied are:
|
||||
|
||||
```jsx
|
||||
Cache-Control: public, max-age=0
|
||||
```
|
||||
|
||||
## Robots, Favicons, and others
|
||||
|
||||
<PagesOnly>
|
||||
|
||||
The folder is also useful for `robots.txt`, `favicon.ico`, Google Site Verification, and any other static files (including `.html`). But make sure to not have a static file with the same name as a file in the `pages/` directory, as this will result in an error. [Read more](/docs/messages/conflicting-public-file-page).
|
||||
|
||||
</PagesOnly>
|
||||
|
||||
<AppOnly>
|
||||
|
||||
For static metadata files, such as `robots.txt`, `favicon.ico`, etc, you should use [special metadata files](/docs/app/api-reference/file-conventions/metadata) inside the `app` folder.
|
||||
|
||||
</AppOnly>
|
||||
Generated
Vendored
+32
@@ -0,0 +1,32 @@
|
||||
---
|
||||
title: Route Groups
|
||||
description: Route Groups can be used to partition your Next.js application into different sections.
|
||||
---
|
||||
|
||||
Route Groups are a folder convention that let you organize routes by category or team.
|
||||
|
||||
## Convention
|
||||
|
||||
A route group can be created by wrapping a folder's name in parenthesis: `(folderName)`.
|
||||
|
||||
This convention indicates the folder is for organizational purposes and should **not be included** in the route's URL path.
|
||||
|
||||
<Image
|
||||
alt="An example folder structure using route groups"
|
||||
srcLight="/docs/light/project-organization-route-groups.png"
|
||||
srcDark="/docs/dark/project-organization-route-groups.png"
|
||||
width="1600"
|
||||
height="849"
|
||||
/>
|
||||
|
||||
## Use cases
|
||||
|
||||
- Organizing routes by team, concern, or feature.
|
||||
- Defining multiple [root layouts](/docs/app/api-reference/file-conventions/layout#root-layout).
|
||||
- Opting specific route segments into sharing a layout, while keeping others out.
|
||||
|
||||
## Caveats
|
||||
|
||||
- **Full page load**: If you navigate between routes that use different root layouts, it'll trigger a full page reload. For example, navigating from `/cart` that uses `app/(shop)/layout.js` to `/blog` that uses `app/(marketing)/layout.js`. This **only** applies to multiple root layouts.
|
||||
- **Conflicting paths**: Routes in different groups should not resolve to the same URL path. For example, `(marketing)/about/page.js` and `(shop)/about/page.js` would both resolve to `/about` and cause an error.
|
||||
- **Top-level root layout**: If you use multiple root layouts without a top-level `layout.js` file, make sure your home route (/) is defined within one of the route groups, e.g. app/(marketing)/page.js.
|
||||
Generated
Vendored
+670
@@ -0,0 +1,670 @@
|
||||
---
|
||||
title: route.js
|
||||
description: API reference for the route.js special file.
|
||||
---
|
||||
|
||||
Route Handlers allow you to create custom request handlers for a given route using the Web [Request](https://developer.mozilla.org/docs/Web/API/Request) and [Response](https://developer.mozilla.org/docs/Web/API/Response) APIs.
|
||||
|
||||
```ts filename="route.ts" switcher
|
||||
export async function GET() {
|
||||
return Response.json({ message: 'Hello World' })
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="route.js" switcher
|
||||
export async function GET() {
|
||||
return Response.json({ message: 'Hello World' })
|
||||
}
|
||||
```
|
||||
|
||||
## Reference
|
||||
|
||||
### HTTP Methods
|
||||
|
||||
A **route** file allows you to create custom request handlers for a given route. The following [HTTP methods](https://developer.mozilla.org/docs/Web/HTTP/Methods) are supported: `GET`, `POST`, `PUT`, `PATCH`, `DELETE`, `HEAD`, and `OPTIONS`.
|
||||
|
||||
```ts filename="route.ts" switcher
|
||||
export async function GET(request: Request) {}
|
||||
|
||||
export async function HEAD(request: Request) {}
|
||||
|
||||
export async function POST(request: Request) {}
|
||||
|
||||
export async function PUT(request: Request) {}
|
||||
|
||||
export async function DELETE(request: Request) {}
|
||||
|
||||
export async function PATCH(request: Request) {}
|
||||
|
||||
// If `OPTIONS` is not defined, Next.js will automatically implement `OPTIONS` and set the appropriate Response `Allow` header depending on the other methods defined in the Route Handler.
|
||||
export async function OPTIONS(request: Request) {}
|
||||
```
|
||||
|
||||
```js filename="route.js" switcher
|
||||
export async function GET(request) {}
|
||||
|
||||
export async function HEAD(request) {}
|
||||
|
||||
export async function POST(request) {}
|
||||
|
||||
export async function PUT(request) {}
|
||||
|
||||
export async function DELETE(request) {}
|
||||
|
||||
export async function PATCH(request) {}
|
||||
|
||||
// If `OPTIONS` is not defined, Next.js will automatically implement `OPTIONS` and set the appropriate Response `Allow` header depending on the other methods defined in the Route Handler.
|
||||
export async function OPTIONS(request) {}
|
||||
```
|
||||
|
||||
### Parameters
|
||||
|
||||
#### `request` (optional)
|
||||
|
||||
The `request` object is a [NextRequest](/docs/app/api-reference/functions/next-request) object, which is an extension of the Web [Request](https://developer.mozilla.org/docs/Web/API/Request) API. `NextRequest` gives you further control over the incoming request, including easily accessing `cookies` and an extended, parsed, URL object `nextUrl`.
|
||||
|
||||
```ts filename="route.ts" switcher
|
||||
import type { NextRequest } from 'next/server'
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
const url = request.nextUrl
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="route.js" switcher
|
||||
export async function GET(request) {
|
||||
const url = request.nextUrl
|
||||
}
|
||||
```
|
||||
|
||||
#### `context` (optional)
|
||||
|
||||
- **`params`**: a promise that resolves to an object containing the [dynamic route parameters](/docs/app/api-reference/file-conventions/dynamic-routes) for the current route.
|
||||
|
||||
```ts filename="app/dashboard/[team]/route.ts" switcher
|
||||
export async function GET(
|
||||
request: Request,
|
||||
{ params }: { params: Promise<{ team: string }> }
|
||||
) {
|
||||
const { team } = await params
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="app/dashboard/[team]/route.js" switcher
|
||||
export async function GET(request, { params }) {
|
||||
const { team } = await params
|
||||
}
|
||||
```
|
||||
|
||||
| Example | URL | `params` |
|
||||
| -------------------------------- | -------------- | ---------------------------------- |
|
||||
| `app/dashboard/[team]/route.js` | `/dashboard/1` | `Promise<{ team: '1' }>` |
|
||||
| `app/shop/[tag]/[item]/route.js` | `/shop/1/2` | `Promise<{ tag: '1', item: '2' }>` |
|
||||
| `app/blog/[...slug]/route.js` | `/blog/1/2` | `Promise<{ slug: ['1', '2'] }>` |
|
||||
|
||||
### Route Context Helper
|
||||
|
||||
You can type the Route Handler context using `RouteContext` to get strongly typed `params` from a route literal. `RouteContext` is a globally available helper.
|
||||
|
||||
```ts filename="app/users/[id]/route.ts"
|
||||
import type { NextRequest } from 'next/server'
|
||||
|
||||
export async function GET(_req: NextRequest, ctx: RouteContext<'/users/[id]'>) {
|
||||
const { id } = await ctx.params
|
||||
return Response.json({ id })
|
||||
}
|
||||
```
|
||||
|
||||
> **Good to know**
|
||||
>
|
||||
> - Types are generated during `next dev`, `next build` or `next typegen`.
|
||||
> - After type generation, the `RouteContext` helper is globally available. It doesn't need to be imported.
|
||||
|
||||
## Examples
|
||||
|
||||
### Cookies
|
||||
|
||||
You can read or set cookies with [`cookies`](/docs/app/api-reference/functions/cookies) from `next/headers`.
|
||||
|
||||
```ts filename="route.ts" switcher
|
||||
import { cookies } from 'next/headers'
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
const cookieStore = await cookies()
|
||||
|
||||
const a = cookieStore.get('a')
|
||||
const b = cookieStore.set('b', '1')
|
||||
const c = cookieStore.delete('c')
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="route.js" switcher
|
||||
import { cookies } from 'next/headers'
|
||||
|
||||
export async function GET(request) {
|
||||
const cookieStore = await cookies()
|
||||
|
||||
const a = cookieStore.get('a')
|
||||
const b = cookieStore.set('b', '1')
|
||||
const c = cookieStore.delete('c')
|
||||
}
|
||||
```
|
||||
|
||||
Alternatively, you can return a new `Response` using the [`Set-Cookie`](https://developer.mozilla.org/docs/Web/HTTP/Headers/Set-Cookie) header.
|
||||
|
||||
```ts filename="app/api/route.ts" switcher
|
||||
import { cookies } from 'next/headers'
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const cookieStore = await cookies()
|
||||
const token = cookieStore.get('token')
|
||||
|
||||
return new Response('Hello, Next.js!', {
|
||||
status: 200,
|
||||
headers: { 'Set-Cookie': `token=${token.value}` },
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="app/api/route.js" switcher
|
||||
import { cookies } from 'next/headers'
|
||||
|
||||
export async function GET(request) {
|
||||
const cookieStore = await cookies()
|
||||
const token = cookieStore.get('token')
|
||||
|
||||
return new Response('Hello, Next.js!', {
|
||||
status: 200,
|
||||
headers: { 'Set-Cookie': `token=${token.value}` },
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
You can also use the underlying Web APIs to read cookies from the request ([`NextRequest`](/docs/app/api-reference/functions/next-request)):
|
||||
|
||||
```ts filename="app/api/route.ts" switcher
|
||||
import { type NextRequest } from 'next/server'
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
const token = request.cookies.get('token')
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="app/api/route.js" switcher
|
||||
export async function GET(request) {
|
||||
const token = request.cookies.get('token')
|
||||
}
|
||||
```
|
||||
|
||||
### Headers
|
||||
|
||||
You can read headers with [`headers`](/docs/app/api-reference/functions/headers) from `next/headers`.
|
||||
|
||||
```ts filename="route.ts" switcher
|
||||
import { headers } from 'next/headers'
|
||||
import type { NextRequest } from 'next/server'
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
const headersList = await headers()
|
||||
const referer = headersList.get('referer')
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="route.js" switcher
|
||||
import { headers } from 'next/headers'
|
||||
|
||||
export async function GET(request) {
|
||||
const headersList = await headers()
|
||||
const referer = headersList.get('referer')
|
||||
}
|
||||
```
|
||||
|
||||
This `headers` instance is read-only. To set headers, you need to return a new `Response` with new `headers`.
|
||||
|
||||
```ts filename="app/api/route.ts" switcher
|
||||
import { headers } from 'next/headers'
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const headersList = await headers()
|
||||
const referer = headersList.get('referer')
|
||||
|
||||
return new Response('Hello, Next.js!', {
|
||||
status: 200,
|
||||
headers: { referer: referer },
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="app/api/route.js" switcher
|
||||
import { headers } from 'next/headers'
|
||||
|
||||
export async function GET(request) {
|
||||
const headersList = await headers()
|
||||
const referer = headersList.get('referer')
|
||||
|
||||
return new Response('Hello, Next.js!', {
|
||||
status: 200,
|
||||
headers: { referer: referer },
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
You can also use the underlying Web APIs to read headers from the request ([`NextRequest`](/docs/app/api-reference/functions/next-request)):
|
||||
|
||||
```ts filename="app/api/route.ts" switcher
|
||||
import { type NextRequest } from 'next/server'
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
const requestHeaders = new Headers(request.headers)
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="app/api/route.js" switcher
|
||||
export async function GET(request) {
|
||||
const requestHeaders = new Headers(request.headers)
|
||||
}
|
||||
```
|
||||
|
||||
### Revalidating Cached Data
|
||||
|
||||
You can [revalidate cached data](/docs/app/guides/incremental-static-regeneration) using the `revalidate` route segment config option.
|
||||
|
||||
```ts filename="app/posts/route.ts" switcher
|
||||
export const revalidate = 60
|
||||
|
||||
export async function GET() {
|
||||
const data = await fetch('https://api.vercel.app/blog')
|
||||
const posts = await data.json()
|
||||
|
||||
return Response.json(posts)
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="app/posts/route.js" switcher
|
||||
export const revalidate = 60
|
||||
|
||||
export async function GET() {
|
||||
const data = await fetch('https://api.vercel.app/blog')
|
||||
const posts = await data.json()
|
||||
|
||||
return Response.json(posts)
|
||||
}
|
||||
```
|
||||
|
||||
### Redirects
|
||||
|
||||
```ts filename="app/api/route.ts" switcher
|
||||
import { redirect } from 'next/navigation'
|
||||
|
||||
export async function GET(request: Request) {
|
||||
redirect('https://nextjs.org/')
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="app/api/route.js" switcher
|
||||
import { redirect } from 'next/navigation'
|
||||
|
||||
export async function GET(request) {
|
||||
redirect('https://nextjs.org/')
|
||||
}
|
||||
```
|
||||
|
||||
### Dynamic Route Segments
|
||||
|
||||
Route Handlers can use [Dynamic Segments](/docs/app/api-reference/file-conventions/dynamic-routes) to create request handlers from dynamic data.
|
||||
|
||||
```ts filename="app/items/[slug]/route.ts" switcher
|
||||
export async function GET(
|
||||
request: Request,
|
||||
{ params }: { params: Promise<{ slug: string }> }
|
||||
) {
|
||||
const { slug } = await params // 'a', 'b', or 'c'
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="app/items/[slug]/route.js" switcher
|
||||
export async function GET(request, { params }) {
|
||||
const { slug } = await params // 'a', 'b', or 'c'
|
||||
}
|
||||
```
|
||||
|
||||
| Route | Example URL | `params` |
|
||||
| --------------------------- | ----------- | ------------------------ |
|
||||
| `app/items/[slug]/route.js` | `/items/a` | `Promise<{ slug: 'a' }>` |
|
||||
| `app/items/[slug]/route.js` | `/items/b` | `Promise<{ slug: 'b' }>` |
|
||||
| `app/items/[slug]/route.js` | `/items/c` | `Promise<{ slug: 'c' }>` |
|
||||
|
||||
#### Static Generation with `generateStaticParams`
|
||||
|
||||
You can use [`generateStaticParams`](/docs/app/api-reference/functions/generate-static-params) with dynamic Route Handlers to statically generate responses at build time for specified params, while handling other params dynamically at request time.
|
||||
|
||||
When using [Cache Components](/docs/app/getting-started/caching), you can combine `generateStaticParams` with `use cache` to enable data caching for both prerendered and runtime params.
|
||||
|
||||
See the [generateStaticParams with Route Handlers](/docs/app/api-reference/functions/generate-static-params#with-route-handlers) documentation for examples and details.
|
||||
|
||||
### URL Query Parameters
|
||||
|
||||
The request object passed to the Route Handler is a `NextRequest` instance, which includes [some additional convenience methods](/docs/app/api-reference/functions/next-request#nexturl), such as those for more easily handling query parameters.
|
||||
|
||||
```ts filename="app/api/search/route.ts" switcher
|
||||
import { type NextRequest } from 'next/server'
|
||||
|
||||
export function GET(request: NextRequest) {
|
||||
const searchParams = request.nextUrl.searchParams
|
||||
const query = searchParams.get('query')
|
||||
// query is "hello" for /api/search?query=hello
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="app/api/search/route.js" switcher
|
||||
export function GET(request) {
|
||||
const searchParams = request.nextUrl.searchParams
|
||||
const query = searchParams.get('query')
|
||||
// query is "hello" for /api/search?query=hello
|
||||
}
|
||||
```
|
||||
|
||||
### Streaming
|
||||
|
||||
Streaming is commonly used in combination with Large Language Models (LLMs), such as OpenAI, for AI-generated content. Learn more about the [AI SDK](https://sdk.vercel.ai/docs/introduction).
|
||||
|
||||
```ts filename="app/api/chat/route.ts" switcher
|
||||
import { openai } from '@ai-sdk/openai'
|
||||
import { StreamingTextResponse, streamText } from 'ai'
|
||||
|
||||
export async function POST(req: Request) {
|
||||
const { messages } = await req.json()
|
||||
const result = await streamText({
|
||||
model: openai('gpt-4-turbo'),
|
||||
messages,
|
||||
})
|
||||
|
||||
return new StreamingTextResponse(result.toAIStream())
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="app/api/chat/route.js" switcher
|
||||
import { openai } from '@ai-sdk/openai'
|
||||
import { StreamingTextResponse, streamText } from 'ai'
|
||||
|
||||
export async function POST(req) {
|
||||
const { messages } = await req.json()
|
||||
const result = await streamText({
|
||||
model: openai('gpt-4-turbo'),
|
||||
messages,
|
||||
})
|
||||
|
||||
return new StreamingTextResponse(result.toAIStream())
|
||||
}
|
||||
```
|
||||
|
||||
These abstractions use the Web APIs to create a stream. You can also use the underlying Web APIs directly.
|
||||
|
||||
```ts filename="app/api/route.ts" switcher
|
||||
// https://developer.mozilla.org/docs/Web/API/ReadableStream#convert_async_iterator_to_stream
|
||||
function iteratorToStream(iterator: any) {
|
||||
return new ReadableStream({
|
||||
async pull(controller) {
|
||||
const { value, done } = await iterator.next()
|
||||
|
||||
if (done) {
|
||||
controller.close()
|
||||
} else {
|
||||
controller.enqueue(value)
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
function sleep(time: number) {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, time)
|
||||
})
|
||||
}
|
||||
|
||||
const encoder = new TextEncoder()
|
||||
|
||||
async function* makeIterator() {
|
||||
yield encoder.encode('<p>One</p>')
|
||||
await sleep(200)
|
||||
yield encoder.encode('<p>Two</p>')
|
||||
await sleep(200)
|
||||
yield encoder.encode('<p>Three</p>')
|
||||
}
|
||||
|
||||
export async function GET() {
|
||||
const iterator = makeIterator()
|
||||
const stream = iteratorToStream(iterator)
|
||||
|
||||
return new Response(stream)
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="app/api/route.js" switcher
|
||||
// https://developer.mozilla.org/docs/Web/API/ReadableStream#convert_async_iterator_to_stream
|
||||
function iteratorToStream(iterator) {
|
||||
return new ReadableStream({
|
||||
async pull(controller) {
|
||||
const { value, done } = await iterator.next()
|
||||
|
||||
if (done) {
|
||||
controller.close()
|
||||
} else {
|
||||
controller.enqueue(value)
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
function sleep(time) {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, time)
|
||||
})
|
||||
}
|
||||
|
||||
const encoder = new TextEncoder()
|
||||
|
||||
async function* makeIterator() {
|
||||
yield encoder.encode('<p>One</p>')
|
||||
await sleep(200)
|
||||
yield encoder.encode('<p>Two</p>')
|
||||
await sleep(200)
|
||||
yield encoder.encode('<p>Three</p>')
|
||||
}
|
||||
|
||||
export async function GET() {
|
||||
const iterator = makeIterator()
|
||||
const stream = iteratorToStream(iterator)
|
||||
|
||||
return new Response(stream)
|
||||
}
|
||||
```
|
||||
|
||||
### Request Body
|
||||
|
||||
You can read the `Request` body using the standard Web API methods:
|
||||
|
||||
```ts filename="app/items/route.ts" switcher
|
||||
export async function POST(request: Request) {
|
||||
const res = await request.json()
|
||||
return Response.json({ res })
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="app/items/route.js" switcher
|
||||
export async function POST(request) {
|
||||
const res = await request.json()
|
||||
return Response.json({ res })
|
||||
}
|
||||
```
|
||||
|
||||
### Request Body FormData
|
||||
|
||||
You can read the `FormData` using the `request.formData()` function:
|
||||
|
||||
```ts filename="app/items/route.ts" switcher
|
||||
export async function POST(request: Request) {
|
||||
const formData = await request.formData()
|
||||
const name = formData.get('name')
|
||||
const email = formData.get('email')
|
||||
return Response.json({ name, email })
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="app/items/route.js" switcher
|
||||
export async function POST(request) {
|
||||
const formData = await request.formData()
|
||||
const name = formData.get('name')
|
||||
const email = formData.get('email')
|
||||
return Response.json({ name, email })
|
||||
}
|
||||
```
|
||||
|
||||
Since `formData` data are all strings, you may want to use [`zod-form-data`](https://www.npmjs.com/zod-form-data) to validate the request and retrieve data in the format you prefer (e.g. `number`).
|
||||
|
||||
### CORS
|
||||
|
||||
You can set CORS headers for a specific Route Handler using the standard Web API methods:
|
||||
|
||||
```ts filename="app/api/route.ts" switcher
|
||||
export async function GET(request: Request) {
|
||||
return new Response('Hello, Next.js!', {
|
||||
status: 200,
|
||||
headers: {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
|
||||
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
|
||||
},
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="app/api/route.js" switcher
|
||||
export async function GET(request) {
|
||||
return new Response('Hello, Next.js!', {
|
||||
status: 200,
|
||||
headers: {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
|
||||
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
|
||||
},
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
> **Good to know**:
|
||||
>
|
||||
> - To add CORS headers to multiple Route Handlers, you can use [Proxy](/docs/app/api-reference/file-conventions/proxy#cors) or the [`next.config.js` file](/docs/app/api-reference/config/next-config-js/headers#cors).
|
||||
|
||||
### Webhooks
|
||||
|
||||
You can use a Route Handler to receive webhooks from third-party services:
|
||||
|
||||
```ts filename="app/api/route.ts" switcher
|
||||
export async function POST(request: Request) {
|
||||
try {
|
||||
const text = await request.text()
|
||||
// Process the webhook payload
|
||||
} catch (error) {
|
||||
return new Response(`Webhook error: ${error.message}`, {
|
||||
status: 400,
|
||||
})
|
||||
}
|
||||
|
||||
return new Response('Success!', {
|
||||
status: 200,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="app/api/route.js" switcher
|
||||
export async function POST(request) {
|
||||
try {
|
||||
const text = await request.text()
|
||||
// Process the webhook payload
|
||||
} catch (error) {
|
||||
return new Response(`Webhook error: ${error.message}`, {
|
||||
status: 400,
|
||||
})
|
||||
}
|
||||
|
||||
return new Response('Success!', {
|
||||
status: 200,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
Notably, unlike API Routes with the Pages Router, you do not need to use `bodyParser` to use any additional configuration.
|
||||
|
||||
### Non-UI Responses
|
||||
|
||||
You can use Route Handlers to return non-UI content. Note that [`sitemap.xml`](/docs/app/api-reference/file-conventions/metadata/sitemap#generating-a-sitemap-using-code-js-ts), [`robots.txt`](/docs/app/api-reference/file-conventions/metadata/robots#generate-a-robots-file), [`app icons`](/docs/app/api-reference/file-conventions/metadata/app-icons#generate-icons-using-code-js-ts-tsx), and [open graph images](/docs/app/api-reference/file-conventions/metadata/opengraph-image) all have built-in support.
|
||||
|
||||
```ts filename="app/rss.xml/route.ts" switcher
|
||||
export async function GET() {
|
||||
return new Response(
|
||||
`<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<rss version="2.0">
|
||||
|
||||
<channel>
|
||||
<title>Next.js Documentation</title>
|
||||
<link>https://nextjs.org/docs</link>
|
||||
<description>The React Framework for the Web</description>
|
||||
</channel>
|
||||
|
||||
</rss>`,
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'text/xml',
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="app/rss.xml/route.js" switcher
|
||||
export async function GET() {
|
||||
return new Response(`<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<rss version="2.0">
|
||||
|
||||
<channel>
|
||||
<title>Next.js Documentation</title>
|
||||
<link>https://nextjs.org/docs</link>
|
||||
<description>The React Framework for the Web</description>
|
||||
</channel>
|
||||
|
||||
</rss>`)
|
||||
}
|
||||
```
|
||||
|
||||
### Segment Config Options
|
||||
|
||||
Route Handlers use the same [route segment configuration](/docs/app/api-reference/file-conventions/route-segment-config) as pages and layouts.
|
||||
|
||||
```ts filename="app/items/route.ts" switcher
|
||||
export const dynamic = 'auto'
|
||||
export const dynamicParams = true
|
||||
export const revalidate = false
|
||||
export const fetchCache = 'auto'
|
||||
export const runtime = 'nodejs'
|
||||
export const preferredRegion = 'auto'
|
||||
```
|
||||
|
||||
```js filename="app/items/route.js" switcher
|
||||
export const dynamic = 'auto'
|
||||
export const dynamicParams = true
|
||||
export const revalidate = false
|
||||
export const fetchCache = 'auto'
|
||||
export const runtime = 'nodejs'
|
||||
export const preferredRegion = 'auto'
|
||||
```
|
||||
|
||||
See the [API reference](/docs/app/api-reference/file-conventions/route-segment-config) for more details.
|
||||
|
||||
## Version History
|
||||
|
||||
| Version | Changes |
|
||||
| ------------ | ---------------------------------------------------------------------------------------------------- |
|
||||
| `v15.0.0-RC` | `context.params` is now a promise. A [codemod](/docs/app/guides/upgrading/codemods#150) is available |
|
||||
| `v15.0.0-RC` | The default caching for `GET` handlers was changed from static to dynamic |
|
||||
| `v13.2.0` | Route Handlers are introduced. |
|
||||
Generated
Vendored
+35
@@ -0,0 +1,35 @@
|
||||
---
|
||||
title: src Folder
|
||||
nav_title: src
|
||||
description: Save pages under the `src` folder as an alternative to the root `pages` directory.
|
||||
related:
|
||||
links:
|
||||
- app/getting-started/project-structure
|
||||
---
|
||||
|
||||
{/* The content of this doc is shared between the app and pages router. You can use the `<PagesOnly>Content</PagesOnly>` component to add content that is specific to the Pages Router. Any shared content should not be wrapped in a component. */}
|
||||
|
||||
As an alternative to having the special Next.js `app` or `pages` directories in the root of your project, Next.js also supports the common pattern of placing application code under the `src` folder.
|
||||
|
||||
This separates application code from project configuration files which mostly live in the root of a project, which is preferred by some individuals and teams.
|
||||
|
||||
To use the `src` folder, move the `app` Router folder or `pages` Router folder to `src/app` or `src/pages` respectively.
|
||||
|
||||
<Image
|
||||
alt="An example folder structure with the `src` folder"
|
||||
srcLight="/docs/light/project-organization-src-directory.png"
|
||||
srcDark="/docs/dark/project-organization-src-directory.png"
|
||||
width="1600"
|
||||
height="687"
|
||||
/>
|
||||
|
||||
> **Good to know**:
|
||||
>
|
||||
> - The `/public` directory should remain in the root of your project.
|
||||
> - Config files like `package.json`, `next.config.js` and `tsconfig.json` should remain in the root of your project.
|
||||
> - `.env.*` files should remain in the root of your project.
|
||||
> - `src/app` or `src/pages` will be ignored if `app` or `pages` are present in the root directory.
|
||||
> - If you're using `src`, you'll probably also move other application folders such as `/components` or `/lib`.
|
||||
> - If you're using Proxy, ensure it is placed inside the `src` folder.
|
||||
> - If you're using Tailwind CSS, you'll need to add the `/src` prefix to the `tailwind.config.js` file in the [content section](https://tailwindcss.com/docs/content-configuration).
|
||||
> - If you are using TypeScript paths for imports such as `@/*`, you should update the `paths` object in `tsconfig.json` to include `src/`.
|
||||
Generated
Vendored
+162
@@ -0,0 +1,162 @@
|
||||
---
|
||||
title: template.js
|
||||
description: API Reference for the template.js file.
|
||||
---
|
||||
|
||||
A **template** file is similar to a [layout](/docs/app/getting-started/layouts-and-pages#creating-a-layout) in that it wraps a layout or page. Unlike layouts that persist across routes and maintain state, templates are given a unique key, meaning children Client Components reset their state on navigation.
|
||||
|
||||
They are useful when you need to:
|
||||
|
||||
- Resynchronize `useEffect` on navigation.
|
||||
- Reset the state of a child Client Components on navigation. For example, an input field.
|
||||
- To change default framework behavior. For example, Suspense boundaries inside layouts only show a fallback on first load, while templates show it on every navigation.
|
||||
|
||||
## Convention
|
||||
|
||||
A template can be defined by exporting a default React component from a `template.js` file. The component should accept a `children` prop.
|
||||
|
||||
<Image
|
||||
alt="template.js special file"
|
||||
srcLight="/docs/light/template-special-file.png"
|
||||
srcDark="/docs/dark/template-special-file.png"
|
||||
width="1600"
|
||||
height="444"
|
||||
/>
|
||||
|
||||
```tsx filename="app/template.tsx" switcher
|
||||
export default function Template({ children }: { children: React.ReactNode }) {
|
||||
return <div>{children}</div>
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/template.js" switcher
|
||||
export default function Template({ children }) {
|
||||
return <div>{children}</div>
|
||||
}
|
||||
```
|
||||
|
||||
In terms of nesting, `template.js` is rendered between a layout and its children. Here's a simplified output:
|
||||
|
||||
```jsx filename="Output"
|
||||
<Layout>
|
||||
{/* Note that the template is given a unique key. */}
|
||||
<Template key={routeParam}>{children}</Template>
|
||||
</Layout>
|
||||
```
|
||||
|
||||
In the [component hierarchy](/docs/app/getting-started/project-structure#component-hierarchy), `template.js` renders between `layout.js` and `error.js`. It wraps `error.js`, `loading.js`, `not-found.js`, and `page.js`, but does **not** wrap the `layout.js` in the same segment.
|
||||
|
||||
## Props
|
||||
|
||||
### `children` (required)
|
||||
|
||||
Template accepts a `children` prop.
|
||||
|
||||
```jsx filename="Output"
|
||||
<Layout>
|
||||
{/* Note that the template is automatically given a unique key. */}
|
||||
<Template key={routeParam}>{children}</Template>
|
||||
</Layout>
|
||||
```
|
||||
|
||||
## Behavior
|
||||
|
||||
- **Server Components**: By default, templates are Server Components.
|
||||
- **With navigation**: Templates receive a unique key for their own segment level. They remount when that segment (including its dynamic params) changes. Navigations within deeper segments do not remount higher-level templates. Search params do not trigger remounts.
|
||||
- **State reset**: Any Client Component inside the template will reset its state on navigation.
|
||||
- **Effect re-run**: Effects like `useEffect` will re-synchronize as the component remounts.
|
||||
- **DOM reset**: DOM elements inside the template are fully recreated.
|
||||
|
||||
### Templates during navigation and remounting
|
||||
|
||||
This section illustrates how templates behave during navigation. It shows, step by step, which templates remount on each route change and why.
|
||||
|
||||
Using this project tree:
|
||||
|
||||
```
|
||||
app
|
||||
├── about
|
||||
│ ├── page.tsx
|
||||
├── blog
|
||||
│ ├── [slug]
|
||||
│ │ └── page.tsx
|
||||
│ ├── page.tsx
|
||||
│ └── template.tsx
|
||||
├── layout.tsx
|
||||
├── page.tsx
|
||||
└── template.tsx
|
||||
```
|
||||
|
||||
Starting at `/`, the React tree looks roughly like this.
|
||||
|
||||
> Note: The `key` values shown in the examples are illustrative, the values in your application may differ.
|
||||
|
||||
```jsx filename="Output"
|
||||
<RootLayout>
|
||||
{/* app/template.tsx */}
|
||||
<Template key="/">
|
||||
<Page />
|
||||
</Template>
|
||||
</RootLayout>
|
||||
```
|
||||
|
||||
Navigating to `/about` (first segment changes), the root template key changes, it remounts:
|
||||
|
||||
```jsx filename="Output"
|
||||
<RootLayout>
|
||||
{/* app/template.tsx */}
|
||||
<Template key="/about">
|
||||
<AboutPage />
|
||||
</Template>
|
||||
</RootLayout>
|
||||
```
|
||||
|
||||
Navigating to `/blog` (first segment changes), the root template key changes, it remounts and the blog-level template mounts:
|
||||
|
||||
```jsx filename="Output"
|
||||
<RootLayout>
|
||||
{/* app/template.tsx (root) */}
|
||||
<Template key="/blog">
|
||||
{/* app/blog/template.tsx */}
|
||||
<Template key="/blog">
|
||||
<BlogIndexPage />
|
||||
</Template>
|
||||
</Template>
|
||||
</RootLayout>
|
||||
```
|
||||
|
||||
Navigating within the same first segment to `/blog/first-post` (child segment changes), the root template key doesn't change, but the blog-level template key changes, it remounts:
|
||||
|
||||
```jsx filename="Output"
|
||||
<RootLayout>
|
||||
{/* app/template.tsx (root) */}
|
||||
<Template key="/blog">
|
||||
{/* app/blog/template.tsx */}
|
||||
{/* remounts because the child segment at this level changed */}
|
||||
<Template key="/blog/first-post">
|
||||
<BlogPostPage slug="first-post" />
|
||||
</Template>
|
||||
</Template>
|
||||
</RootLayout>
|
||||
```
|
||||
|
||||
Navigating to `/blog/second-post` (same first segment, different child segment), the root template key doesn't change, but the blog-level template key changes, it remounts again:
|
||||
|
||||
```jsx filename="Output"
|
||||
<RootLayout>
|
||||
{/* app/template.tsx (root) */}
|
||||
<Template key="/blog">
|
||||
{/* app/blog/template.tsx */}
|
||||
{/* remounts again due to changed child segment */}
|
||||
<Template key="/blog/second-post">
|
||||
<BlogPostPage slug="second-post" />
|
||||
</Template>
|
||||
</Template>
|
||||
</RootLayout>
|
||||
```
|
||||
|
||||
## Version History
|
||||
|
||||
| Version | Changes |
|
||||
| --------- | ---------------------- |
|
||||
| `v13.0.0` | `template` introduced. |
|
||||
Generated
Vendored
+114
@@ -0,0 +1,114 @@
|
||||
---
|
||||
title: unauthorized.js
|
||||
description: API reference for the unauthorized.js special file.
|
||||
related:
|
||||
links:
|
||||
- app/api-reference/functions/unauthorized
|
||||
version: experimental
|
||||
---
|
||||
|
||||
The **unauthorized** file is used to render UI when the [`unauthorized`](/docs/app/api-reference/functions/unauthorized) function is invoked during authentication. Along with allowing you to customize the UI, Next.js will return a `401` status code.
|
||||
|
||||
```tsx filename="app/unauthorized.tsx" switcher
|
||||
import Login from '@/app/components/Login'
|
||||
|
||||
export default function Unauthorized() {
|
||||
return (
|
||||
<main>
|
||||
<h1>401 - Unauthorized</h1>
|
||||
<p>Please log in to access this page.</p>
|
||||
<Login />
|
||||
</main>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/unauthorized.js" switcher
|
||||
import Login from '@/app/components/Login'
|
||||
|
||||
export default function Unauthorized() {
|
||||
return (
|
||||
<main>
|
||||
<h1>401 - Unauthorized</h1>
|
||||
<p>Please log in to access this page.</p>
|
||||
<Login />
|
||||
</main>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Reference
|
||||
|
||||
### Props
|
||||
|
||||
`unauthorized.js` components do not accept any props.
|
||||
|
||||
## Examples
|
||||
|
||||
### Displaying login UI to unauthenticated users
|
||||
|
||||
You can use [`unauthorized`](/docs/app/api-reference/functions/unauthorized) function to render the `unauthorized.js` file with a login UI.
|
||||
|
||||
```tsx filename="app/dashboard/page.tsx" switcher
|
||||
import { verifySession } from '@/app/lib/dal'
|
||||
import { unauthorized } from 'next/navigation'
|
||||
|
||||
export default async function DashboardPage() {
|
||||
const session = await verifySession()
|
||||
|
||||
if (!session) {
|
||||
unauthorized()
|
||||
}
|
||||
|
||||
return <div>Dashboard</div>
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/dashboard/page.js" switcher
|
||||
import { verifySession } from '@/app/lib/dal'
|
||||
import { unauthorized } from 'next/navigation'
|
||||
|
||||
export default async function DashboardPage() {
|
||||
const session = await verifySession()
|
||||
|
||||
if (!session) {
|
||||
unauthorized()
|
||||
}
|
||||
|
||||
return <div>Dashboard</div>
|
||||
}
|
||||
```
|
||||
|
||||
```tsx filename="app/unauthorized.tsx" switcher
|
||||
import Login from '@/app/components/Login'
|
||||
|
||||
export default function UnauthorizedPage() {
|
||||
return (
|
||||
<main>
|
||||
<h1>401 - Unauthorized</h1>
|
||||
<p>Please log in to access this page.</p>
|
||||
<Login />
|
||||
</main>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/unauthorized.js" switcher
|
||||
import Login from '@/app/components/Login'
|
||||
|
||||
export default function UnauthorizedPage() {
|
||||
return (
|
||||
<main>
|
||||
<h1>401 - Unauthorized</h1>
|
||||
<p>Please log in to access this page.</p>
|
||||
<Login />
|
||||
</main>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Version History
|
||||
|
||||
| Version | Changes |
|
||||
| --------- | ----------------------------- |
|
||||
| `v15.1.0` | `unauthorized.js` introduced. |
|
||||
Generated
Vendored
+303
@@ -0,0 +1,303 @@
|
||||
---
|
||||
title: after
|
||||
description: API Reference for the after function.
|
||||
---
|
||||
|
||||
`after` allows you to schedule work to be executed after a response (or prerender) is finished. This is useful for tasks and other side effects that should not block the response, such as logging and analytics.
|
||||
|
||||
It can be used in [Server Components](/docs/app/getting-started/server-and-client-components) (including [`generateMetadata`](/docs/app/api-reference/functions/generate-metadata)), [Server Functions](/docs/app/getting-started/mutating-data), [Route Handlers](/docs/app/api-reference/file-conventions/route), and [Proxy](/docs/app/api-reference/file-conventions/proxy).
|
||||
|
||||
The function accepts a callback that will be executed after the response (or prerender) is finished:
|
||||
|
||||
```tsx filename="app/layout.tsx" switcher
|
||||
import { after } from 'next/server'
|
||||
// Custom logging function
|
||||
import { log } from '@/app/utils'
|
||||
|
||||
export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
after(() => {
|
||||
// Execute after the layout is rendered and sent to the user
|
||||
log()
|
||||
})
|
||||
return <>{children}</>
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/layout.jsx" switcher
|
||||
import { after } from 'next/server'
|
||||
// Custom logging function
|
||||
import { log } from '@/app/utils'
|
||||
|
||||
export default function Layout({ children }) {
|
||||
after(() => {
|
||||
// Execute after the layout is rendered and sent to the user
|
||||
log()
|
||||
})
|
||||
return <>{children}</>
|
||||
}
|
||||
```
|
||||
|
||||
> **Good to know:** `after` is not a [Request-time API](/docs/app/glossary#request-time-apis) and calling it does not cause a route to become dynamic. If it's used within a static page, the callback will execute at build time, or whenever a page is revalidated.
|
||||
|
||||
## Reference
|
||||
|
||||
### Parameters
|
||||
|
||||
- A callback function which will be executed after the response (or prerender) is finished.
|
||||
|
||||
### Duration
|
||||
|
||||
`after` will run for the platform's default or configured max duration of your route. If your platform supports it, you can configure the timeout limit using the [`maxDuration`](/docs/app/api-reference/file-conventions/route-segment-config/maxDuration) route segment config.
|
||||
|
||||
## Good to know
|
||||
|
||||
- `after` will be executed even if the response didn't complete successfully. Including when an error is thrown or when `notFound` or `redirect` is called.
|
||||
- You can use React `cache` to deduplicate functions called inside `after`.
|
||||
- `after` can be nested inside other `after` calls, for example, you can create utility functions that wrap `after` calls to add additional functionality.
|
||||
|
||||
## Examples
|
||||
|
||||
### With request APIs
|
||||
|
||||
Whether you can use request APIs like [`cookies`](/docs/app/api-reference/functions/cookies) and [`headers`](/docs/app/api-reference/functions/headers) inside `after` depends on where `after` is called from.
|
||||
|
||||
#### In Route Handlers and Server Functions
|
||||
|
||||
You can call `cookies` and `headers` directly inside the `after` callback when used in [Route Handlers](/docs/app/api-reference/file-conventions/route) and [Server Functions](/docs/app/getting-started/mutating-data). This is useful for logging activity after a mutation or API request. For example:
|
||||
|
||||
```ts filename="app/api/route.ts" highlight={2,10-16} switcher
|
||||
import { after } from 'next/server'
|
||||
import { cookies, headers } from 'next/headers'
|
||||
import { logUserAction } from '@/app/utils'
|
||||
|
||||
export async function POST(request: Request) {
|
||||
// Perform mutation
|
||||
// ...
|
||||
|
||||
// Log user activity for analytics
|
||||
after(async () => {
|
||||
const userAgent = (await headers()).get('user-agent') || 'unknown'
|
||||
const sessionCookie =
|
||||
(await cookies()).get('session-id')?.value || 'anonymous'
|
||||
|
||||
logUserAction({ sessionCookie, userAgent })
|
||||
})
|
||||
|
||||
return new Response(JSON.stringify({ status: 'success' }), {
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="app/api/route.js" highlight={2,10-16} switcher
|
||||
import { after } from 'next/server'
|
||||
import { cookies, headers } from 'next/headers'
|
||||
import { logUserAction } from '@/app/utils'
|
||||
|
||||
export async function POST(request) {
|
||||
// Perform mutation
|
||||
// ...
|
||||
|
||||
// Log user activity for analytics
|
||||
after(async () => {
|
||||
const userAgent = (await headers()).get('user-agent') || 'unknown'
|
||||
const sessionCookie =
|
||||
(await cookies()).get('session-id')?.value || 'anonymous'
|
||||
|
||||
logUserAction({ sessionCookie, userAgent })
|
||||
})
|
||||
|
||||
return new Response(JSON.stringify({ status: 'success' }), {
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
#### In Server Components (pages and layouts)
|
||||
|
||||
[Server Components](/docs/app/getting-started/server-and-client-components) (including pages, layouts, and `generateMetadata`) **cannot** use `cookies`, `headers`, or other Request-time APIs inside `after`. This is because Next.js needs to know which part of the component tree accesses request data to support [Partial Prerendering](/docs/app/glossary#partial-prerendering-ppr) and [Cache Components](/docs/app/getting-started/caching), but `after` runs after React's rendering lifecycle.
|
||||
|
||||
If you need request data inside an `after` callback in a Server Component, read it beforehand and pass the values in:
|
||||
|
||||
```tsx filename="app/page.tsx" highlight={8-10,12} switcher
|
||||
import { after } from 'next/server'
|
||||
import { cookies, headers } from 'next/headers'
|
||||
import { logUserAction } from '@/app/utils'
|
||||
|
||||
export default async function Page() {
|
||||
// Read request data before `after` — this is allowed
|
||||
// These calls will be read during the component's rendering lifecycle
|
||||
const userAgent = (await headers()).get('user-agent') || 'unknown'
|
||||
const sessionCookie =
|
||||
(await cookies()).get('session-id')?.value || 'anonymous'
|
||||
|
||||
after(() => {
|
||||
// Use the values read above
|
||||
logUserAction({ sessionCookie, userAgent })
|
||||
})
|
||||
|
||||
return <h1>My Page</h1>
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/page.jsx" highlight={8-10,12} switcher
|
||||
import { after } from 'next/server'
|
||||
import { cookies, headers } from 'next/headers'
|
||||
import { logUserAction } from '@/app/utils'
|
||||
|
||||
export default async function Page() {
|
||||
// Read request data before `after` — this is allowed
|
||||
// These calls will be read during the component's rendering lifecycle
|
||||
const userAgent = (await headers()).get('user-agent') || 'unknown'
|
||||
const sessionCookie =
|
||||
(await cookies()).get('session-id')?.value || 'anonymous'
|
||||
|
||||
after(() => {
|
||||
// Use the values read above
|
||||
logUserAction({ sessionCookie, userAgent })
|
||||
})
|
||||
|
||||
return <h1>My Page</h1>
|
||||
}
|
||||
```
|
||||
|
||||
Calling `cookies()` or `headers()` inside the `after` callback in a Server Component will throw a runtime error.
|
||||
|
||||
#### With Cache Components
|
||||
|
||||
When using [Cache Components](/docs/app/getting-started/caching), components that access request data like `cookies` or `headers` must be wrapped in [`<Suspense>`](https://react.dev/reference/react/Suspense) so the rest of the page can be prerendered into a static shell.
|
||||
|
||||
You can combine this pattern with `after` by reading request data in a dynamic component and passing it into `after`:
|
||||
|
||||
```tsx filename="app/page.tsx" highlight={18-19,22-24} switcher
|
||||
import { Suspense } from 'react'
|
||||
import { after } from 'next/server'
|
||||
import { cookies } from 'next/headers'
|
||||
import { logUserAction } from '@/app/utils'
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<>
|
||||
<h1>Part of the static shell</h1>
|
||||
<Suspense fallback={<p>Loading...</p>}>
|
||||
<DynamicContent />
|
||||
</Suspense>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
async function DynamicContent() {
|
||||
const sessionCookie =
|
||||
(await cookies()).get('session-id')?.value || 'anonymous'
|
||||
|
||||
// Schedule work after the response is sent
|
||||
after(() => {
|
||||
logUserAction({ sessionCookie })
|
||||
})
|
||||
|
||||
return <p>Your session: {sessionCookie}</p>
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/page.jsx" highlight={18-19,22-24} switcher
|
||||
import { Suspense } from 'react'
|
||||
import { after } from 'next/server'
|
||||
import { cookies } from 'next/headers'
|
||||
import { logUserAction } from '@/app/utils'
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<>
|
||||
<h1>Part of the static shell</h1>
|
||||
<Suspense fallback={<p>Loading...</p>}>
|
||||
<DynamicContent />
|
||||
</Suspense>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
async function DynamicContent() {
|
||||
const sessionCookie =
|
||||
(await cookies()).get('session-id')?.value || 'anonymous'
|
||||
|
||||
// Schedule work after the response is sent
|
||||
after(() => {
|
||||
logUserAction({ sessionCookie })
|
||||
})
|
||||
|
||||
return <p>Your session: {sessionCookie}</p>
|
||||
}
|
||||
```
|
||||
|
||||
In this example, `<h1>` and the `<Suspense>` fallback are included in the static shell. `DynamicContent` reads the cookie during rendering and passes it into `after` via closure. Since `cookies()` is called _outside_ the `after` callback (during the component's render), this works correctly.
|
||||
|
||||
## Platform Support
|
||||
|
||||
| Deployment Option | Supported |
|
||||
| ------------------------------------------------------------------- | ----------------- |
|
||||
| [Node.js server](/docs/app/getting-started/deploying#nodejs-server) | Yes |
|
||||
| [Docker container](/docs/app/getting-started/deploying#docker) | Yes |
|
||||
| [Static export](/docs/app/getting-started/deploying#static-export) | No |
|
||||
| [Adapters](/docs/app/getting-started/deploying#adapters) | Platform-specific |
|
||||
|
||||
Learn how to [configure `after`](/docs/app/guides/self-hosting#after) when self-hosting Next.js.
|
||||
|
||||
<details id="after-serverless">
|
||||
<summary>Reference: supporting `after` for serverless platforms</summary>
|
||||
|
||||
Using `after` in a serverless context requires waiting for asynchronous tasks to finish after the response has been sent. In Next.js and Vercel, this is achieved using a primitive called `waitUntil(promise)`, which extends the lifetime of a serverless invocation until all promises passed to [`waitUntil`](https://vercel.com/docs/functions/functions-api-reference#waituntil) have settled.
|
||||
|
||||
If you want your users to be able to run `after`, you will have to provide your implementation of `waitUntil` that behaves in an analogous way.
|
||||
|
||||
When `after` is called, Next.js will access `waitUntil` like this:
|
||||
|
||||
```jsx
|
||||
const RequestContext = globalThis[Symbol.for('@next/request-context')]
|
||||
const contextValue = RequestContext?.get()
|
||||
const waitUntil = contextValue?.waitUntil
|
||||
```
|
||||
|
||||
Which means that `globalThis[Symbol.for('@next/request-context')]` is expected to contain an object like this:
|
||||
|
||||
```tsx
|
||||
type NextRequestContext = {
|
||||
get(): NextRequestContextValue | undefined
|
||||
}
|
||||
|
||||
type NextRequestContextValue = {
|
||||
waitUntil?: (promise: Promise<any>) => void
|
||||
}
|
||||
```
|
||||
|
||||
Here is an example of the implementation.
|
||||
|
||||
```tsx
|
||||
import { AsyncLocalStorage } from 'node:async_hooks'
|
||||
|
||||
const RequestContextStorage = new AsyncLocalStorage<NextRequestContextValue>()
|
||||
|
||||
// Define and inject the accessor that next.js will use
|
||||
const RequestContext: NextRequestContext = {
|
||||
get() {
|
||||
return RequestContextStorage.getStore()
|
||||
},
|
||||
}
|
||||
globalThis[Symbol.for('@next/request-context')] = RequestContext
|
||||
|
||||
const handler = (req, res) => {
|
||||
const contextValue = { waitUntil: YOUR_WAITUNTIL }
|
||||
// Provide the value
|
||||
return RequestContextStorage.run(contextValue, () => nextJsHandler(req, res))
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## Version History
|
||||
|
||||
| Version | Changes |
|
||||
| ------------ | ---------------------------- |
|
||||
| `v15.1.0` | `after` became stable. |
|
||||
| `v15.0.0-rc` | `unstable_after` introduced. |
|
||||
Generated
Vendored
+584
@@ -0,0 +1,584 @@
|
||||
---
|
||||
title: cacheLife
|
||||
description: Learn how to use the cacheLife function to set the cache expiration time for a cached function or component.
|
||||
related:
|
||||
title: Related
|
||||
description: View related API references.
|
||||
links:
|
||||
- app/api-reference/config/next-config-js/cacheComponents
|
||||
- app/api-reference/directives/use-cache
|
||||
- app/api-reference/functions/revalidateTag
|
||||
- app/api-reference/functions/cacheTag
|
||||
---
|
||||
|
||||
The `cacheLife` function is used to set the cache lifetime of a function or component. It should be used alongside the [`use cache`](/docs/app/api-reference/directives/use-cache) directive, and within the scope of the function or component.
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic setup
|
||||
|
||||
To use `cacheLife`, first enable the [`cacheComponents` flag](/docs/app/api-reference/config/next-config-js/cacheComponents) in your `next.config.js` file:
|
||||
|
||||
```ts filename="next.config.ts" switcher
|
||||
import type { NextConfig } from 'next'
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
cacheComponents: true,
|
||||
}
|
||||
|
||||
export default nextConfig
|
||||
```
|
||||
|
||||
```js filename="next.config.js" switcher
|
||||
const nextConfig = {
|
||||
cacheComponents: true,
|
||||
}
|
||||
|
||||
export default nextConfig
|
||||
```
|
||||
|
||||
`cacheLife` requires the `use cache` directive, which must be placed at the file level or at the top of an async function or component.
|
||||
|
||||
> **Good to know**:
|
||||
>
|
||||
> - If used, `cacheLife` should be placed within the function whose output is being cached, even when the `use cache` directive is at file level
|
||||
> - Only one `cacheLife` call should execute per function invocation. You can call `cacheLife` in different control flow branches, but ensure only one executes per run. See the [conditional cache lifetimes](#conditional-cache-lifetimes) example
|
||||
|
||||
### Using preset profiles
|
||||
|
||||
Next.js provides preset cache profiles that cover common caching needs. Each profile balances three factors:
|
||||
|
||||
- How long users see cached content without checking for updates (client-side)
|
||||
- How often fresh content is generated on the server
|
||||
- When old content expires completely
|
||||
|
||||
Choose a profile based on how frequently your content changes:
|
||||
|
||||
- **`seconds`** - Real-time data (stock prices, live scores)
|
||||
- **`minutes`** - Frequently updated (social feeds, news)
|
||||
- **`hours`** - Multiple daily updates (product inventory, weather)
|
||||
- **`days`** - Daily updates (blog posts, articles)
|
||||
- **`weeks`** - Weekly updates (podcasts, newsletters)
|
||||
- **`max`** - Rarely changes (legal pages, archived content)
|
||||
|
||||
Import `cacheLife` and pass a profile name:
|
||||
|
||||
```tsx filename="app/blog/page.tsx" highlight={1,5}
|
||||
'use cache'
|
||||
import { cacheLife } from 'next/cache'
|
||||
|
||||
export default async function BlogPage() {
|
||||
cacheLife('days') // Blog content updated daily
|
||||
|
||||
const posts = await getBlogPosts()
|
||||
return <div>{/* render posts */}</div>
|
||||
}
|
||||
```
|
||||
|
||||
The profile name tells Next.js how to cache the entire function's output. If you don't call `cacheLife`, the `default` profile is used. See [preset cache profiles](#preset-cache-profiles) for timing details.
|
||||
|
||||
## Reference
|
||||
|
||||
### Cache profile properties
|
||||
|
||||
Cache profiles control caching behavior through three timing properties:
|
||||
|
||||
- **[`stale`](#stale)**: How long the client can use cached data without checking the server
|
||||
- **[`revalidate`](#revalidate)**: After this time, the next request will trigger a background refresh
|
||||
- **[`expire`](#expire)**: After this time with no requests, the next one waits for fresh content
|
||||
|
||||
#### `stale`
|
||||
|
||||
**Client-side:** How long the client can use cached data without checking the server.
|
||||
|
||||
During this time, the client-side router displays cached content immediately without any network request. After this period expires, the router must check with the server on the next navigation or request. This provides instant page loads from the client cache, but data may be outdated.
|
||||
|
||||
- If omitted, defaults to the `default` profile's `stale` value (5 minutes, see [`staleTimes`](/docs/app/api-reference/config/next-config-js/staleTimes))
|
||||
|
||||
```tsx
|
||||
cacheLife({ stale: 300 }) // 5 minutes
|
||||
```
|
||||
|
||||
#### `revalidate`
|
||||
|
||||
How often the server regenerates cached content in the background.
|
||||
|
||||
- When a request arrives after this period, the server:
|
||||
1. Serves the cached version immediately (if available)
|
||||
2. Regenerates content in the background
|
||||
3. Updates the cache with fresh content
|
||||
- Similar to [Incremental Static Regeneration (ISR)](/docs/app/guides/incremental-static-regeneration)
|
||||
- If omitted, defaults to the `default` profile's `revalidate` value (15 minutes)
|
||||
|
||||
```tsx
|
||||
cacheLife({ revalidate: 900 }) // 15 minutes
|
||||
```
|
||||
|
||||
#### `expire`
|
||||
|
||||
Maximum time before the server must regenerate cached content.
|
||||
|
||||
- After this period with no traffic, the server regenerates content synchronously on the next request
|
||||
- When you set both `revalidate` and `expire`, `expire` must be longer than `revalidate`. Next.js validates this and raises an error for invalid configurations.
|
||||
- If omitted, defaults to the `default` profile's `expire` value (never expires)
|
||||
|
||||
```tsx
|
||||
cacheLife({ expire: 3600 }) // 1 hour
|
||||
```
|
||||
|
||||
### Preset cache profiles
|
||||
|
||||
If you don't specify a profile, Next.js uses the `default` profile. We recommend explicitly setting a profile to make caching behavior clear.
|
||||
|
||||
| **Profile** | **Use Case** | `stale` | `revalidate` | `expire` |
|
||||
| ----------- | -------------------------------------- | ---------- | ------------ | -------- |
|
||||
| `default` | Standard content | 5 minutes | 15 minutes | never |
|
||||
| `seconds` | Real-time data | 30 seconds | 1 second | 1 minute |
|
||||
| `minutes` | Frequently updated content | 5 minutes | 1 minute | 1 hour |
|
||||
| `hours` | Content updated multiple times per day | 5 minutes | 1 hour | 1 day |
|
||||
| `days` | Content updated daily | 5 minutes | 1 day | 1 week |
|
||||
| `weeks` | Content updated weekly | 5 minutes | 1 week | 30 days |
|
||||
| `max` | Stable content that rarely changes | 5 minutes | 30 days | 1 year |
|
||||
|
||||
### Custom cache profiles
|
||||
|
||||
Define reusable cache profiles in your `next.config.ts` file:
|
||||
|
||||
```ts filename="next.config.ts"
|
||||
import type { NextConfig } from 'next'
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
cacheComponents: true,
|
||||
cacheLife: {
|
||||
biweekly: {
|
||||
stale: 60 * 60 * 24 * 14, // 14 days
|
||||
revalidate: 60 * 60 * 24, // 1 day
|
||||
expire: 60 * 60 * 24 * 14, // 14 days
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export default nextConfig
|
||||
```
|
||||
|
||||
```js filename="next.config.js" switcher
|
||||
const nextConfig = {
|
||||
cacheComponents: true,
|
||||
cacheLife: {
|
||||
biweekly: {
|
||||
stale: 60 * 60 * 24 * 14, // 14 days
|
||||
revalidate: 60 * 60 * 24, // 1 day
|
||||
expire: 60 * 60 * 24 * 14, // 14 days
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
module.exports = nextConfig
|
||||
```
|
||||
|
||||
The example above caches for 14 days, checks for updates daily, and expires the cache after 14 days. You can then reference this profile throughout your application by its name:
|
||||
|
||||
> **Good to know**: Any omitted properties in a custom profile inherit from the `default` profile. This also applies to inline profile objects passed directly to `cacheLife()`.
|
||||
|
||||
```tsx filename="app/page.tsx" highlight={5}
|
||||
'use cache'
|
||||
import { cacheLife } from 'next/cache'
|
||||
|
||||
export default async function Page() {
|
||||
cacheLife('biweekly')
|
||||
return <div>Page</div>
|
||||
}
|
||||
```
|
||||
|
||||
### Overriding the default cache profiles
|
||||
|
||||
While the default cache profiles provide a useful way to think about how fresh or stale any given part of cacheable output can be, you may prefer different named profiles to better align with your applications caching strategies.
|
||||
|
||||
You can override the default named cache profiles by creating a new configuration with the same name as the defaults.
|
||||
|
||||
The example below shows how to override the default `"days"` cache profile:
|
||||
|
||||
```ts filename="next.config.ts"
|
||||
const nextConfig = {
|
||||
cacheComponents: true,
|
||||
cacheLife: {
|
||||
// Override the 'days' profile
|
||||
days: {
|
||||
stale: 3600, // 1 hour
|
||||
revalidate: 900, // 15 minutes
|
||||
expire: 86400, // 1 day
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export default nextConfig
|
||||
```
|
||||
|
||||
### Inline cache profiles
|
||||
|
||||
For one-off cases, pass a profile object directly to `cacheLife`:
|
||||
|
||||
```tsx filename="app/page.tsx"
|
||||
'use cache'
|
||||
import { cacheLife } from 'next/cache'
|
||||
|
||||
export default async function Page() {
|
||||
cacheLife({
|
||||
stale: 3600,
|
||||
revalidate: 900,
|
||||
expire: 86400,
|
||||
})
|
||||
|
||||
return <div>Page</div>
|
||||
}
|
||||
```
|
||||
|
||||
Inline profiles apply only to the specific function or component. For reusable configurations, define custom profiles in `next.config.ts`.
|
||||
|
||||
Using `cacheLife({})` with an empty object applies the `default` profile values.
|
||||
|
||||
### Client cache behavior
|
||||
|
||||
The `stale` property controls the [Client Cache](/docs/app/glossary#client-cache), not the `Cache-Control` header:
|
||||
|
||||
- The server sends the stale time via the `x-nextjs-stale-time` response header
|
||||
- The client router uses this value to determine when to revalidate
|
||||
- **Minimum of 30 seconds is enforced** to ensure prefetched links remain usable
|
||||
|
||||
This 30-second minimum prevents prefetched data from expiring before users can click on links. It only applies to time-based expiration.
|
||||
|
||||
When you call revalidation functions from a Server Action ([`revalidateTag`](/docs/app/api-reference/functions/revalidateTag), [`revalidatePath`](/docs/app/api-reference/functions/revalidatePath), [`updateTag`](/docs/app/api-reference/functions/updateTag), or [`refresh`](/docs/app/api-reference/functions/refresh)), the entire client cache is immediately cleared, bypassing the stale time.
|
||||
|
||||
> **Good to know**: The `stale` property in `cacheLife` differs from [`staleTimes`](/docs/app/api-reference/config/next-config-js/staleTimes). While `staleTimes` is a global setting affecting all routes, `cacheLife` allows per-function or per-route configuration. Updating `staleTimes.static` also updates the `stale` value of the `default` cache profile.
|
||||
|
||||
### Prerendering behavior
|
||||
|
||||
Caches with very short lifetimes — zero `revalidate` or `expire` under 5 minutes — are automatically excluded from prerenders and become "dynamic holes" instead. This includes the `seconds` profile.
|
||||
|
||||
This behavior allows you to mix static and dynamic content within the same page. Static parts are prerendered, while short-lived caches create boundaries where data is fetched at request time rather than build time. Use a `<Suspense>` boundary around dynamic caches to provide a fallback while content loads.
|
||||
|
||||
## Examples
|
||||
|
||||
### Using preset profiles
|
||||
|
||||
The simplest way to configure caching is using preset profiles. Choose one that matches your content's update pattern:
|
||||
|
||||
```tsx filename="app/blog/[slug]/page.tsx"
|
||||
import { cacheLife } from 'next/cache'
|
||||
|
||||
export default async function BlogPost() {
|
||||
'use cache'
|
||||
cacheLife('days') // Blog posts updated daily
|
||||
|
||||
const post = await fetchBlogPost()
|
||||
return <article>{post.content}</article>
|
||||
}
|
||||
```
|
||||
|
||||
```tsx filename="app/products/[id]/page.tsx"
|
||||
import { cacheLife } from 'next/cache'
|
||||
|
||||
export default async function ProductPage() {
|
||||
'use cache'
|
||||
cacheLife('hours') // Product data updated multiple times per day
|
||||
|
||||
const product = await fetchProduct()
|
||||
return <div>{product.name}</div>
|
||||
}
|
||||
```
|
||||
|
||||
### Custom profiles for specific needs
|
||||
|
||||
Define custom profiles when preset options don't match your requirements:
|
||||
|
||||
```ts filename="next.config.ts"
|
||||
import type { NextConfig } from 'next'
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
cacheComponents: true,
|
||||
cacheLife: {
|
||||
editorial: {
|
||||
stale: 600, // 10 minutes
|
||||
revalidate: 3600, // 1 hour
|
||||
expire: 86400, // 1 day
|
||||
},
|
||||
marketing: {
|
||||
stale: 300, // 5 minutes
|
||||
revalidate: 1800, // 30 minutes
|
||||
expire: 43200, // 12 hours
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export default nextConfig
|
||||
```
|
||||
|
||||
Then use these profiles throughout your application:
|
||||
|
||||
```tsx filename="app/editorial/page.tsx"
|
||||
import { cacheLife } from 'next/cache'
|
||||
|
||||
export default async function EditorialPage() {
|
||||
'use cache'
|
||||
cacheLife('editorial')
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### Inline profiles for unique cases
|
||||
|
||||
Use inline profiles when a specific function needs one-off caching behavior:
|
||||
|
||||
```tsx filename="app/api/limited-offer/route.ts"
|
||||
import { cacheLife } from 'next/cache'
|
||||
import { getDb } from '@lib/db'
|
||||
|
||||
async function getLimitedOffer() {
|
||||
'use cache'
|
||||
|
||||
cacheLife({
|
||||
stale: 60, // 1 minute
|
||||
revalidate: 300, // 5 minutes
|
||||
expire: 3600, // 1 hour
|
||||
})
|
||||
|
||||
const offer = await getDb().offer.findFirst({
|
||||
where: { type: 'limited' },
|
||||
orderBy: { created_at: 'desc' },
|
||||
})
|
||||
|
||||
return offer
|
||||
}
|
||||
|
||||
export async function GET() {
|
||||
const offer = await getLimitedOffer()
|
||||
|
||||
return Response.json(offer)
|
||||
}
|
||||
```
|
||||
|
||||
### Caching individual functions
|
||||
|
||||
Apply caching to utility functions for granular control:
|
||||
|
||||
```tsx filename="lib/api.ts"
|
||||
import { cacheLife } from 'next/cache'
|
||||
|
||||
export async function getSettings() {
|
||||
'use cache'
|
||||
cacheLife('max') // Settings rarely change
|
||||
|
||||
return await fetchSettings()
|
||||
}
|
||||
```
|
||||
|
||||
```tsx filename="lib/stats.ts"
|
||||
import { cacheLife } from 'next/cache'
|
||||
|
||||
export async function getRealtimeStats() {
|
||||
'use cache'
|
||||
cacheLife('seconds') // Stats update constantly
|
||||
|
||||
return await fetchStats()
|
||||
}
|
||||
```
|
||||
|
||||
### Nested caching behavior
|
||||
|
||||
When you nest `use cache` directives (a cached function or component using another cached function or component), the outer cache's behavior depends on whether it has an explicit `cacheLife`.
|
||||
|
||||
#### With explicit outer cacheLife
|
||||
|
||||
The outer cache uses its own lifetime, regardless of inner cache lifetimes. When the outer cache hits, it returns the complete output including all nested data. An explicit `cacheLife` always takes precedence, whether it's longer or shorter than inner lifetimes.
|
||||
|
||||
```tsx filename="app/dashboard/page.tsx"
|
||||
import { cacheLife } from 'next/cache'
|
||||
import { Widget } from './widget'
|
||||
|
||||
export default async function Dashboard() {
|
||||
'use cache'
|
||||
cacheLife('hours') // Outer scope sets its own lifetime
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Dashboard</h1>
|
||||
<Widget /> {/* Inner scope has 'minutes' lifetime */}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
#### Without explicit outer cacheLife
|
||||
|
||||
If you don't call `cacheLife` in the outer cache, it uses the `default` profile (15 min revalidate). Inner caches with shorter lifetimes can reduce the outer cache's `default` lifetime. Inner caches with longer lifetimes cannot extend it beyond the default.
|
||||
|
||||
```tsx filename="app/dashboard/page.tsx"
|
||||
import { Widget } from './widget'
|
||||
|
||||
export default async function Dashboard() {
|
||||
'use cache'
|
||||
// No cacheLife call - uses default (15 min)
|
||||
// If Widget has 5 min → Dashboard becomes 5 min
|
||||
// If Widget has 1 hour → Dashboard stays 15 min
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Dashboard</h1>
|
||||
<Widget />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
**It is recommended to specify an explicit `cacheLife`.** With explicit lifetime values, you can inspect a cached function or component and immediately know its behavior without tracing through nested caches. Without explicit lifetime values, the behavior becomes dependent on inner cache lifetimes, making it harder to reason about.
|
||||
|
||||
#### Nested short-lived caches
|
||||
|
||||
As described in [Prerendering behavior](#prerendering-behavior), short-lived caches (zero `revalidate` or `expire` under 5 minutes) become dynamic holes excluded from prerenders.
|
||||
|
||||
When a short-lived cache is nested inside another `use cache` without an explicit `cacheLife`, the outer cache's lifetime would silently become short too via propagation. To prevent this accidental misconfiguration, Next.js throws an error during prerendering.
|
||||
|
||||
Note that the nested cache may not be obvious — it could be in an imported module or even a third-party dependency:
|
||||
|
||||
```tsx filename="components/short-lived-widget.tsx" highlight={5}
|
||||
import { cacheLife } from 'next/cache'
|
||||
|
||||
export async function ShortLivedWidget() {
|
||||
'use cache'
|
||||
cacheLife('seconds')
|
||||
const data = await fetchRealtimeData()
|
||||
return <div>{data}</div>
|
||||
}
|
||||
```
|
||||
|
||||
Using this component from another `use cache` without an explicit `cacheLife` will error during prerendering:
|
||||
|
||||
```tsx filename="app/page.tsx"
|
||||
import { ShortLivedWidget } from '@/components/short-lived-widget'
|
||||
|
||||
export default async function Page() {
|
||||
'use cache'
|
||||
// Error: no explicit cacheLife on outer cache
|
||||
return (
|
||||
<div>
|
||||
<h1>Dashboard</h1>
|
||||
<p>Last updated: {new Date().toISOString()}</p>
|
||||
<ShortLivedWidget />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
To fix the error, add an explicit `cacheLife()` to the outer `use cache`:
|
||||
|
||||
**If you want the outer cache to remain static (prerendered)**, set a longer cache lifetime:
|
||||
|
||||
```tsx filename="app/page.tsx" highlight={6}
|
||||
import { cacheLife } from 'next/cache'
|
||||
import { ShortLivedWidget } from '@/components/short-lived-widget'
|
||||
|
||||
export default async function Page() {
|
||||
'use cache'
|
||||
cacheLife('default') // Explicit cacheLife prevents the error
|
||||
return (
|
||||
<div>
|
||||
<h1>Dashboard</h1>
|
||||
<p>Last updated: {new Date().toISOString()}</p>
|
||||
<ShortLivedWidget />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
**If you want the outer cache to also be short-lived**, explicitly set a short cache lifetime to confirm this is intentional. Wrap the component in a `<Suspense>` boundary to provide a fallback while content loads:
|
||||
|
||||
```tsx filename="app/page.tsx" highlight={7,17-19}
|
||||
import { Suspense } from 'react'
|
||||
import { cacheLife } from 'next/cache'
|
||||
import { ShortLivedWidget } from '@/components/short-lived-widget'
|
||||
|
||||
async function Content() {
|
||||
'use cache: remote'
|
||||
cacheLife('seconds') // Explicit cacheLife confirms this is intentionally short-lived
|
||||
return (
|
||||
<>
|
||||
<p>Last updated: {new Date().toISOString()}</p>
|
||||
<ShortLivedWidget />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<div>
|
||||
<h1>Dashboard</h1>
|
||||
<Suspense fallback={<p>Loading...</p>}>
|
||||
<Content />
|
||||
</Suspense>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
> **Note:** This example uses `"use cache: remote"` because runtime caching in serverless deployments doesn't persist across requests with the default in-memory cache. For self-hosted environments, `"use cache"` may be sufficient. See [Runtime caching considerations](/docs/app/api-reference/directives/use-cache#runtime-caching-considerations) for more details.
|
||||
|
||||
### Conditional cache lifetimes
|
||||
|
||||
You can call `cacheLife` conditionally in different code paths to set different cache durations based on your application logic:
|
||||
|
||||
```tsx filename="lib/posts.ts" highlight={14,19}
|
||||
import { cacheLife, cacheTag } from 'next/cache'
|
||||
|
||||
async function getPostContent(slug: string) {
|
||||
'use cache'
|
||||
|
||||
const post = await fetchPost(slug)
|
||||
|
||||
// Tag the cache entry for targeted revalidation
|
||||
cacheTag(`post-${slug}`)
|
||||
|
||||
if (!post) {
|
||||
// Content may not be published yet or could be in draft
|
||||
// Cache briefly to reduce database load
|
||||
cacheLife('minutes')
|
||||
return null
|
||||
}
|
||||
|
||||
// Published content can be cached longer
|
||||
cacheLife('days')
|
||||
|
||||
// Return only the necessary data to keep cache size minimal
|
||||
return post.data
|
||||
}
|
||||
```
|
||||
|
||||
This pattern is useful when different outcomes need different cache durations, for example, when an item is missing but is likely to be available later.
|
||||
|
||||
#### Using dynamic cache lifetimes from data
|
||||
|
||||
If you want to calculate cache lifetime at runtime, for example by reading it from the fetched data, use an [inline cache profile](#inline-cache-profiles) object:
|
||||
|
||||
```tsx filename="lib/posts.ts" highlight={15,16,17,18}
|
||||
import { cacheLife, cacheTag } from 'next/cache'
|
||||
|
||||
async function getPostContent(slug: string) {
|
||||
'use cache'
|
||||
|
||||
const post = await fetchPost(slug)
|
||||
cacheTag(`post-${slug}`)
|
||||
|
||||
if (!post) {
|
||||
cacheLife('minutes')
|
||||
return null
|
||||
}
|
||||
|
||||
// Use cache timing from CMS data directly as an object
|
||||
cacheLife({
|
||||
// Ensure post.revalidateSeconds is a number in seconds
|
||||
// stale and expire inherit from 'default' profile
|
||||
revalidate: post.revalidateSeconds ?? 3600,
|
||||
})
|
||||
|
||||
return post.data
|
||||
}
|
||||
```
|
||||
Generated
Vendored
+199
@@ -0,0 +1,199 @@
|
||||
---
|
||||
title: cacheTag
|
||||
description: Learn how to use the cacheTag function to manage cache invalidation in your Next.js application.
|
||||
related:
|
||||
title: Related
|
||||
description: View related API references.
|
||||
links:
|
||||
- app/api-reference/config/next-config-js/cacheComponents
|
||||
- app/api-reference/directives/use-cache
|
||||
- app/api-reference/functions/revalidateTag
|
||||
- app/api-reference/functions/cacheLife
|
||||
---
|
||||
|
||||
The `cacheTag` function allows you to tag cached data for on-demand invalidation. By associating tags with cache entries, you can selectively purge or revalidate specific cache entries without affecting other cached data.
|
||||
|
||||
## Usage
|
||||
|
||||
To use `cacheTag`, enable the [`cacheComponents` flag](/docs/app/api-reference/config/next-config-js/cacheComponents) in your `next.config.js` file:
|
||||
|
||||
```ts filename="next.config.ts" switcher
|
||||
import type { NextConfig } from 'next'
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
cacheComponents: true,
|
||||
}
|
||||
|
||||
export default nextConfig
|
||||
```
|
||||
|
||||
```js filename="next.config.js" switcher
|
||||
const nextConfig = {
|
||||
cacheComponents: true,
|
||||
}
|
||||
|
||||
export default nextConfig
|
||||
```
|
||||
|
||||
The `cacheTag` function takes one or more string values.
|
||||
|
||||
```tsx filename="app/data.ts" switcher
|
||||
import { cacheTag } from 'next/cache'
|
||||
|
||||
export async function getData() {
|
||||
'use cache'
|
||||
cacheTag('my-data')
|
||||
const data = await fetch('/api/data')
|
||||
return data
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/data.js" switcher
|
||||
import { cacheTag } from 'next/cache'
|
||||
|
||||
export async function getData() {
|
||||
'use cache'
|
||||
cacheTag('my-data')
|
||||
const data = await fetch('/api/data')
|
||||
return data
|
||||
}
|
||||
```
|
||||
|
||||
You can then purge the cache on-demand using [`revalidateTag`](/docs/app/api-reference/functions/revalidateTag) API in another function, for example, a [route handler](/docs/app/api-reference/file-conventions/route) or [Server Action](/docs/app/getting-started/mutating-data):
|
||||
|
||||
```tsx filename="app/action.ts" switcher
|
||||
'use server'
|
||||
|
||||
import { revalidateTag } from 'next/cache'
|
||||
|
||||
export default async function submit() {
|
||||
await addPost()
|
||||
revalidateTag('my-data')
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/action.js" switcher
|
||||
'use server'
|
||||
|
||||
import { revalidateTag } from 'next/cache'
|
||||
|
||||
export default async function submit() {
|
||||
await addPost()
|
||||
revalidateTag('my-data')
|
||||
}
|
||||
```
|
||||
|
||||
## Good to know
|
||||
|
||||
- **Idempotent Tags**: Applying the same tag multiple times has no additional effect.
|
||||
- **Multiple Tags**: You can assign multiple tags to a single cache entry by passing multiple string values to `cacheTag`.
|
||||
|
||||
```tsx
|
||||
cacheTag('tag-one', 'tag-two')
|
||||
```
|
||||
|
||||
- **Limits**: The max length for a custom tag is 256 characters and the max tag items is 128.
|
||||
|
||||
## Examples
|
||||
|
||||
### Tagging components or functions
|
||||
|
||||
Tag your cached data by calling `cacheTag` within a cached function or component:
|
||||
|
||||
```tsx filename="app/components/bookings.tsx" switcher
|
||||
import { cacheTag } from 'next/cache'
|
||||
|
||||
interface BookingsProps {
|
||||
type: string
|
||||
}
|
||||
|
||||
export async function Bookings({ type = 'haircut' }: BookingsProps) {
|
||||
'use cache'
|
||||
cacheTag('bookings-data')
|
||||
|
||||
async function getBookingsData() {
|
||||
const data = await fetch(`/api/bookings?type=${encodeURIComponent(type)}`)
|
||||
return data
|
||||
}
|
||||
|
||||
return //...
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/components/bookings.js" switcher
|
||||
import { cacheTag } from 'next/cache'
|
||||
|
||||
export async function Bookings({ type = 'haircut' }) {
|
||||
'use cache'
|
||||
cacheTag('bookings-data')
|
||||
|
||||
async function getBookingsData() {
|
||||
const data = await fetch(`/api/bookings?type=${encodeURIComponent(type)}`)
|
||||
return data
|
||||
}
|
||||
|
||||
return //...
|
||||
}
|
||||
```
|
||||
|
||||
### Creating tags from external data
|
||||
|
||||
You can use the data returned from an async function to tag the cache entry.
|
||||
|
||||
```tsx filename="app/components/bookings.tsx" switcher
|
||||
import { cacheTag } from 'next/cache'
|
||||
|
||||
interface BookingsProps {
|
||||
type: string
|
||||
}
|
||||
|
||||
export async function Bookings({ type = 'haircut' }: BookingsProps) {
|
||||
async function getBookingsData() {
|
||||
'use cache'
|
||||
const data = await fetch(`/api/bookings?type=${encodeURIComponent(type)}`)
|
||||
cacheTag('bookings-data', data.id)
|
||||
return data
|
||||
}
|
||||
return //...
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/components/bookings.js" switcher
|
||||
import { cacheTag } from 'next/cache'
|
||||
|
||||
export async function Bookings({ type = 'haircut' }) {
|
||||
async function getBookingsData() {
|
||||
'use cache'
|
||||
const data = await fetch(`/api/bookings?type=${encodeURIComponent(type)}`)
|
||||
cacheTag('bookings-data', data.id)
|
||||
return data
|
||||
}
|
||||
return //...
|
||||
}
|
||||
```
|
||||
|
||||
### Invalidating tagged cache
|
||||
|
||||
Using [`revalidateTag`](/docs/app/api-reference/functions/revalidateTag), you can invalidate the cache for a specific tag when needed:
|
||||
|
||||
```tsx filename="app/actions.ts" switcher
|
||||
'use server'
|
||||
|
||||
import { revalidateTag } from 'next/cache'
|
||||
|
||||
export async function updateBookings() {
|
||||
await updateBookingData()
|
||||
revalidateTag('bookings-data')
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/actions.js" switcher
|
||||
'use server'
|
||||
|
||||
import { revalidateTag } from 'next/cache'
|
||||
|
||||
export async function updateBookings() {
|
||||
await updateBookingData()
|
||||
revalidateTag('bookings-data')
|
||||
}
|
||||
```
|
||||
Generated
Vendored
+226
@@ -0,0 +1,226 @@
|
||||
---
|
||||
title: unstable_catchError
|
||||
description: API Reference for the unstable_catchError function.
|
||||
related:
|
||||
title: Learn more about error handling
|
||||
links:
|
||||
- app/getting-started/error-handling
|
||||
- app/api-reference/file-conventions/error
|
||||
---
|
||||
|
||||
The `unstable_catchError` function creates a component that wraps its children in an error boundary. It provides a programmatic alternative to the [`error.js`](/docs/app/api-reference/file-conventions/error) file convention, enabling component-level error recovery anywhere in your component tree.
|
||||
|
||||
Compared to a custom React error boundary, `unstable_catchError` is designed to work with Next.js out of the box:
|
||||
|
||||
- **Built-in error recovery** — [`unstable_retry()`](/docs/app/api-reference/file-conventions/error#unstable_retry) re-fetches and re-renders the error boundary's children, including Server Components.
|
||||
- **Framework-aware integration** — APIs like `redirect()` and `notFound()` work by throwing special errors under the hood. `unstable_catchError` handles these seamlessly, so they're not accidentally caught by your error boundary.
|
||||
- **Client navigation handling** — The error state automatically clears when you do a client navigation to a different route.
|
||||
|
||||
`unstable_catchError` can be called from [Client Components](/docs/app/getting-started/server-and-client-components).
|
||||
|
||||
```tsx filename="app/custom-error-boundary.tsx" switcher
|
||||
'use client'
|
||||
|
||||
import { unstable_catchError, type ErrorInfo } from 'next/error'
|
||||
|
||||
function ErrorFallback(
|
||||
props: { title: string },
|
||||
{ error, unstable_retry }: ErrorInfo
|
||||
) {
|
||||
return (
|
||||
<div>
|
||||
<h2>{props.title}</h2>
|
||||
<p>{error.message}</p>
|
||||
<button onClick={() => unstable_retry()}>Try again</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default unstable_catchError(ErrorFallback)
|
||||
```
|
||||
|
||||
```jsx filename="app/custom-error-boundary.js" switcher
|
||||
'use client'
|
||||
|
||||
import { unstable_catchError } from 'next/error'
|
||||
|
||||
function ErrorFallback(props, { error, unstable_retry }) {
|
||||
return (
|
||||
<div>
|
||||
<h2>{props.title}</h2>
|
||||
<p>{error.message}</p>
|
||||
<button onClick={() => unstable_retry()}>Try again</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default unstable_catchError(ErrorFallback)
|
||||
```
|
||||
|
||||
## Reference
|
||||
|
||||
### Parameters
|
||||
|
||||
`unstable_catchError` accepts a single argument:
|
||||
|
||||
```ts
|
||||
const ErrorWrapper = unstable_catchError(fallback)
|
||||
```
|
||||
|
||||
#### `fallback`
|
||||
|
||||
A function that renders the error UI when an error is caught. It receives two arguments:
|
||||
|
||||
- `props` — The props passed to the wrapper component (excluding `children`).
|
||||
- `errorInfo` — An object containing information about the error:
|
||||
|
||||
| Property | Type | Description |
|
||||
| ---------------- | ------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `error` | [`Error`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error) | The error instance that was caught. |
|
||||
| `unstable_retry` | `() => void` | Re-fetches and re-renders the error boundary's children. If successful, the fallback is replaced with the re-rendered result. |
|
||||
| `reset` | `() => void` | Resets the error state and re-renders without re-fetching. Use [`unstable_retry()`](/docs/app/api-reference/file-conventions/error#unstable_retry) in most cases. |
|
||||
|
||||
The `fallback` function must be a Client Component (or defined in a `'use client'` module).
|
||||
|
||||
### Returns
|
||||
|
||||
`unstable_catchError` returns a React component that:
|
||||
|
||||
- Accepts the same props as your fallback's first argument, plus `children`.
|
||||
- Wraps `children` in an error boundary.
|
||||
- Renders the `fallback` when an error is caught in `children`.
|
||||
|
||||
## Examples
|
||||
|
||||
### Client Component
|
||||
|
||||
Define a fallback and use the returned component to wrap parts of your UI:
|
||||
|
||||
```tsx filename="app/some-component.tsx" switcher
|
||||
import ErrorWrapper from '../custom-error-boundary'
|
||||
|
||||
export default function Component({ children }: { children: React.ReactNode }) {
|
||||
return <ErrorWrapper title="Dashboard Error">{children}</ErrorWrapper>
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/some-component.js" switcher
|
||||
import ErrorWrapper from '../custom-error-boundary'
|
||||
|
||||
export default function Component({ children }) {
|
||||
return <ErrorWrapper title="Dashboard Error">{children}</ErrorWrapper>
|
||||
}
|
||||
```
|
||||
|
||||
### Recovering from errors
|
||||
|
||||
Use `unstable_retry()` to prompt the user to recover from the error. When called, the function re-fetches and re-renders the error boundary's children. If successful, the fallback is replaced with the re-rendered result.
|
||||
|
||||
In most cases, use `unstable_retry()` instead of `reset()`. The `reset()` function only clears the error state and re-renders without re-fetching, which means it won't recover from Server Component errors.
|
||||
|
||||
```tsx filename="app/custom-error-boundary.tsx" switcher
|
||||
'use client'
|
||||
|
||||
import { unstable_catchError, type ErrorInfo } from 'next/error'
|
||||
|
||||
function ErrorFallback(props: {}, { error, unstable_retry, reset }: ErrorInfo) {
|
||||
return (
|
||||
<div>
|
||||
<p>{error.message}</p>
|
||||
<button onClick={() => unstable_retry()}>Try again</button>
|
||||
<button onClick={() => reset()}>Reset</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default unstable_catchError(ErrorFallback)
|
||||
```
|
||||
|
||||
```jsx filename="app/custom-error-boundary.js" switcher
|
||||
'use client'
|
||||
|
||||
import { unstable_catchError } from 'next/error'
|
||||
|
||||
function ErrorFallback(props, { error, unstable_retry, reset }) {
|
||||
return (
|
||||
<div>
|
||||
<p>{error.message}</p>
|
||||
<button onClick={() => unstable_retry()}>Try again</button>
|
||||
<button onClick={() => reset()}>Reset</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default unstable_catchError(ErrorFallback)
|
||||
```
|
||||
|
||||
### Server-rendered error fallback
|
||||
|
||||
You can pass server-rendered content as a prop to display data-driven fallback UI. This works by rendering a Server Component as a `React.ReactNode` prop that the fallback displays when an error is caught.
|
||||
|
||||
> **Good to know**: This pattern eagerly renders the fallback on every page render, even when no error occurs. For most use cases, a simpler client-side fallback is sufficient.
|
||||
|
||||
```tsx filename="app/error-boundary.tsx" switcher
|
||||
'use client'
|
||||
|
||||
import { unstable_catchError, type ErrorInfo } from 'next/error'
|
||||
|
||||
function ErrorFallback(
|
||||
props: { fallback: React.ReactNode },
|
||||
errorInfo: ErrorInfo
|
||||
) {
|
||||
return props.fallback
|
||||
}
|
||||
|
||||
export default unstable_catchError(ErrorFallback)
|
||||
```
|
||||
|
||||
```jsx filename="app/error-boundary.js" switcher
|
||||
'use client'
|
||||
|
||||
import { unstable_catchError } from 'next/error'
|
||||
|
||||
function ErrorFallback(props, errorInfo) {
|
||||
return props.fallback
|
||||
}
|
||||
|
||||
export default unstable_catchError(ErrorFallback)
|
||||
```
|
||||
|
||||
```tsx filename="app/some-component.tsx" switcher
|
||||
import ErrorBoundary from '../error-boundary'
|
||||
|
||||
async function ErrorFallback() {
|
||||
const data = await getData()
|
||||
return <div>{data.message}</div>
|
||||
}
|
||||
|
||||
export default function Component({ children }: { children: React.ReactNode }) {
|
||||
return <ErrorBoundary fallback={<ErrorFallback />}>{children}</ErrorBoundary>
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/some-component.js" switcher
|
||||
import ErrorBoundary from '../error-boundary'
|
||||
|
||||
async function ErrorFallback() {
|
||||
const data = await getData()
|
||||
return <div>{data.message}</div>
|
||||
}
|
||||
|
||||
export default function Component({ children }) {
|
||||
return <ErrorBoundary fallback={<ErrorFallback />}>{children}</ErrorBoundary>
|
||||
}
|
||||
```
|
||||
|
||||
> **Good to know**:
|
||||
>
|
||||
> - Unlike the `error.js` file convention which is scoped to route segments, `unstable_catchError` can be used to wrap any part of your component tree for component-level error recovery.
|
||||
> - Props passed to the wrapper component are forwarded to the fallback function, making it easy to create reusable error UIs with different configurations.
|
||||
> - You don't need to wrap `error.js` default exports with `unstable_catchError`. The [`error.js`](/docs/app/api-reference/file-conventions/error) file convention already renders inside a built-in error boundary provided by Next.js.
|
||||
|
||||
## Version History
|
||||
|
||||
| Version | Changes |
|
||||
| --------- | --------------------------------- |
|
||||
| `v16.2.0` | `unstable_catchError` introduced. |
|
||||
Generated
Vendored
+58
@@ -0,0 +1,58 @@
|
||||
---
|
||||
title: connection
|
||||
description: API Reference for the connection function.
|
||||
---
|
||||
|
||||
The `connection()` function allows you to indicate rendering should wait for an incoming user request before continuing.
|
||||
|
||||
It's useful when a component doesn't use [Request-time APIs](/docs/app/glossary#request-time-apis), but you want it to be rendered at runtime and not prerendered at build time. This usually occurs when you access external information that you intentionally want to change the result of a render, such as `Math.random()` or `new Date()`.
|
||||
|
||||
```ts filename="app/page.tsx" switcher
|
||||
import { connection } from 'next/server'
|
||||
|
||||
export default async function Page() {
|
||||
await connection()
|
||||
// Everything below will be excluded from prerendering
|
||||
const rand = Math.random()
|
||||
return <span>{rand}</span>
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/page.js" switcher
|
||||
import { connection } from 'next/server'
|
||||
|
||||
export default async function Page() {
|
||||
await connection()
|
||||
// Everything below will be excluded from prerendering
|
||||
const rand = Math.random()
|
||||
return <span>{rand}</span>
|
||||
}
|
||||
```
|
||||
|
||||
## Reference
|
||||
|
||||
### Type
|
||||
|
||||
```jsx
|
||||
function connection(): Promise<void>
|
||||
```
|
||||
|
||||
### Parameters
|
||||
|
||||
- The function does not accept any parameters.
|
||||
|
||||
### Returns
|
||||
|
||||
- The function returns a `void` Promise. It is not meant to be consumed.
|
||||
|
||||
## Good to know
|
||||
|
||||
- `connection` replaces [`unstable_noStore`](/docs/app/api-reference/functions/unstable_noStore) to better align with the future of Next.js.
|
||||
- The function is only necessary when dynamic rendering is required and common Request-time APIs are not used.
|
||||
|
||||
### Version History
|
||||
|
||||
| Version | Changes |
|
||||
| ------------ | ------------------------ |
|
||||
| `v15.0.0` | `connection` stabilized. |
|
||||
| `v15.0.0-RC` | `connection` introduced. |
|
||||
Generated
Vendored
+301
@@ -0,0 +1,301 @@
|
||||
---
|
||||
title: cookies
|
||||
description: API Reference for the cookies function.
|
||||
---
|
||||
|
||||
`cookies` is an **async** function that allows you to read the HTTP incoming request cookies in [Server Components](/docs/app/getting-started/server-and-client-components), and read/write outgoing request cookies in [Server Functions](/docs/app/getting-started/mutating-data) or [Route Handlers](/docs/app/api-reference/file-conventions/route).
|
||||
|
||||
```tsx filename="app/page.tsx" switcher
|
||||
import { cookies } from 'next/headers'
|
||||
|
||||
export default async function Page() {
|
||||
const cookieStore = await cookies()
|
||||
const theme = cookieStore.get('theme')
|
||||
return '...'
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="app/page.js" switcher
|
||||
import { cookies } from 'next/headers'
|
||||
|
||||
export default async function Page() {
|
||||
const cookieStore = await cookies()
|
||||
const theme = cookieStore.get('theme')
|
||||
return '...'
|
||||
}
|
||||
```
|
||||
|
||||
## Reference
|
||||
|
||||
### Methods
|
||||
|
||||
The following methods are available:
|
||||
|
||||
| Method | Return Type | Description |
|
||||
| --------------------------- | ---------------- | ------------------------------------------------------------------------------- |
|
||||
| `get('name')` | Object | Accepts a cookie name and returns an object with the name and value. |
|
||||
| `getAll()` | Array of objects | Returns a list of all the cookies with a matching name. |
|
||||
| `has('name')` | Boolean | Accepts a cookie name and returns a boolean based on if the cookie exists. |
|
||||
| `set(name, value, options)` | - | Accepts a cookie name, value, and options and sets the outgoing request cookie. |
|
||||
| `delete(name)` | - | Accepts a cookie name and deletes the cookie. |
|
||||
| `toString()` | String | Returns a string representation of the cookies. |
|
||||
|
||||
### Options
|
||||
|
||||
When setting a cookie, the following properties from the `options` object are supported:
|
||||
|
||||
| Option | Type | Description |
|
||||
| ------------- | -------------------------------------- | ---------------------------------------------------------------------------------- |
|
||||
| `name` | String | Specifies the name of the cookie. |
|
||||
| `value` | String | Specifies the value to be stored in the cookie. |
|
||||
| `expires` | Date | Defines the exact date when the cookie will expire. |
|
||||
| `maxAge` | Number | Sets the cookie’s lifespan in seconds. |
|
||||
| `domain` | String | Specifies the domain where the cookie is available. |
|
||||
| `path` | String, default: `'/'` | Limits the cookie's scope to a specific path within the domain. |
|
||||
| `secure` | Boolean | Ensures the cookie is sent only over HTTPS connections for added security. |
|
||||
| `httpOnly` | Boolean | Restricts the cookie to HTTP requests, preventing client-side access. |
|
||||
| `sameSite` | Boolean, `'lax'`, `'strict'`, `'none'` | Controls the cookie's cross-site request behavior. |
|
||||
| `priority` | String (`"low"`, `"medium"`, `"high"`) | Specifies the cookie's priority |
|
||||
| `partitioned` | Boolean | Indicates whether the cookie is [partitioned](https://github.com/privacycg/CHIPS). |
|
||||
|
||||
The only option with a default value is `path`.
|
||||
|
||||
To learn more about these options, see the [MDN docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies).
|
||||
|
||||
## Good to know
|
||||
|
||||
- `cookies` is an **asynchronous** function that returns a promise. You must use `async/await` or React's [`use`](https://react.dev/reference/react/use) function to access cookies.
|
||||
- In version 14 and earlier, `cookies` was a synchronous function. To help with backwards compatibility, you can still access it synchronously in Next.js 15, but this behavior will be deprecated in the future.
|
||||
- `cookies` is a [Request-time API](/docs/app/glossary#request-time-apis) whose returned values cannot be known ahead of time. Using it in a layout or page will opt a route into [dynamic rendering](/docs/app/glossary#dynamic-rendering).
|
||||
- The `.delete` method can only be called:
|
||||
- In a [Server Function](/docs/app/getting-started/mutating-data) or [Route Handler](/docs/app/api-reference/file-conventions/route).
|
||||
- If it belongs to the same domain from which `.set` is called. For wildcard domains, the specific subdomain must be an exact match. Additionally, the code must be executed on the same protocol (HTTP or HTTPS) as the cookie you want to delete.
|
||||
- HTTP does not allow setting cookies after streaming starts, so you must use `.set` in a [Server Function](/docs/app/getting-started/mutating-data) or [Route Handler](/docs/app/api-reference/file-conventions/route).
|
||||
|
||||
## Understanding Cookie Behavior in Server Components
|
||||
|
||||
When working with cookies in Server Components, it's important to understand that cookies are fundamentally a client-side storage mechanism:
|
||||
|
||||
- **Reading cookies** works in Server Components because you're accessing the cookie data that the client's browser sends to the server in the HTTP request headers.
|
||||
- **Setting cookies** is not supported during Server Component rendering. To modify cookies, invoke a Server Function from the client or use a Route Handler.
|
||||
|
||||
The server can only send instructions (via `Set-Cookie` headers) to tell the browser to store cookies - the actual storage happens on the client side. This is why cookie operations that modify state (`.set`, `.delete`) must be performed in a Server Function or Route Handler where the response headers can be properly set.
|
||||
|
||||
## Understanding Cookie Behavior in Server Functions
|
||||
|
||||
After you set or delete a cookie in a Server Function, Next.js can return both the updated UI and new data in a single server roundtrip when the function is used as a [Server Action](/docs/app/getting-started/mutating-data#what-are-server-functions) (e.g., passed to a form's `action` prop). See [Caching and Revalidating](/docs/app/getting-started/caching).
|
||||
|
||||
The UI is not unmounted, but effects that depend on data coming from the server will re-run.
|
||||
|
||||
To refresh cached data too, call [`revalidatePath`](/docs/app/api-reference/functions/revalidatePath) or [`revalidateTag`](/docs/app/api-reference/functions/revalidateTag) inside the function.
|
||||
|
||||
## Examples
|
||||
|
||||
### Getting a cookie
|
||||
|
||||
You can use the `(await cookies()).get('name')` method to get a single cookie:
|
||||
|
||||
```tsx filename="app/page.tsx" switcher
|
||||
import { cookies } from 'next/headers'
|
||||
|
||||
export default async function Page() {
|
||||
const cookieStore = await cookies()
|
||||
const theme = cookieStore.get('theme')
|
||||
return '...'
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/page.js" switcher
|
||||
import { cookies } from 'next/headers'
|
||||
|
||||
export default async function Page() {
|
||||
const cookieStore = await cookies()
|
||||
const theme = cookieStore.get('theme')
|
||||
return '...'
|
||||
}
|
||||
```
|
||||
|
||||
### Getting all cookies
|
||||
|
||||
You can use the `(await cookies()).getAll()` method to get all cookies with a matching name. If `name` is unspecified, it returns all the available cookies.
|
||||
|
||||
```tsx filename="app/page.tsx" switcher
|
||||
import { cookies } from 'next/headers'
|
||||
|
||||
export default async function Page() {
|
||||
const cookieStore = await cookies()
|
||||
return cookieStore.getAll().map((cookie) => (
|
||||
<div key={cookie.name}>
|
||||
<p>Name: {cookie.name}</p>
|
||||
<p>Value: {cookie.value}</p>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/page.js" switcher
|
||||
import { cookies } from 'next/headers'
|
||||
|
||||
export default async function Page() {
|
||||
const cookieStore = await cookies()
|
||||
return cookieStore.getAll().map((cookie) => (
|
||||
<div key={cookie.name}>
|
||||
<p>Name: {cookie.name}</p>
|
||||
<p>Value: {cookie.value}</p>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
```
|
||||
|
||||
### Setting a cookie
|
||||
|
||||
You can use the `(await cookies()).set(name, value, options)` method in a [Server Function](/docs/app/getting-started/mutating-data) or [Route Handler](/docs/app/api-reference/file-conventions/route) to set a cookie. The [`options` object](#options) is optional.
|
||||
|
||||
```tsx filename="app/actions.ts" switcher
|
||||
'use server'
|
||||
|
||||
import { cookies } from 'next/headers'
|
||||
|
||||
export async function create(data) {
|
||||
const cookieStore = await cookies()
|
||||
|
||||
cookieStore.set('name', 'lee')
|
||||
// or
|
||||
cookieStore.set('name', 'lee', { secure: true })
|
||||
// or
|
||||
cookieStore.set({
|
||||
name: 'name',
|
||||
value: 'lee',
|
||||
httpOnly: true,
|
||||
path: '/',
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="app/actions.js" switcher
|
||||
'use server'
|
||||
|
||||
import { cookies } from 'next/headers'
|
||||
|
||||
export async function create(data) {
|
||||
const cookieStore = await cookies()
|
||||
|
||||
cookieStore.set('name', 'lee')
|
||||
// or
|
||||
cookieStore.set('name', 'lee', { secure: true })
|
||||
// or
|
||||
cookieStore.set({
|
||||
name: 'name',
|
||||
value: 'lee',
|
||||
httpOnly: true,
|
||||
path: '/',
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
### Checking if a cookie exists
|
||||
|
||||
You can use the `(await cookies()).has(name)` method to check if a cookie exists:
|
||||
|
||||
```tsx filename="app/page.ts" switcher
|
||||
import { cookies } from 'next/headers'
|
||||
|
||||
export default async function Page() {
|
||||
const cookieStore = await cookies()
|
||||
const hasCookie = cookieStore.has('theme')
|
||||
return '...'
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/page.js" switcher
|
||||
import { cookies } from 'next/headers'
|
||||
|
||||
export default async function Page() {
|
||||
const cookieStore = await cookies()
|
||||
const hasCookie = cookieStore.has('theme')
|
||||
return '...'
|
||||
}
|
||||
```
|
||||
|
||||
### Deleting cookies
|
||||
|
||||
There are three ways you can delete a cookie.
|
||||
|
||||
Using the `delete()` method:
|
||||
|
||||
```tsx filename="app/actions.ts" switcher
|
||||
'use server'
|
||||
|
||||
import { cookies } from 'next/headers'
|
||||
|
||||
export async function deleteCookie(data) {
|
||||
const cookieStore = await cookies()
|
||||
cookieStore.delete('name')
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="app/actions.js" switcher
|
||||
'use server'
|
||||
|
||||
import { cookies } from 'next/headers'
|
||||
|
||||
export async function deleteCookie(data) {
|
||||
const cookieStore = await cookies()
|
||||
cookieStore.delete('name')
|
||||
}
|
||||
```
|
||||
|
||||
Setting a new cookie with the same name and an empty value:
|
||||
|
||||
```tsx filename="app/actions.ts" switcher
|
||||
'use server'
|
||||
|
||||
import { cookies } from 'next/headers'
|
||||
|
||||
export async function deleteCookie(data) {
|
||||
const cookieStore = await cookies()
|
||||
cookieStore.set('name', '')
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="app/actions.js" switcher
|
||||
'use server'
|
||||
|
||||
import { cookies } from 'next/headers'
|
||||
|
||||
export async function deleteCookie(data) {
|
||||
const cookieStore = await cookies()
|
||||
cookieStore.set('name', '')
|
||||
}
|
||||
```
|
||||
|
||||
Setting the `maxAge` to 0 will immediately expire a cookie. `maxAge` accepts a value in seconds.
|
||||
|
||||
```tsx filename="app/actions.ts" switcher
|
||||
'use server'
|
||||
|
||||
import { cookies } from 'next/headers'
|
||||
|
||||
export async function deleteCookie(data) {
|
||||
const cookieStore = await cookies()
|
||||
cookieStore.set('name', 'value', { maxAge: 0 })
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="app/actions.js" switcher
|
||||
'use server'
|
||||
|
||||
import { cookies } from 'next/headers'
|
||||
|
||||
export async function deleteCookie(data) {
|
||||
const cookieStore = await cookies()
|
||||
cookieStore.set('name', 'value', { maxAge: 0 })
|
||||
}
|
||||
```
|
||||
|
||||
## Version History
|
||||
|
||||
| Version | Changes |
|
||||
| ------------ | ------------------------------------------------------------------------------------------------------ |
|
||||
| `v15.0.0-RC` | `cookies` is now an async function. A [codemod](/docs/app/guides/upgrading/codemods#150) is available. |
|
||||
| `v13.0.0` | `cookies` introduced. |
|
||||
Generated
Vendored
+137
@@ -0,0 +1,137 @@
|
||||
---
|
||||
title: draftMode
|
||||
description: API Reference for the draftMode function.
|
||||
related:
|
||||
title: Next Steps
|
||||
description: Learn how to use Draft Mode with this step-by-step guide.
|
||||
links:
|
||||
- app/guides/draft-mode
|
||||
---
|
||||
|
||||
`draftMode` is an **async** function allows you to enable and disable [Draft Mode](/docs/app/guides/draft-mode), as well as check if Draft Mode is enabled in a [Server Component](/docs/app/getting-started/server-and-client-components).
|
||||
|
||||
```tsx filename="app/page.ts" switcher
|
||||
import { draftMode } from 'next/headers'
|
||||
|
||||
export default async function Page() {
|
||||
const { isEnabled } = await draftMode()
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/page.js" switcher
|
||||
import { draftMode } from 'next/headers'
|
||||
|
||||
export default async function Page() {
|
||||
const { isEnabled } = await draftMode()
|
||||
}
|
||||
```
|
||||
|
||||
## Reference
|
||||
|
||||
The following methods and properties are available:
|
||||
|
||||
| Method | Description |
|
||||
| ----------- | --------------------------------------------------------------------------------- |
|
||||
| `isEnabled` | A boolean value that indicates if Draft Mode is enabled. |
|
||||
| `enable()` | Enables Draft Mode in a Route Handler by setting a cookie (`__prerender_bypass`). |
|
||||
| `disable()` | Disables Draft Mode in a Route Handler by deleting a cookie. |
|
||||
|
||||
## Good to know
|
||||
|
||||
- `draftMode` is an **asynchronous** function that returns a promise. You must use `async/await` or React's [`use`](https://react.dev/reference/react/use) function.
|
||||
- In version 14 and earlier, `draftMode` was a synchronous function. To help with backwards compatibility, you can still access it synchronously in Next.js 15, but this behavior will be deprecated in the future.
|
||||
- A new bypass cookie value will be generated each time you run `next build`. This ensures that the bypass cookie can’t be guessed.
|
||||
- To test Draft Mode locally over HTTP, your browser will need to allow third-party cookies and local storage access.
|
||||
|
||||
## Examples
|
||||
|
||||
### Enabling Draft Mode
|
||||
|
||||
To enable Draft Mode, create a new [Route Handler](/docs/app/api-reference/file-conventions/route) and call the `enable()` method:
|
||||
|
||||
```tsx filename="app/draft/route.ts" switcher
|
||||
import { draftMode } from 'next/headers'
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const draft = await draftMode()
|
||||
draft.enable()
|
||||
return new Response('Draft mode is enabled')
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="app/draft/route.js" switcher
|
||||
import { draftMode } from 'next/headers'
|
||||
|
||||
export async function GET(request) {
|
||||
const draft = await draftMode()
|
||||
draft.enable()
|
||||
return new Response('Draft mode is enabled')
|
||||
}
|
||||
```
|
||||
|
||||
### Disabling Draft Mode
|
||||
|
||||
By default, the Draft Mode session ends when the browser is closed.
|
||||
|
||||
To disable Draft Mode manually, call the `disable()` method in your [Route Handler](/docs/app/api-reference/file-conventions/route):
|
||||
|
||||
```tsx filename="app/draft/route.ts" switcher
|
||||
import { draftMode } from 'next/headers'
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const draft = await draftMode()
|
||||
draft.disable()
|
||||
return new Response('Draft mode is disabled')
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="app/draft/route.js" switcher
|
||||
import { draftMode } from 'next/headers'
|
||||
|
||||
export async function GET(request) {
|
||||
const draft = await draftMode()
|
||||
draft.disable()
|
||||
return new Response('Draft mode is disabled')
|
||||
}
|
||||
```
|
||||
|
||||
Then, send a request to invoke the Route Handler. If calling the route using the [`<Link>` component](/docs/app/api-reference/components/link), you must pass `prefetch={false}` to prevent accidentally deleting the cookie on prefetch.
|
||||
|
||||
### Checking if Draft Mode is enabled
|
||||
|
||||
You can check if Draft Mode is enabled in a Server Component with the `isEnabled` property:
|
||||
|
||||
```tsx filename="app/page.ts" switcher
|
||||
import { draftMode } from 'next/headers'
|
||||
|
||||
export default async function Page() {
|
||||
const { isEnabled } = await draftMode()
|
||||
return (
|
||||
<main>
|
||||
<h1>My Blog Post</h1>
|
||||
<p>Draft Mode is currently {isEnabled ? 'Enabled' : 'Disabled'}</p>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/page.js" switcher
|
||||
import { draftMode } from 'next/headers'
|
||||
|
||||
export default async function Page() {
|
||||
const { isEnabled } = await draftMode()
|
||||
return (
|
||||
<main>
|
||||
<h1>My Blog Post</h1>
|
||||
<p>Draft Mode is currently {isEnabled ? 'Enabled' : 'Disabled'}</p>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Version History
|
||||
|
||||
| Version | Changes |
|
||||
| ------------ | -------------------------------------------------------------------------------------------------------- |
|
||||
| `v15.0.0-RC` | `draftMode` is now an async function. A [codemod](/docs/app/guides/upgrading/codemods#150) is available. |
|
||||
| `v13.4.0` | `draftMode` introduced. |
|
||||
Generated
Vendored
+117
@@ -0,0 +1,117 @@
|
||||
---
|
||||
title: fetch
|
||||
description: API reference for the extended fetch function.
|
||||
---
|
||||
|
||||
Next.js extends the [Web `fetch()` API](https://developer.mozilla.org/docs/Web/API/Fetch_API) to allow each request on the server to set its own persistent caching and revalidation semantics.
|
||||
|
||||
In the browser, the `cache` option indicates how a fetch request will interact with the _browser's_ HTTP cache. With this extension, `cache` indicates how a _server-side_ fetch request will interact with the framework's persistent cache.
|
||||
|
||||
You can call `fetch` with `async` and `await` directly within Server Components.
|
||||
|
||||
```tsx filename="app/page.tsx" switcher
|
||||
export default async function Page() {
|
||||
let data = await fetch('https://api.vercel.app/blog')
|
||||
let posts = await data.json()
|
||||
return (
|
||||
<ul>
|
||||
{posts.map((post) => (
|
||||
<li key={post.id}>{post.title}</li>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/page.js" switcher
|
||||
export default async function Page() {
|
||||
let data = await fetch('https://api.vercel.app/blog')
|
||||
let posts = await data.json()
|
||||
return (
|
||||
<ul>
|
||||
{posts.map((post) => (
|
||||
<li key={post.id}>{post.title}</li>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## `fetch(url, options)`
|
||||
|
||||
Since Next.js extends the [Web `fetch()` API](https://developer.mozilla.org/docs/Web/API/Fetch_API), you can use any of the [native options available](https://developer.mozilla.org/docs/Web/API/fetch#parameters).
|
||||
|
||||
### `options.cache`
|
||||
|
||||
Configure how the request should interact with Next.js [caching](/docs/app/getting-started/caching).
|
||||
|
||||
```ts
|
||||
fetch(`https://...`, { cache: 'force-cache' | 'no-store' })
|
||||
```
|
||||
|
||||
- **`auto no cache`** (default): Next.js fetches the resource from the remote server on every request in development, but will fetch once during `next build` because the route will be statically prerendered. If [Request-time APIs](/docs/app/glossary#request-time-apis) are detected on the route, Next.js will fetch the resource on every request.
|
||||
- **`no-store`**: Next.js fetches the resource from the remote server on every request, even if Request-time APIs are not detected on the route.
|
||||
- **`force-cache`**: Next.js looks for a matching request in its server-side cache.
|
||||
- If there is a match and it is fresh, it will be returned from the cache.
|
||||
- If there is no match or a stale match, Next.js will fetch the resource from the remote server and update the cache with the downloaded resource.
|
||||
|
||||
### `options.next.revalidate`
|
||||
|
||||
```ts
|
||||
fetch(`https://...`, { next: { revalidate: false | 0 | number } })
|
||||
```
|
||||
|
||||
Set the cache lifetime of a resource (in seconds).
|
||||
|
||||
- **`false`** - Cache the resource indefinitely. Semantically equivalent to `revalidate: Infinity`. The HTTP cache may evict older resources over time.
|
||||
- **`0`** - Prevent the resource from being cached.
|
||||
- **`number`** - (in seconds) Specify the resource should have a cache lifetime of at most `n` seconds.
|
||||
|
||||
> **Good to know**:
|
||||
>
|
||||
> - If an individual `fetch()` request sets a `revalidate` number lower than the [default `revalidate`](/docs/app/guides/caching-without-cache-components#route-segment-config-revalidate) of a route, the whole route revalidation interval will be decreased.
|
||||
> - If two fetch requests with the same URL in the same route have different `revalidate` values, the lower value will be used.
|
||||
> - Conflicting options such as `{ revalidate: 3600, cache: 'no-store' }` are not allowed, both will be ignored, and in development mode a warning will be printed to the terminal.
|
||||
|
||||
### `options.next.tags`
|
||||
|
||||
```ts
|
||||
fetch(`https://...`, { next: { tags: ['collection'] } })
|
||||
```
|
||||
|
||||
Set the cache tags of a resource. Data can then be revalidated on-demand using [`revalidateTag`](/docs/app/api-reference/functions/revalidateTag). The max length for a custom tag is 256 characters and the max tag items is 128.
|
||||
|
||||
## Memoization
|
||||
|
||||
`fetch` requests using `GET` with the same URL and options are automatically [memoized](/docs/app/glossary#memoization) during a server render pass. If you call the same `fetch` in multiple Server Components, layouts, pages, `generateStaticParams` and `generateViewport`, Next.js executes it only once and shares the result.
|
||||
|
||||
To opt out, pass an [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) signal to `fetch`:
|
||||
|
||||
```js
|
||||
const { signal } = new AbortController()
|
||||
fetch(url, { signal })
|
||||
```
|
||||
|
||||
> **Good to know**: Memoization does not apply in [Route Handlers](/docs/app/api-reference/file-conventions/route), since they are not part of the React component tree.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Fetch default `auto no store` and `cache: 'no-store'` not showing fresh data in development
|
||||
|
||||
Next.js caches `fetch` responses in Server Components across Hot Module Replacement (HMR) in local development for faster responses and to reduce costs for billed API calls.
|
||||
|
||||
By default, the [HMR cache](/docs/app/api-reference/config/next-config-js/serverComponentsHmrCache) applies to all fetch requests, including those with the default `auto no cache` and `cache: 'no-store'` option. This means uncached requests will not show fresh data between HMR refreshes. However, the cache will be cleared on navigation or full-page reloads.
|
||||
|
||||
See the [`serverComponentsHmrCache`](/docs/app/api-reference/config/next-config-js/serverComponentsHmrCache) docs for more information.
|
||||
|
||||
### Hard refresh and caching in development
|
||||
|
||||
In development mode, if the request includes the `cache-control: no-cache` header, `options.cache`, `options.next.revalidate`, and `options.next.tags` are ignored, and the `fetch` request is served from the source.
|
||||
|
||||
Browsers typically include `cache-control: no-cache` when the cache is disabled in developer tools or during a hard refresh.
|
||||
|
||||
## Version History
|
||||
|
||||
| Version | Changes |
|
||||
| --------- | ------------------- |
|
||||
| `v13.0.0` | `fetch` introduced. |
|
||||
Generated
Vendored
+172
@@ -0,0 +1,172 @@
|
||||
---
|
||||
title: forbidden
|
||||
description: API Reference for the forbidden function.
|
||||
version: experimental
|
||||
related:
|
||||
links:
|
||||
- app/api-reference/file-conventions/forbidden
|
||||
---
|
||||
|
||||
The `forbidden` function throws an error that renders a Next.js 403 error page. It's useful for handling authorization errors in your application. You can customize the UI using the [`forbidden.js` file](/docs/app/api-reference/file-conventions/forbidden).
|
||||
|
||||
To start using `forbidden`, enable the experimental [`authInterrupts`](/docs/app/api-reference/config/next-config-js/authInterrupts) configuration option in your `next.config.js` file:
|
||||
|
||||
```ts filename="next.config.ts" switcher
|
||||
import type { NextConfig } from 'next'
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
experimental: {
|
||||
authInterrupts: true,
|
||||
},
|
||||
}
|
||||
|
||||
export default nextConfig
|
||||
```
|
||||
|
||||
```js filename="next.config.js" switcher
|
||||
module.exports = {
|
||||
experimental: {
|
||||
authInterrupts: true,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
`forbidden` can be invoked in [Server Components](/docs/app/getting-started/server-and-client-components), [Server Functions](/docs/app/getting-started/mutating-data), and [Route Handlers](/docs/app/api-reference/file-conventions/route).
|
||||
|
||||
```tsx filename="app/auth/page.tsx" switcher
|
||||
import { verifySession } from '@/app/lib/dal'
|
||||
import { forbidden } from 'next/navigation'
|
||||
|
||||
export default async function AdminPage() {
|
||||
const session = await verifySession()
|
||||
|
||||
// Check if the user has the 'admin' role
|
||||
if (session.role !== 'admin') {
|
||||
forbidden()
|
||||
}
|
||||
|
||||
// Render the admin page for authorized users
|
||||
return <></>
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/auth/page.js" switcher
|
||||
import { verifySession } from '@/app/lib/dal'
|
||||
import { forbidden } from 'next/navigation'
|
||||
|
||||
export default async function AdminPage() {
|
||||
const session = await verifySession()
|
||||
|
||||
// Check if the user has the 'admin' role
|
||||
if (session.role !== 'admin') {
|
||||
forbidden()
|
||||
}
|
||||
|
||||
// Render the admin page for authorized users
|
||||
return <></>
|
||||
}
|
||||
```
|
||||
|
||||
## Good to know
|
||||
|
||||
- The `forbidden` function cannot be called in the [root layout](/docs/app/api-reference/file-conventions/layout#root-layout).
|
||||
|
||||
## Examples
|
||||
|
||||
### Role-based route protection
|
||||
|
||||
You can use `forbidden` to restrict access to certain routes based on user roles. This ensures that users who are authenticated but lack the required permissions cannot access the route.
|
||||
|
||||
```tsx filename="app/admin/page.tsx" switcher
|
||||
import { verifySession } from '@/app/lib/dal'
|
||||
import { forbidden } from 'next/navigation'
|
||||
|
||||
export default async function AdminPage() {
|
||||
const session = await verifySession()
|
||||
|
||||
// Check if the user has the 'admin' role
|
||||
if (session.role !== 'admin') {
|
||||
forbidden()
|
||||
}
|
||||
|
||||
// Render the admin page for authorized users
|
||||
return (
|
||||
<main>
|
||||
<h1>Admin Dashboard</h1>
|
||||
<p>Welcome, {session.user.name}!</p>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/admin/page.js" switcher
|
||||
import { verifySession } from '@/app/lib/dal'
|
||||
import { forbidden } from 'next/navigation'
|
||||
|
||||
export default async function AdminPage() {
|
||||
const session = await verifySession()
|
||||
|
||||
// Check if the user has the 'admin' role
|
||||
if (session.role !== 'admin') {
|
||||
forbidden()
|
||||
}
|
||||
|
||||
// Render the admin page for authorized users
|
||||
return (
|
||||
<main>
|
||||
<h1>Admin Dashboard</h1>
|
||||
<p>Welcome, {session.user.name}!</p>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Mutations with Server Actions
|
||||
|
||||
When implementing mutations in Server Actions, you can use `forbidden` to only allow users with a specific role to update sensitive data.
|
||||
|
||||
```ts filename="app/actions/update-role.ts" switcher
|
||||
'use server'
|
||||
|
||||
import { verifySession } from '@/app/lib/dal'
|
||||
import { forbidden } from 'next/navigation'
|
||||
import db from '@/app/lib/db'
|
||||
|
||||
export async function updateRole(formData: FormData) {
|
||||
const session = await verifySession()
|
||||
|
||||
// Ensure only admins can update roles
|
||||
if (session.role !== 'admin') {
|
||||
forbidden()
|
||||
}
|
||||
|
||||
// Perform the role update for authorized users
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="app/actions/update-role.js" switcher
|
||||
'use server'
|
||||
|
||||
import { verifySession } from '@/app/lib/dal'
|
||||
import { forbidden } from 'next/navigation'
|
||||
import db from '@/app/lib/db'
|
||||
|
||||
export async function updateRole(formData) {
|
||||
const session = await verifySession()
|
||||
|
||||
// Ensure only admins can update roles
|
||||
if (session.role !== 'admin') {
|
||||
forbidden()
|
||||
}
|
||||
|
||||
// Perform the role update for authorized users
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
## Version History
|
||||
|
||||
| Version | Changes |
|
||||
| --------- | ----------------------- |
|
||||
| `v15.1.0` | `forbidden` introduced. |
|
||||
frontend/node_modules/next/dist/docs/01-app/03-api-reference/04-functions/generate-image-metadata.md
Generated
Vendored
+274
@@ -0,0 +1,274 @@
|
||||
---
|
||||
title: generateImageMetadata
|
||||
description: Learn how to generate multiple images in a single Metadata API special file.
|
||||
related:
|
||||
title: Next Steps
|
||||
description: View all the Metadata API options.
|
||||
links:
|
||||
- app/api-reference/file-conventions/metadata
|
||||
---
|
||||
|
||||
You can use `generateImageMetadata` to generate different versions of one image or return multiple images for one route segment. This is useful for when you want to avoid hard-coding metadata values, such as for icons.
|
||||
|
||||
## Parameters
|
||||
|
||||
`generateImageMetadata` function accepts the following parameters:
|
||||
|
||||
#### `params` (optional)
|
||||
|
||||
An object containing the [dynamic route parameters](/docs/app/api-reference/file-conventions/dynamic-routes) object from the root segment down to the segment `generateImageMetadata` is called from.
|
||||
|
||||
```tsx filename="icon.tsx" switcher
|
||||
export function generateImageMetadata({
|
||||
params,
|
||||
}: {
|
||||
params: { slug: string }
|
||||
}) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="icon.js" switcher
|
||||
export function generateImageMetadata({ params }) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
| Route | URL | `params` |
|
||||
| ------------------------------- | ----------- | ------------------------- |
|
||||
| `app/shop/icon.js` | `/shop` | `undefined` |
|
||||
| `app/shop/[slug]/icon.js` | `/shop/1` | `{ slug: '1' }` |
|
||||
| `app/shop/[tag]/[item]/icon.js` | `/shop/1/2` | `{ tag: '1', item: '2' }` |
|
||||
|
||||
## Returns
|
||||
|
||||
The `generateImageMetadata` function should return an `array` of objects containing the image's metadata such as `alt` and `size`. In addition, each item **must** include an `id` value which will be passed as a promise to the props of the image generating function.
|
||||
|
||||
| Image Metadata Object | Type |
|
||||
| --------------------- | ----------------------------------- |
|
||||
| `id` | `string` (required) |
|
||||
| `alt` | `string` |
|
||||
| `size` | `{ width: number; height: number }` |
|
||||
| `contentType` | `string` |
|
||||
|
||||
```tsx filename="icon.tsx" switcher
|
||||
import { ImageResponse } from 'next/og'
|
||||
|
||||
export function generateImageMetadata() {
|
||||
return [
|
||||
{
|
||||
contentType: 'image/png',
|
||||
size: { width: 48, height: 48 },
|
||||
id: 'small',
|
||||
},
|
||||
{
|
||||
contentType: 'image/png',
|
||||
size: { width: 72, height: 72 },
|
||||
id: 'medium',
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
export default async function Icon({ id }: { id: Promise<string | number> }) {
|
||||
const iconId = await id
|
||||
return new ImageResponse(
|
||||
(
|
||||
<div
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
fontSize: 88,
|
||||
background: '#000',
|
||||
color: '#fafafa',
|
||||
}}
|
||||
>
|
||||
Icon {iconId}
|
||||
</div>
|
||||
)
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="icon.js" switcher
|
||||
import { ImageResponse } from 'next/og'
|
||||
|
||||
export function generateImageMetadata() {
|
||||
return [
|
||||
{
|
||||
contentType: 'image/png',
|
||||
size: { width: 48, height: 48 },
|
||||
id: 'small',
|
||||
},
|
||||
{
|
||||
contentType: 'image/png',
|
||||
size: { width: 72, height: 72 },
|
||||
id: 'medium',
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
export default async function Icon({ id }) {
|
||||
const iconId = await id
|
||||
return new ImageResponse(
|
||||
(
|
||||
<div
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
fontSize: 88,
|
||||
background: '#000',
|
||||
color: '#fafafa',
|
||||
}}
|
||||
>
|
||||
Icon {iconId}
|
||||
</div>
|
||||
)
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Image generation function props
|
||||
|
||||
When using `generateImageMetadata`, the default export image generation function receives the following props:
|
||||
|
||||
#### `id`
|
||||
|
||||
A promise that resolves to the `id` value from one of the items returned by `generateImageMetadata`. The `id` will be a `string` or `number` depending on what was returned from `generateImageMetadata`.
|
||||
|
||||
```tsx filename="icon.tsx" switcher
|
||||
export default async function Icon({ id }: { id: Promise<string | number> }) {
|
||||
const iconId = await id
|
||||
// Use iconId to generate the image
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="icon.js" switcher
|
||||
export default async function Icon({ id }) {
|
||||
const iconId = await id
|
||||
// Use iconId to generate the image
|
||||
}
|
||||
```
|
||||
|
||||
#### `params` (optional)
|
||||
|
||||
A promise that resolves to an object containing the [dynamic route parameters](/docs/app/api-reference/file-conventions/dynamic-routes) from the root segment down to the segment the image is colocated in.
|
||||
|
||||
```tsx filename="icon.tsx" switcher
|
||||
export default async function Icon({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ slug: string }>
|
||||
}) {
|
||||
const { slug } = await params
|
||||
// Use slug to generate the image
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="icon.js" switcher
|
||||
export default async function Icon({ params }) {
|
||||
const { slug } = await params
|
||||
// Use slug to generate the image
|
||||
}
|
||||
```
|
||||
|
||||
### Examples
|
||||
|
||||
#### Using external data
|
||||
|
||||
This example uses the `params` object and external data to generate multiple [Open Graph images](/docs/app/api-reference/file-conventions/metadata/opengraph-image) for a route segment.
|
||||
|
||||
```tsx filename="app/products/[id]/opengraph-image.tsx" switcher
|
||||
import { ImageResponse } from 'next/og'
|
||||
import { getCaptionForImage, getOGImages } from '@/app/utils/images'
|
||||
|
||||
export async function generateImageMetadata({
|
||||
params,
|
||||
}: {
|
||||
params: { id: string }
|
||||
}) {
|
||||
const images = await getOGImages(params.id)
|
||||
|
||||
return images.map((image, idx) => ({
|
||||
id: idx,
|
||||
size: { width: 1200, height: 600 },
|
||||
alt: image.text,
|
||||
contentType: 'image/png',
|
||||
}))
|
||||
}
|
||||
|
||||
export default async function Image({
|
||||
params,
|
||||
id,
|
||||
}: {
|
||||
params: Promise<{ id: string }>
|
||||
id: Promise<number>
|
||||
}) {
|
||||
const productId = (await params).id
|
||||
const imageId = await id
|
||||
const text = await getCaptionForImage(productId, imageId)
|
||||
|
||||
return new ImageResponse(
|
||||
(
|
||||
<div
|
||||
style={
|
||||
{
|
||||
// ...
|
||||
}
|
||||
}
|
||||
>
|
||||
{text}
|
||||
</div>
|
||||
)
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/products/[id]/opengraph-image.js" switcher
|
||||
import { ImageResponse } from 'next/og'
|
||||
import { getCaptionForImage, getOGImages } from '@/app/utils/images'
|
||||
|
||||
export async function generateImageMetadata({ params }) {
|
||||
const images = await getOGImages(params.id)
|
||||
|
||||
return images.map((image, idx) => ({
|
||||
id: idx,
|
||||
size: { width: 1200, height: 600 },
|
||||
alt: image.text,
|
||||
contentType: 'image/png',
|
||||
}))
|
||||
}
|
||||
|
||||
export default async function Image({ params, id }) {
|
||||
const productId = (await params).id
|
||||
const imageId = await id
|
||||
const text = await getCaptionForImage(productId, imageId)
|
||||
|
||||
return new ImageResponse(
|
||||
(
|
||||
<div
|
||||
style={
|
||||
{
|
||||
// ...
|
||||
}
|
||||
}
|
||||
>
|
||||
{text}
|
||||
</div>
|
||||
)
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Version History
|
||||
|
||||
| Version | Changes |
|
||||
| --------- | --------------------------------------------------------------------------------------------------- |
|
||||
| `v16.0.0` | `id` passed to the Image generation function is now a promise that resolves to `string` or `number` |
|
||||
| `v16.0.0` | `params` passed to the Image generation function is now a promise that resolves to an object |
|
||||
| `v13.3.0` | `generateImageMetadata` introduced. |
|
||||
Generated
Vendored
+1424
File diff suppressed because it is too large
Load Diff
Generated
Vendored
+81
@@ -0,0 +1,81 @@
|
||||
---
|
||||
title: generateSitemaps
|
||||
nav_title: generateSitemaps
|
||||
description: Learn how to use the generateSiteMaps function to create multiple sitemaps for your application.
|
||||
related:
|
||||
title: Next Steps
|
||||
description: Learn how to create sitemaps for your Next.js application.
|
||||
links:
|
||||
- app/api-reference/file-conventions/metadata/sitemap
|
||||
---
|
||||
|
||||
You can use the `generateSitemaps` function to generate multiple sitemaps for your application.
|
||||
|
||||
## Returns
|
||||
|
||||
The `generateSitemaps` returns an array of objects with an `id` property.
|
||||
|
||||
## URLs
|
||||
|
||||
Your generated sitemaps will be available at `/.../sitemap/[id].xml`. For example, `/product/sitemap/1.xml`.
|
||||
|
||||
## Example
|
||||
|
||||
For example, to split a sitemap using `generateSitemaps`, return an array of objects with the sitemap `id`. Then, use the `id` to generate the unique sitemaps.
|
||||
|
||||
```ts filename="app/product/sitemap.ts" switcher
|
||||
import type { MetadataRoute } from 'next'
|
||||
import { BASE_URL } from '@/app/lib/constants'
|
||||
|
||||
export async function generateSitemaps() {
|
||||
// Fetch the total number of products and calculate the number of sitemaps needed
|
||||
return [{ id: 0 }, { id: 1 }, { id: 2 }, { id: 3 }]
|
||||
}
|
||||
|
||||
export default async function sitemap(props: {
|
||||
id: Promise<string>
|
||||
}): Promise<MetadataRoute.Sitemap> {
|
||||
const id = await props.id
|
||||
// Google's limit is 50,000 URLs per sitemap
|
||||
const start = id * 50000
|
||||
const end = start + 50000
|
||||
const products = await getProducts(
|
||||
`SELECT id, date FROM products WHERE id BETWEEN ${start} AND ${end}`
|
||||
)
|
||||
return products.map((product) => ({
|
||||
url: `${BASE_URL}/product/${product.id}`,
|
||||
lastModified: product.date,
|
||||
}))
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="app/product/sitemap.js" switcher
|
||||
import { BASE_URL } from '@/app/lib/constants'
|
||||
|
||||
export async function generateSitemaps() {
|
||||
// Fetch the total number of products and calculate the number of sitemaps needed
|
||||
return [{ id: 0 }, { id: 1 }, { id: 2 }, { id: 3 }]
|
||||
}
|
||||
|
||||
export default async function sitemap(props) {
|
||||
const id = await props.id
|
||||
// Google's limit is 50,000 URLs per sitemap
|
||||
const start = id * 50000
|
||||
const end = start + 50000
|
||||
const products = await getProducts(
|
||||
`SELECT id, date FROM products WHERE id BETWEEN ${start} AND ${end}`
|
||||
)
|
||||
return products.map((product) => ({
|
||||
url: `${BASE_URL}/product/${product.id}`,
|
||||
lastModified: product.date,
|
||||
}))
|
||||
}
|
||||
```
|
||||
|
||||
## Version History
|
||||
|
||||
| Version | Changes |
|
||||
| --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `v16.0.0` | The `id` values returned from `generateSitemaps` are now passed as a promise that resolves to a `string` to the sitemap function. |
|
||||
| `v15.0.0` | `generateSitemaps` now generates consistent URLs between development and production |
|
||||
| `v13.3.2` | `generateSitemaps` introduced. In development, you can view the generated sitemap on `/.../sitemap.xml/[id]`. For example, `/product/sitemap.xml/1`. |
|
||||
Generated
Vendored
+539
@@ -0,0 +1,539 @@
|
||||
---
|
||||
title: generateStaticParams
|
||||
description: API reference for the generateStaticParams function.
|
||||
---
|
||||
|
||||
The `generateStaticParams` function can be used in combination with [dynamic route segments](/docs/app/api-reference/file-conventions/dynamic-routes) to [**statically generate**](/docs/app/glossary#prerendering) routes at build time instead of on-demand at request time.
|
||||
|
||||
`generateStaticParams` can be used with:
|
||||
|
||||
- [Pages](/docs/app/api-reference/file-conventions/page) (`page.tsx`/`page.js`)
|
||||
- [Layouts](/docs/app/api-reference/file-conventions/layout) (`layout.tsx`/`layout.js`)
|
||||
- [Route Handlers](/docs/app/api-reference/file-conventions/route) (`route.ts`/`route.js`)
|
||||
|
||||
```tsx filename="app/blog/[slug]/page.tsx" switcher
|
||||
// Return a list of `params` to populate the [slug] dynamic segment
|
||||
export async function generateStaticParams() {
|
||||
const posts = await fetch('https://.../posts').then((res) => res.json())
|
||||
|
||||
return posts.map((post) => ({
|
||||
slug: post.slug,
|
||||
}))
|
||||
}
|
||||
|
||||
// Multiple versions of this page will be statically generated
|
||||
// using the `params` returned by `generateStaticParams`
|
||||
export default async function Page({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ slug: string }>
|
||||
}) {
|
||||
const { slug } = await params
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/blog/[slug]/page.js" switcher
|
||||
// Return a list of `params` to populate the [slug] dynamic segment
|
||||
export async function generateStaticParams() {
|
||||
const posts = await fetch('https://.../posts').then((res) => res.json())
|
||||
|
||||
return posts.map((post) => ({
|
||||
slug: post.slug,
|
||||
}))
|
||||
}
|
||||
|
||||
// Multiple versions of this page will be statically generated
|
||||
// using the `params` returned by `generateStaticParams`
|
||||
export default async function Page({ params }) {
|
||||
const { slug } = await params
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
> **Good to know**:
|
||||
>
|
||||
> - You can use the [`dynamicParams`](/docs/app/api-reference/file-conventions/route-segment-config/dynamicParams) segment config option to control what happens when a dynamic segment is visited that was not generated with `generateStaticParams`.
|
||||
> - You must return [an empty array from `generateStaticParams`](#all-paths-at-build-time) or utilize [`export const dynamic = 'force-static'`](/docs/app/guides/caching-without-cache-components#dynamic) in order to revalidate (ISR) [paths at runtime](#all-paths-at-runtime).
|
||||
> - During `next dev`, `generateStaticParams` will be called when you navigate to a route.
|
||||
> - During `next build`, `generateStaticParams` runs before the corresponding Layouts or Pages are generated.
|
||||
> - During revalidation (ISR), `generateStaticParams` will not be called again.
|
||||
> - `generateStaticParams` replaces the [`getStaticPaths`](/docs/pages/api-reference/functions/get-static-paths) function in the Pages Router.
|
||||
|
||||
## Parameters
|
||||
|
||||
`options.params` (optional)
|
||||
|
||||
If multiple dynamic segments in a route use `generateStaticParams`, the child `generateStaticParams` function is executed once for each set of `params` the parent generates.
|
||||
|
||||
The `params` object contains the populated `params` from the parent `generateStaticParams`, which can be used to [generate the `params` in a child segment](#multiple-dynamic-segments-in-a-route).
|
||||
|
||||
## Returns
|
||||
|
||||
`generateStaticParams` should return an array of objects where each object represents the populated dynamic segments of a single route.
|
||||
|
||||
- Each property in the object is a dynamic segment to be filled in for the route.
|
||||
- The properties name is the segment's name, and the properties value is what that segment should be filled in with.
|
||||
|
||||
| Example Route | `generateStaticParams` Return Type |
|
||||
| -------------------------------- | ----------------------------------------- |
|
||||
| `/product/[id]` | `{ id: string }[]` |
|
||||
| `/products/[category]/[product]` | `{ category: string, product: string }[]` |
|
||||
| `/products/[...slug]` | `{ slug: string[] }[]` |
|
||||
|
||||
## Single Dynamic Segment
|
||||
|
||||
```tsx filename="app/product/[id]/page.tsx" switcher
|
||||
export function generateStaticParams() {
|
||||
return [{ id: '1' }, { id: '2' }, { id: '3' }]
|
||||
}
|
||||
|
||||
// Three versions of this page will be statically generated
|
||||
// using the `params` returned by `generateStaticParams`
|
||||
// - /product/1
|
||||
// - /product/2
|
||||
// - /product/3
|
||||
export default async function Page({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ id: string }>
|
||||
}) {
|
||||
const { id } = await params
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/product/[id]/page.js" switcher
|
||||
export function generateStaticParams() {
|
||||
return [{ id: '1' }, { id: '2' }, { id: '3' }]
|
||||
}
|
||||
|
||||
// Three versions of this page will be statically generated
|
||||
// using the `params` returned by `generateStaticParams`
|
||||
// - /product/1
|
||||
// - /product/2
|
||||
// - /product/3
|
||||
export default async function Page({ params }) {
|
||||
const { id } = await params
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
## Multiple Dynamic Segments
|
||||
|
||||
```tsx filename="app/products/[category]/[product]/page.tsx" switcher
|
||||
export function generateStaticParams() {
|
||||
return [
|
||||
{ category: 'a', product: '1' },
|
||||
{ category: 'b', product: '2' },
|
||||
{ category: 'c', product: '3' },
|
||||
]
|
||||
}
|
||||
|
||||
// Three versions of this page will be statically generated
|
||||
// using the `params` returned by `generateStaticParams`
|
||||
// - /products/a/1
|
||||
// - /products/b/2
|
||||
// - /products/c/3
|
||||
export default async function Page({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ category: string; product: string }>
|
||||
}) {
|
||||
const { category, product } = await params
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/products/[category]/[product]/page.js" switcher
|
||||
export function generateStaticParams() {
|
||||
return [
|
||||
{ category: 'a', product: '1' },
|
||||
{ category: 'b', product: '2' },
|
||||
{ category: 'c', product: '3' },
|
||||
]
|
||||
}
|
||||
|
||||
// Three versions of this page will be statically generated
|
||||
// using the `params` returned by `generateStaticParams`
|
||||
// - /products/a/1
|
||||
// - /products/b/2
|
||||
// - /products/c/3
|
||||
export default async function Page({ params }) {
|
||||
const { category, product } = await params
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
## Catch-all Dynamic Segment
|
||||
|
||||
```tsx filename="app/product/[...slug]/page.tsx" switcher
|
||||
export function generateStaticParams() {
|
||||
return [{ slug: ['a', '1'] }, { slug: ['b', '2'] }, { slug: ['c', '3'] }]
|
||||
}
|
||||
|
||||
// Three versions of this page will be statically generated
|
||||
// using the `params` returned by `generateStaticParams`
|
||||
// - /product/a/1
|
||||
// - /product/b/2
|
||||
// - /product/c/3
|
||||
export default async function Page({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ slug: string[] }>
|
||||
}) {
|
||||
const { slug } = await params
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/product/[...slug]/page.js" switcher
|
||||
export function generateStaticParams() {
|
||||
return [{ slug: ['a', '1'] }, { slug: ['b', '2'] }, { slug: ['c', '3'] }]
|
||||
}
|
||||
|
||||
// Three versions of this page will be statically generated
|
||||
// using the `params` returned by `generateStaticParams`
|
||||
// - /product/a/1
|
||||
// - /product/b/2
|
||||
// - /product/c/3
|
||||
export default async function Page({ params }) {
|
||||
const { slug } = await params
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Prerendering
|
||||
|
||||
#### All paths at build time
|
||||
|
||||
To statically render all paths at build time, supply the full list of paths to `generateStaticParams`:
|
||||
|
||||
```tsx filename="app/blog/[slug]/page.tsx" switcher
|
||||
export async function generateStaticParams() {
|
||||
const posts = await fetch('https://.../posts').then((res) => res.json())
|
||||
|
||||
return posts.map((post) => ({
|
||||
slug: post.slug,
|
||||
}))
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/blog/[slug]/page.js" switcher
|
||||
export async function generateStaticParams() {
|
||||
const posts = await fetch('https://.../posts').then((res) => res.json())
|
||||
|
||||
return posts.map((post) => ({
|
||||
slug: post.slug,
|
||||
}))
|
||||
}
|
||||
```
|
||||
|
||||
#### Subset of paths at build time
|
||||
|
||||
To statically render a subset of paths at build time, and the rest the first time they're visited at runtime, return a partial list of paths:
|
||||
|
||||
```tsx filename="app/blog/[slug]/page.tsx" switcher
|
||||
export async function generateStaticParams() {
|
||||
const posts = await fetch('https://.../posts').then((res) => res.json())
|
||||
|
||||
// Render the first 10 posts at build time
|
||||
return posts.slice(0, 10).map((post) => ({
|
||||
slug: post.slug,
|
||||
}))
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/blog/[slug]/page.js" switcher
|
||||
export async function generateStaticParams() {
|
||||
const posts = await fetch('https://.../posts').then((res) => res.json())
|
||||
|
||||
// Render the first 10 posts at build time
|
||||
return posts.slice(0, 10).map((post) => ({
|
||||
slug: post.slug,
|
||||
}))
|
||||
}
|
||||
```
|
||||
|
||||
Then, by using the [`dynamicParams`](/docs/app/api-reference/file-conventions/route-segment-config/dynamicParams) segment config option, you can control what happens when a dynamic segment is visited that was not generated with `generateStaticParams`.
|
||||
|
||||
```tsx filename="app/blog/[slug]/page.tsx" switcher
|
||||
// All posts besides the top 10 will be a 404
|
||||
export const dynamicParams = false
|
||||
|
||||
export async function generateStaticParams() {
|
||||
const posts = await fetch('https://.../posts').then((res) => res.json())
|
||||
const topPosts = posts.slice(0, 10)
|
||||
|
||||
return topPosts.map((post) => ({
|
||||
slug: post.slug,
|
||||
}))
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/blog/[slug]/page.js" switcher
|
||||
// All posts besides the top 10 will be a 404
|
||||
export const dynamicParams = false
|
||||
|
||||
export async function generateStaticParams() {
|
||||
const posts = await fetch('https://.../posts').then((res) => res.json())
|
||||
const topPosts = posts.slice(0, 10)
|
||||
|
||||
return topPosts.map((post) => ({
|
||||
slug: post.slug,
|
||||
}))
|
||||
}
|
||||
```
|
||||
|
||||
#### All paths at runtime
|
||||
|
||||
To statically render all paths the first time they're visited, return an empty array (no paths will be rendered at build time) or utilize [`export const dynamic = 'force-static'`](/docs/app/guides/caching-without-cache-components#dynamic):
|
||||
|
||||
```jsx filename="app/blog/[slug]/page.js"
|
||||
export async function generateStaticParams() {
|
||||
return []
|
||||
}
|
||||
```
|
||||
|
||||
> **Good to know:**
|
||||
>
|
||||
> - You must always return an array from `generateStaticParams`, even if it's empty. Otherwise, the route will be dynamically rendered.
|
||||
|
||||
```jsx filename="app/changelog/[slug]/page.js"
|
||||
export const dynamic = 'force-static'
|
||||
```
|
||||
|
||||
#### With Cache Components
|
||||
|
||||
When using [Cache Components](/docs/app/getting-started/caching) with dynamic routes, `generateStaticParams` must return **at least one param**. Empty arrays cause a [build error](/docs/messages/empty-generate-static-params). This allows Cache Components to validate your route doesn't incorrectly access `cookies()`, `headers()`, or `searchParams` at runtime.
|
||||
|
||||
> **Good to know**: If you don't know the actual param values at build time, you can return a placeholder param (e.g., `[{ slug: '__placeholder__' }]`) for validation, then handle it in your page with `notFound()`. However, this prevents build time validation from working effectively and may cause runtime errors.
|
||||
|
||||
See the [dynamic routes section](/docs/app/api-reference/file-conventions/dynamic-routes#with-cache-components) for detailed walkthroughs.
|
||||
|
||||
### With Route Handlers
|
||||
|
||||
You can use `generateStaticParams` with [Route Handlers](/docs/app/api-reference/file-conventions/route) to statically generate API responses at build time:
|
||||
|
||||
```ts filename="app/api/posts/[id]/route.ts" switcher
|
||||
export async function generateStaticParams() {
|
||||
return [{ id: '1' }, { id: '2' }, { id: '3' }]
|
||||
}
|
||||
|
||||
export async function GET(
|
||||
request: Request,
|
||||
{ params }: RouteContext<'/api/posts/[id]'>
|
||||
) {
|
||||
const { id } = await params
|
||||
// This will be statically generated for IDs 1, 2, and 3
|
||||
return Response.json({ id, title: `Post ${id}` })
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="app/api/posts/[id]/route.js" switcher
|
||||
export async function generateStaticParams() {
|
||||
return [{ id: '1' }, { id: '2' }, { id: '3' }]
|
||||
}
|
||||
|
||||
export async function GET(request, { params }) {
|
||||
const { id } = await params
|
||||
// This will be statically generated for IDs 1, 2, and 3
|
||||
return Response.json({ id, title: `Post ${id}` })
|
||||
}
|
||||
```
|
||||
|
||||
### Route Handlers with Cache Components
|
||||
|
||||
When using [Cache Components](/docs/app/getting-started/caching), combine with `use cache` for optimal caching:
|
||||
|
||||
```ts filename="app/api/posts/[id]/route.ts"
|
||||
export async function generateStaticParams() {
|
||||
return [{ id: '1' }, { id: '2' }, { id: '3' }]
|
||||
}
|
||||
|
||||
async function getPost(id: Promise<string>) {
|
||||
'use cache'
|
||||
const resolvedId = await id
|
||||
const response = await fetch(`https://api.example.com/posts/${resolvedId}`)
|
||||
return response.json()
|
||||
}
|
||||
|
||||
export async function GET(
|
||||
request: Request,
|
||||
{ params }: RouteContext<'/api/posts/[id]'>
|
||||
) {
|
||||
const post = await getPost(params.then((p) => p.id))
|
||||
return Response.json(post)
|
||||
}
|
||||
```
|
||||
|
||||
See the [Route Handlers documentation](/docs/app/api-reference/file-conventions/route#static-generation-with-generatestaticparams) for more details.
|
||||
|
||||
### Disable rendering for unspecified paths
|
||||
|
||||
To prevent unspecified paths from being prerendered at runtime, add the `export const dynamicParams = false` option in a route segment. When this config option is used, only paths provided by `generateStaticParams` will be served, and unspecified routes will 404 or match (in the case of [catch-all routes](/docs/app/api-reference/file-conventions/dynamic-routes#catch-all-segments)).
|
||||
|
||||
### Multiple Dynamic Segments in a Route
|
||||
|
||||
You can generate params for dynamic segments above the current layout or page, but **not below**. For example, given the `app/products/[category]/[product]` route:
|
||||
|
||||
- `app/products/[category]/[product]/page.js` can generate params for **both** `[category]` and `[product]`.
|
||||
- `app/products/[category]/layout.js` can **only** generate params for `[category]`.
|
||||
|
||||
There are two approaches to generating params for a route with multiple dynamic segments:
|
||||
|
||||
#### Generate params from the bottom up
|
||||
|
||||
Generate multiple dynamic segments from the child route segment.
|
||||
|
||||
```tsx filename="app/products/[category]/[product]/page.tsx" switcher
|
||||
// Generate segments for both [category] and [product]
|
||||
export async function generateStaticParams() {
|
||||
const products = await fetch('https://.../products').then((res) => res.json())
|
||||
|
||||
return products.map((product) => ({
|
||||
category: product.category.slug,
|
||||
product: product.id,
|
||||
}))
|
||||
}
|
||||
|
||||
export default function Page({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ category: string; product: string }>
|
||||
}) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/products/[category]/[product]/page.js" switcher
|
||||
// Generate segments for both [category] and [product]
|
||||
export async function generateStaticParams() {
|
||||
const products = await fetch('https://.../products').then((res) => res.json())
|
||||
|
||||
return products.map((product) => ({
|
||||
category: product.category.slug,
|
||||
product: product.id,
|
||||
}))
|
||||
}
|
||||
|
||||
export default function Page({ params }) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
#### Generate params from the top down
|
||||
|
||||
Generate the parent segments first and use the result to generate the child segments.
|
||||
|
||||
```tsx filename="app/products/[category]/layout.tsx" switcher
|
||||
// Generate segments for [category]
|
||||
export async function generateStaticParams() {
|
||||
const products = await fetch('https://.../products').then((res) => res.json())
|
||||
|
||||
return products.map((product) => ({
|
||||
category: product.category.slug,
|
||||
}))
|
||||
}
|
||||
|
||||
export default function Layout({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ category: string }>
|
||||
}) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/products/[category]/layout.js" switcher
|
||||
// Generate segments for [category]
|
||||
export async function generateStaticParams() {
|
||||
const products = await fetch('https://.../products').then((res) => res.json())
|
||||
|
||||
return products.map((product) => ({
|
||||
category: product.category.slug,
|
||||
}))
|
||||
}
|
||||
|
||||
export default function Layout({ params }) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
A child route segment's `generateStaticParams` function is executed once for each segment a parent `generateStaticParams` generates.
|
||||
|
||||
The child `generateStaticParams` function can use the `params` returned from the parent `generateStaticParams` function to dynamically generate its own segments.
|
||||
|
||||
```tsx filename="app/products/[category]/[product]/page.tsx" switcher
|
||||
// Generate segments for [product] using the `params` passed from
|
||||
// the parent segment's `generateStaticParams` function
|
||||
export async function generateStaticParams({
|
||||
params: { category },
|
||||
}: {
|
||||
params: { category: string }
|
||||
}) {
|
||||
const products = await fetch(
|
||||
`https://.../products?category=${category}`
|
||||
).then((res) => res.json())
|
||||
|
||||
return products.map((product) => ({
|
||||
product: product.id,
|
||||
}))
|
||||
}
|
||||
|
||||
export default function Page({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ category: string; product: string }>
|
||||
}) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/products/[category]/[product]/page.js" switcher
|
||||
// Generate segments for [product] using the `params` passed from
|
||||
// the parent segment's `generateStaticParams` function
|
||||
export async function generateStaticParams({ params: { category } }) {
|
||||
const products = await fetch(
|
||||
`https://.../products?category=${category}`
|
||||
).then((res) => res.json())
|
||||
|
||||
return products.map((product) => ({
|
||||
product: product.id,
|
||||
}))
|
||||
}
|
||||
|
||||
export default function Page({ params }) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Notice that the params argument can be accessed synchronously and includes only parent segment params.
|
||||
|
||||
For type completion, you can make use of the TypeScript `Awaited` helper in combination with either [`Page Props helper`](/docs/app/api-reference/file-conventions/page#page-props-helper) or [`Layout Props helper`](/docs/app/api-reference/file-conventions/layout#layout-props-helper):
|
||||
|
||||
```ts filename="app/products/[category]/[product]/page.tsx" switcher
|
||||
export async function generateStaticParams({
|
||||
params: { category },
|
||||
}: {
|
||||
params: Awaited<LayoutProps<'/products/[category]'>['params']>
|
||||
}) {
|
||||
const products = await fetch(
|
||||
`https://.../products?category=${category}`
|
||||
).then((res) => res.json())
|
||||
|
||||
return products.map((product) => ({
|
||||
product: product.id,
|
||||
}))
|
||||
}
|
||||
```
|
||||
|
||||
> **Good to know**: `fetch` requests are automatically [memoized](/docs/app/glossary#memoization) for the same data across all `generate`-prefixed functions, Layouts, Pages, and Server Components. React [`cache` can be used](https://react.dev/reference/react/cache) if `fetch` is unavailable.
|
||||
|
||||
## Version History
|
||||
|
||||
| Version | Changes |
|
||||
| --------- | ---------------------------------- |
|
||||
| `v13.0.0` | `generateStaticParams` introduced. |
|
||||
Generated
Vendored
+285
@@ -0,0 +1,285 @@
|
||||
---
|
||||
title: generateViewport
|
||||
description: API Reference for the generateViewport function.
|
||||
related:
|
||||
title: Next Steps
|
||||
description: View all the Metadata API options.
|
||||
links:
|
||||
- app/api-reference/file-conventions/metadata
|
||||
- app/getting-started/caching
|
||||
- app/api-reference/config/next-config-js/cacheComponents
|
||||
---
|
||||
|
||||
You can customize the initial viewport of the page with the static `viewport` object or the dynamic `generateViewport` function.
|
||||
|
||||
> **Good to know**:
|
||||
>
|
||||
> - The `viewport` object and `generateViewport` function exports are **only supported in Server Components**.
|
||||
> - You cannot export both the `viewport` object and `generateViewport` function from the same route segment.
|
||||
> - If you're coming from migrating `metadata` exports, you can use [metadata-to-viewport-export codemod](/docs/app/guides/upgrading/codemods#metadata-to-viewport-export) to update your changes.
|
||||
|
||||
## The `viewport` object
|
||||
|
||||
To define the viewport options, export a `viewport` object from a `layout.jsx` or `page.jsx` file.
|
||||
|
||||
```tsx filename="layout.tsx | page.tsx" switcher
|
||||
import type { Viewport } from 'next'
|
||||
|
||||
export const viewport: Viewport = {
|
||||
themeColor: 'black',
|
||||
}
|
||||
|
||||
export default function Page() {}
|
||||
```
|
||||
|
||||
```jsx filename="layout.jsx | page.jsx" switcher
|
||||
export const viewport = {
|
||||
themeColor: 'black',
|
||||
}
|
||||
|
||||
export default function Page() {}
|
||||
```
|
||||
|
||||
## `generateViewport` function
|
||||
|
||||
`generateViewport` should return a [`Viewport` object](#viewport-fields) containing one or more viewport fields.
|
||||
|
||||
```tsx filename="layout.tsx | page.tsx" switcher
|
||||
export function generateViewport({ params }) {
|
||||
return {
|
||||
themeColor: '...',
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In TypeScript, the `params` argument can be typed via [`PageProps<'/route'>`](/docs/app/api-reference/file-conventions/page#page-props-helper) or [`LayoutProps<'/route'>`](/docs/app/api-reference/file-conventions/layout#layout-props-helper) depending on where `generateViewport` is defined.
|
||||
|
||||
```jsx filename="layout.js | page.js" switcher
|
||||
export function generateViewport({ params }) {
|
||||
return {
|
||||
themeColor: '...',
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> **Good to know**:
|
||||
>
|
||||
> - If the viewport doesn't depend on request information, it should be defined using the static [`viewport` object](#the-viewport-object) rather than `generateViewport`.
|
||||
|
||||
## Viewport Fields
|
||||
|
||||
### `themeColor`
|
||||
|
||||
Learn more about [`theme-color`](https://developer.mozilla.org/docs/Web/HTML/Element/meta/name/theme-color).
|
||||
|
||||
**Simple theme color**
|
||||
|
||||
```tsx filename="layout.tsx | page.tsx" switcher
|
||||
import type { Viewport } from 'next'
|
||||
|
||||
export const viewport: Viewport = {
|
||||
themeColor: 'black',
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="layout.jsx | page.jsx" switcher
|
||||
export const viewport = {
|
||||
themeColor: 'black',
|
||||
}
|
||||
```
|
||||
|
||||
```html filename="<head> output" hideLineNumbers
|
||||
<meta name="theme-color" content="black" />
|
||||
```
|
||||
|
||||
**With media attribute**
|
||||
|
||||
```tsx filename="layout.tsx | page.tsx" switcher
|
||||
import type { Viewport } from 'next'
|
||||
|
||||
export const viewport: Viewport = {
|
||||
themeColor: [
|
||||
{ media: '(prefers-color-scheme: light)', color: 'cyan' },
|
||||
{ media: '(prefers-color-scheme: dark)', color: 'black' },
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="layout.jsx | page.jsx" switcher
|
||||
export const viewport = {
|
||||
themeColor: [
|
||||
{ media: '(prefers-color-scheme: light)', color: 'cyan' },
|
||||
{ media: '(prefers-color-scheme: dark)', color: 'black' },
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
```html filename="<head> output" hideLineNumbers
|
||||
<meta name="theme-color" media="(prefers-color-scheme: light)" content="cyan" />
|
||||
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="black" />
|
||||
```
|
||||
|
||||
### `width`, `initialScale`, `maximumScale` and `userScalable`
|
||||
|
||||
> **Good to know**: The `viewport` meta tag is automatically set, and manual configuration is usually unnecessary as the default is sufficient. However, the information is provided for completeness.
|
||||
|
||||
```tsx filename="layout.tsx | page.tsx" switcher
|
||||
import type { Viewport } from 'next'
|
||||
|
||||
export const viewport: Viewport = {
|
||||
width: 'device-width',
|
||||
initialScale: 1,
|
||||
maximumScale: 1,
|
||||
userScalable: false,
|
||||
// Also supported but less commonly used
|
||||
// interactiveWidget: 'resizes-visual',
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="layout.jsx | page.jsx" switcher
|
||||
export const viewport = {
|
||||
width: 'device-width',
|
||||
initialScale: 1,
|
||||
maximumScale: 1,
|
||||
userScalable: false,
|
||||
// Also supported but less commonly used
|
||||
// interactiveWidget: 'resizes-visual',
|
||||
}
|
||||
```
|
||||
|
||||
```html filename="<head> output" hideLineNumbers
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"
|
||||
/>
|
||||
```
|
||||
|
||||
### `colorScheme`
|
||||
|
||||
Learn more about [`color-scheme`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta/name#:~:text=color%2Dscheme%3A%20specifies,of%20the%20following%3A).
|
||||
|
||||
```tsx filename="layout.tsx | page.tsx" switcher
|
||||
import type { Viewport } from 'next'
|
||||
|
||||
export const viewport: Viewport = {
|
||||
colorScheme: 'dark',
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="layout.jsx | page.jsx" switcher
|
||||
export const viewport = {
|
||||
colorScheme: 'dark',
|
||||
}
|
||||
```
|
||||
|
||||
```html filename="<head> output" hideLineNumbers
|
||||
<meta name="color-scheme" content="dark" />
|
||||
```
|
||||
|
||||
## With Cache Components
|
||||
|
||||
When [Cache Components](/docs/app/getting-started/caching) is enabled, `generateViewport` follows the same rules as other components. If viewport accesses runtime data (`cookies()`, `headers()`, `params`, `searchParams`) or performs uncached data fetching, it defers to request time.
|
||||
|
||||
Unlike metadata, viewport cannot be streamed because it affects initial page load UI. If `generateViewport` defers to request time, the page would need to block until resolved.
|
||||
|
||||
If viewport depends on external data but not runtime data, use `use cache`:
|
||||
|
||||
```tsx filename="app/layout.tsx" highlight={2}
|
||||
export async function generateViewport() {
|
||||
'use cache'
|
||||
const { width, initialScale } = await db.query('viewport-size')
|
||||
return { width, initialScale }
|
||||
}
|
||||
```
|
||||
|
||||
If viewport genuinely requires runtime data, wrap the document `<body>` in a Suspense boundary to signal that the entire route should be dynamic:
|
||||
|
||||
```tsx filename="app/layout.tsx" highlight={1,13,17}
|
||||
import { Suspense } from 'react'
|
||||
import { cookies } from 'next/headers'
|
||||
|
||||
export async function generateViewport() {
|
||||
const cookieJar = await cookies()
|
||||
return {
|
||||
themeColor: cookieJar.get('theme-color')?.value,
|
||||
}
|
||||
}
|
||||
|
||||
export default function RootLayout({ children }) {
|
||||
return (
|
||||
<Suspense>
|
||||
<html>
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
</Suspense>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
Caching is preferred because it allows static shell generation. Wrapping the document `body` in Suspense means there is no static shell or content to immediately send when a request arrives, making the entire route block until ready on every request.
|
||||
|
||||
> **Good to know**: Use [multiple root layouts](/docs/app/api-reference/file-conventions/layout#root-layout) to isolate fully dynamic viewport to specific routes, while still letting other routes in your application generate a static shell.
|
||||
|
||||
## Types
|
||||
|
||||
You can add type safety to your viewport object by using the `Viewport` type. If you are using the [built-in TypeScript plugin](/docs/app/api-reference/config/typescript) in your IDE, you do not need to manually add the type, but you can still explicitly add it if you want.
|
||||
|
||||
### `viewport` object
|
||||
|
||||
```tsx
|
||||
import type { Viewport } from 'next'
|
||||
|
||||
export const viewport: Viewport = {
|
||||
themeColor: 'black',
|
||||
}
|
||||
```
|
||||
|
||||
### `generateViewport` function
|
||||
|
||||
#### Regular function
|
||||
|
||||
```tsx
|
||||
import type { Viewport } from 'next'
|
||||
|
||||
export function generateViewport(): Viewport {
|
||||
return {
|
||||
themeColor: 'black',
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### With segment props
|
||||
|
||||
```tsx
|
||||
import type { Viewport } from 'next'
|
||||
|
||||
type Props = {
|
||||
params: Promise<{ id: string }>
|
||||
searchParams: Promise<{ [key: string]: string | string[] | undefined }>
|
||||
}
|
||||
|
||||
export function generateViewport({ params, searchParams }: Props): Viewport {
|
||||
return {
|
||||
themeColor: 'black',
|
||||
}
|
||||
}
|
||||
|
||||
export default function Page({ params, searchParams }: Props) {}
|
||||
```
|
||||
|
||||
#### JavaScript Projects
|
||||
|
||||
For JavaScript projects, you can use JSDoc to add type safety.
|
||||
|
||||
```js
|
||||
/** @type {import("next").Viewport} */
|
||||
export const viewport = {
|
||||
themeColor: 'black',
|
||||
}
|
||||
```
|
||||
|
||||
## Version History
|
||||
|
||||
| Version | Changes |
|
||||
| --------- | --------------------------------------------- |
|
||||
| `v14.0.0` | `viewport` and `generateViewport` introduced. |
|
||||
Generated
Vendored
+73
@@ -0,0 +1,73 @@
|
||||
---
|
||||
title: headers
|
||||
description: API reference for the headers function.
|
||||
---
|
||||
|
||||
`headers` is an **async** function that allows you to **read** the HTTP incoming request headers from a [Server Component](/docs/app/getting-started/server-and-client-components).
|
||||
|
||||
```tsx filename="app/page.tsx" switcher
|
||||
import { headers } from 'next/headers'
|
||||
|
||||
export default async function Page() {
|
||||
const headersList = await headers()
|
||||
const userAgent = headersList.get('user-agent')
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/page.js" switcher
|
||||
import { headers } from 'next/headers'
|
||||
|
||||
export default async function Page() {
|
||||
const headersList = await headers()
|
||||
const userAgent = headersList.get('user-agent')
|
||||
}
|
||||
```
|
||||
|
||||
## Reference
|
||||
|
||||
### Parameters
|
||||
|
||||
`headers` does not take any parameters.
|
||||
|
||||
### Returns
|
||||
|
||||
`headers` returns a **read-only** [Web Headers](https://developer.mozilla.org/docs/Web/API/Headers) object.
|
||||
|
||||
- [`Headers.entries()`](https://developer.mozilla.org/docs/Web/API/Headers/entries): Returns an [`iterator`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Iteration_protocols) allowing to go through all key/value pairs contained in this object.
|
||||
- [`Headers.forEach()`](https://developer.mozilla.org/docs/Web/API/Headers/forEach): Executes a provided function once for each key/value pair in this `Headers` object.
|
||||
- [`Headers.get()`](https://developer.mozilla.org/docs/Web/API/Headers/get): Returns a [`String`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String) sequence of all the values of a header within a `Headers` object with a given name.
|
||||
- [`Headers.has()`](https://developer.mozilla.org/docs/Web/API/Headers/has): Returns a boolean stating whether a `Headers` object contains a certain header.
|
||||
- [`Headers.keys()`](https://developer.mozilla.org/docs/Web/API/Headers/keys): Returns an [`iterator`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Iteration_protocols) allowing you to go through all keys of the key/value pairs contained in this object.
|
||||
- [`Headers.values()`](https://developer.mozilla.org/docs/Web/API/Headers/values): Returns an [`iterator`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Iteration_protocols) allowing you to go through all values of the key/value pairs contained in this object.
|
||||
|
||||
## Good to know
|
||||
|
||||
- `headers` is an **asynchronous** function that returns a promise. You must use `async/await` or React's [`use`](https://react.dev/reference/react/use) function.
|
||||
- In version 14 and earlier, `headers` was a synchronous function. To help with backwards compatibility, you can still access it synchronously in Next.js 15, but this behavior will be deprecated in the future.
|
||||
- Since `headers` is read-only, you cannot `set` or `delete` the outgoing request headers.
|
||||
- `headers` is a [Request-time API](/docs/app/glossary#request-time-apis) whose returned values cannot be known ahead of time. Using it in will opt a route into **[dynamic rendering](/docs/app/glossary#dynamic-rendering)**.
|
||||
|
||||
## Examples
|
||||
|
||||
### Using the Authorization header
|
||||
|
||||
```jsx filename="app/page.js"
|
||||
import { headers } from 'next/headers'
|
||||
|
||||
export default async function Page() {
|
||||
const authorization = (await headers()).get('authorization')
|
||||
const res = await fetch('...', {
|
||||
headers: { authorization }, // Forward the authorization header
|
||||
})
|
||||
const user = await res.json()
|
||||
|
||||
return <h1>{user.name}</h1>
|
||||
}
|
||||
```
|
||||
|
||||
## Version History
|
||||
|
||||
| Version | Changes |
|
||||
| ------------ | ------------------------------------------------------------------------------------------------------ |
|
||||
| `v15.0.0-RC` | `headers` is now an async function. A [codemod](/docs/app/guides/upgrading/codemods#150) is available. |
|
||||
| `v13.0.0` | `headers` introduced. |
|
||||
Generated
Vendored
+212
@@ -0,0 +1,212 @@
|
||||
---
|
||||
title: ImageResponse
|
||||
description: API Reference for the ImageResponse constructor.
|
||||
---
|
||||
|
||||
The `ImageResponse` constructor allows you to generate dynamic images using JSX and CSS. This is useful for generating social media images such as Open Graph images, Twitter cards, and more.
|
||||
|
||||
## Reference
|
||||
|
||||
### Parameters
|
||||
|
||||
The following parameters are available for `ImageResponse`:
|
||||
|
||||
```jsx
|
||||
import { ImageResponse } from 'next/og'
|
||||
|
||||
new ImageResponse(
|
||||
element: ReactElement,
|
||||
options: {
|
||||
width?: number = 1200
|
||||
height?: number = 630
|
||||
emoji?: 'twemoji' | 'blobmoji' | 'noto' | 'openmoji' = 'twemoji',
|
||||
fonts?: {
|
||||
name: string,
|
||||
data: ArrayBuffer,
|
||||
weight: number,
|
||||
style: 'normal' | 'italic'
|
||||
}[]
|
||||
debug?: boolean = false
|
||||
|
||||
// Options that will be passed to the HTTP response
|
||||
status?: number = 200
|
||||
statusText?: string
|
||||
headers?: Record<string, string>
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
> Examples are available in the [Vercel OG Playground](https://og-playground.vercel.app/).
|
||||
|
||||
### Supported HTML and CSS features
|
||||
|
||||
`ImageResponse` supports common CSS properties including flexbox and absolute positioning, custom fonts, text wrapping, centering, and nested images.
|
||||
|
||||
Please refer to [Satori’s documentation](https://github.com/vercel/satori#css) for a list of supported HTML and CSS features.
|
||||
|
||||
## Behavior
|
||||
|
||||
- `ImageResponse` uses [@vercel/og](https://vercel.com/docs/concepts/functions/edge-functions/og-image-generation), [Satori](https://github.com/vercel/satori), and Resvg to convert HTML and CSS into PNG.
|
||||
- Only flexbox and a subset of CSS properties are supported. Advanced layouts (e.g. `display: grid`) will not work.
|
||||
- Maximum bundle size of `500KB`. The bundle size includes your JSX, CSS, fonts, images, and any other assets. If you exceed the limit, consider reducing the size of any assets or fetching at runtime.
|
||||
- Only `ttf`, `otf`, and `woff` font formats are supported. To maximize the font parsing speed, `ttf` or `otf` are preferred over `woff`.
|
||||
|
||||
## Examples
|
||||
|
||||
### Route Handlers
|
||||
|
||||
`ImageResponse` can be used in Route Handlers to generate images dynamically at request time.
|
||||
|
||||
```js filename="app/api/route.js"
|
||||
import { ImageResponse } from 'next/og'
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
return new ImageResponse(
|
||||
(
|
||||
<div
|
||||
style={{
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: 'white',
|
||||
padding: '40px',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
fontSize: 60,
|
||||
fontWeight: 'bold',
|
||||
color: 'black',
|
||||
textAlign: 'center',
|
||||
}}
|
||||
>
|
||||
Welcome to My Site
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
fontSize: 30,
|
||||
color: '#666',
|
||||
marginTop: '20px',
|
||||
}}
|
||||
>
|
||||
Generated with Next.js ImageResponse
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
{
|
||||
width: 1200,
|
||||
height: 630,
|
||||
}
|
||||
)
|
||||
} catch (e) {
|
||||
console.log(`${e.message}`)
|
||||
return new Response(`Failed to generate the image`, {
|
||||
status: 500,
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### File-based Metadata
|
||||
|
||||
You can use `ImageResponse` in a [`opengraph-image.tsx`](/docs/app/api-reference/file-conventions/metadata/opengraph-image) file to generate Open Graph images at build time or dynamically at request time.
|
||||
|
||||
```tsx filename="app/opengraph-image.tsx"
|
||||
import { ImageResponse } from 'next/og'
|
||||
|
||||
// Image metadata
|
||||
export const alt = 'My site'
|
||||
export const size = {
|
||||
width: 1200,
|
||||
height: 630,
|
||||
}
|
||||
|
||||
export const contentType = 'image/png'
|
||||
|
||||
// Image generation
|
||||
export default async function Image() {
|
||||
return new ImageResponse(
|
||||
(
|
||||
// ImageResponse JSX element
|
||||
<div
|
||||
style={{
|
||||
fontSize: 128,
|
||||
background: 'white',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
My site
|
||||
</div>
|
||||
),
|
||||
// ImageResponse options
|
||||
{
|
||||
// For convenience, we can re-use the exported opengraph-image
|
||||
// size config to also set the ImageResponse's width and height.
|
||||
...size,
|
||||
}
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Custom fonts
|
||||
|
||||
You can use custom fonts in your `ImageResponse` by providing a `fonts` array in the options.
|
||||
|
||||
```tsx filename="app/opengraph-image.tsx"
|
||||
import { ImageResponse } from 'next/og'
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { join } from 'node:path'
|
||||
|
||||
// Image metadata
|
||||
export const alt = 'My site'
|
||||
export const size = {
|
||||
width: 1200,
|
||||
height: 630,
|
||||
}
|
||||
|
||||
export const contentType = 'image/png'
|
||||
|
||||
// Image generation
|
||||
export default async function Image() {
|
||||
// Font loading, process.cwd() is Next.js project directory
|
||||
const interSemiBold = await readFile(
|
||||
join(process.cwd(), 'assets/Inter-SemiBold.ttf')
|
||||
)
|
||||
|
||||
return new ImageResponse(
|
||||
(
|
||||
// ...
|
||||
),
|
||||
// ImageResponse options
|
||||
{
|
||||
// For convenience, we can re-use the exported opengraph-image
|
||||
// size config to also set the ImageResponse's width and height.
|
||||
...size,
|
||||
fonts: [
|
||||
{
|
||||
name: 'Inter',
|
||||
data: interSemiBold,
|
||||
style: 'normal',
|
||||
weight: 400,
|
||||
},
|
||||
],
|
||||
}
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Version History
|
||||
|
||||
| Version | Changes |
|
||||
| --------- | ----------------------------------------------------- |
|
||||
| `v14.0.0` | `ImageResponse` moved from `next/server` to `next/og` |
|
||||
| `v13.3.0` | `ImageResponse` can be imported from `next/server`. |
|
||||
| `v13.0.0` | `ImageResponse` introduced via `@vercel/og` package. |
|
||||
Generated
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
---
|
||||
title: Functions
|
||||
description: API Reference for Next.js Functions and Hooks.
|
||||
---
|
||||
|
||||
{/* The content of this doc is shared between the app and pages router. You can use the `<PagesOnly>Content</PagesOnly>` component to add content that is specific to the Pages Router. Any shared content should not be wrapped in a component. */}
|
||||
Generated
Vendored
+123
@@ -0,0 +1,123 @@
|
||||
---
|
||||
title: NextRequest
|
||||
description: API Reference for NextRequest.
|
||||
---
|
||||
|
||||
{/* The content of this doc is shared between the app and pages router. You can use the `<PagesOnly>Content</PagesOnly>` component to add content that is specific to the Pages Router. Any shared content should not be wrapped in a component. */}
|
||||
|
||||
NextRequest extends the [Web Request API](https://developer.mozilla.org/docs/Web/API/Request) with additional convenience methods.
|
||||
|
||||
## `cookies`
|
||||
|
||||
Read or mutate the [`Set-Cookie`](https://developer.mozilla.org/docs/Web/HTTP/Headers/Set-Cookie) header of the request.
|
||||
|
||||
### `set(name, value)`
|
||||
|
||||
Given a name, set a cookie with the given value on the request.
|
||||
|
||||
```ts
|
||||
// Given incoming request /home
|
||||
// Set a cookie to hide the banner
|
||||
// request will have a `Set-Cookie:show-banner=false;path=/home` header
|
||||
request.cookies.set('show-banner', 'false')
|
||||
```
|
||||
|
||||
### `get(name)`
|
||||
|
||||
Given a cookie name, return the value of the cookie. If the cookie is not found, `undefined` is returned. If multiple cookies are found, the first one is returned.
|
||||
|
||||
```ts
|
||||
// Given incoming request /home
|
||||
// { name: 'show-banner', value: 'false', Path: '/home' }
|
||||
request.cookies.get('show-banner')
|
||||
```
|
||||
|
||||
### `getAll()`
|
||||
|
||||
Given a cookie name, return the values of the cookie. If no name is given, return all cookies on the request.
|
||||
|
||||
```ts
|
||||
// Given incoming request /home
|
||||
// [
|
||||
// { name: 'experiments', value: 'new-pricing-page', Path: '/home' },
|
||||
// { name: 'experiments', value: 'winter-launch', Path: '/home' },
|
||||
// ]
|
||||
request.cookies.getAll('experiments')
|
||||
// Alternatively, get all cookies for the request
|
||||
request.cookies.getAll()
|
||||
```
|
||||
|
||||
### `delete(name)`
|
||||
|
||||
Given a cookie name, delete the cookie from the request.
|
||||
|
||||
```ts
|
||||
// Returns true for deleted, false is nothing is deleted
|
||||
request.cookies.delete('experiments')
|
||||
```
|
||||
|
||||
### `has(name)`
|
||||
|
||||
Given a cookie name, return `true` if the cookie exists on the request.
|
||||
|
||||
```ts
|
||||
// Returns true if cookie exists, false if it does not
|
||||
request.cookies.has('experiments')
|
||||
```
|
||||
|
||||
### `clear()`
|
||||
|
||||
Remove all cookies from the request.
|
||||
|
||||
```ts
|
||||
request.cookies.clear()
|
||||
```
|
||||
|
||||
## `nextUrl`
|
||||
|
||||
Extends the native [`URL`](https://developer.mozilla.org/docs/Web/API/URL) API with additional convenience methods, including Next.js specific properties.
|
||||
|
||||
```ts
|
||||
// Given a request to /home, pathname is /home
|
||||
request.nextUrl.pathname
|
||||
// Given a request to /home?name=lee, searchParams is { 'name': 'lee' }
|
||||
request.nextUrl.searchParams
|
||||
```
|
||||
|
||||
The following options are available:
|
||||
|
||||
<PagesOnly>
|
||||
|
||||
| Property | Type | Description |
|
||||
| ----------------- | ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `basePath` | `string` | The [base path](/docs/pages/api-reference/config/next-config-js/basePath) of the URL. |
|
||||
| `buildId` | `string` \| `undefined` | The build identifier of the Next.js application. Can be [customized](/docs/pages/api-reference/config/next-config-js/generateBuildId). |
|
||||
| `defaultLocale` | `string` \| `undefined` | The default locale for [internationalization](/docs/pages/guides/internationalization). |
|
||||
| `domainLocale` | | |
|
||||
| - `defaultLocale` | `string` | The default locale within a domain. |
|
||||
| - `domain` | `string` | The domain associated with a specific locale. |
|
||||
| - `http` | `boolean` \| `undefined` | Indicates if the domain is using HTTP. |
|
||||
| `locales` | `string[]` \| `undefined` | An array of available locales. |
|
||||
| `locale` | `string` \| `undefined` | The currently active locale. |
|
||||
| `url` | `URL` | The URL object. |
|
||||
|
||||
</PagesOnly>
|
||||
|
||||
<AppOnly>
|
||||
|
||||
| Property | Type | Description |
|
||||
| -------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `basePath` | `string` | The [base path](/docs/app/api-reference/config/next-config-js/basePath) of the URL. |
|
||||
| `buildId` | `string` \| `undefined` | The build identifier of the Next.js application. Can be [customized](/docs/app/api-reference/config/next-config-js/generateBuildId). |
|
||||
| `pathname` | `string` | The pathname of the URL. |
|
||||
| `searchParams` | `Object` | The search parameters of the URL. |
|
||||
|
||||
> **Note:** The internationalization properties from the Pages Router are not available for usage in the App Router. Learn more about [internationalization with the App Router](/docs/app/guides/internationalization).
|
||||
|
||||
</AppOnly>
|
||||
|
||||
## Version History
|
||||
|
||||
| Version | Changes |
|
||||
| --------- | ----------------------- |
|
||||
| `v15.0.0` | `ip` and `geo` removed. |
|
||||
Generated
Vendored
+203
@@ -0,0 +1,203 @@
|
||||
---
|
||||
title: NextResponse
|
||||
description: API Reference for NextResponse.
|
||||
---
|
||||
|
||||
{/* The content of this doc is shared between the app and pages router. You can use the `<PagesOnly>Content</PagesOnly>` component to add content that is specific to the Pages Router. Any shared content should not be wrapped in a component. */}
|
||||
|
||||
NextResponse extends the [Web Response API](https://developer.mozilla.org/docs/Web/API/Response) with additional convenience methods.
|
||||
|
||||
## `cookies`
|
||||
|
||||
Read or mutate the [`Set-Cookie`](https://developer.mozilla.org/docs/Web/HTTP/Headers/Set-Cookie) header of the response.
|
||||
|
||||
### `set(name, value)`
|
||||
|
||||
Given a name, set a cookie with the given value on the response.
|
||||
|
||||
```ts
|
||||
// Given incoming request /home
|
||||
let response = NextResponse.next()
|
||||
// Set a cookie to hide the banner
|
||||
response.cookies.set('show-banner', 'false')
|
||||
// Response will have a `Set-Cookie:show-banner=false;path=/home` header
|
||||
return response
|
||||
```
|
||||
|
||||
### `get(name)`
|
||||
|
||||
Given a cookie name, return the value of the cookie. If the cookie is not found, `undefined` is returned. If multiple cookies are found, the first one is returned.
|
||||
|
||||
```ts
|
||||
// Given incoming request /home
|
||||
let response = NextResponse.next()
|
||||
// { name: 'show-banner', value: 'false', Path: '/home' }
|
||||
response.cookies.get('show-banner')
|
||||
```
|
||||
|
||||
### `getAll()`
|
||||
|
||||
Given a cookie name, return the values of the cookie. If no name is given, return all cookies on the response.
|
||||
|
||||
```ts
|
||||
// Given incoming request /home
|
||||
let response = NextResponse.next()
|
||||
// [
|
||||
// { name: 'experiments', value: 'new-pricing-page', Path: '/home' },
|
||||
// { name: 'experiments', value: 'winter-launch', Path: '/home' },
|
||||
// ]
|
||||
response.cookies.getAll('experiments')
|
||||
// Alternatively, get all cookies for the response
|
||||
response.cookies.getAll()
|
||||
```
|
||||
|
||||
### `has(name)`
|
||||
|
||||
Given a cookie name, return `true` if the cookie exists on the response.
|
||||
|
||||
```ts
|
||||
// Given incoming request /home
|
||||
let response = NextResponse.next()
|
||||
// Returns true if cookie exists, false if it does not
|
||||
response.cookies.has('experiments')
|
||||
```
|
||||
|
||||
### `delete(name)`
|
||||
|
||||
Given a cookie name, delete the cookie from the response.
|
||||
|
||||
```ts
|
||||
// Given incoming request /home
|
||||
let response = NextResponse.next()
|
||||
// Returns true for deleted, false if nothing is deleted
|
||||
response.cookies.delete('experiments')
|
||||
```
|
||||
|
||||
## `json()`
|
||||
|
||||
Produce a response with the given JSON body.
|
||||
|
||||
```ts filename="app/api/route.ts" switcher
|
||||
import { NextResponse } from 'next/server'
|
||||
|
||||
export async function GET(request: Request) {
|
||||
return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 })
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="app/api/route.js" switcher
|
||||
import { NextResponse } from 'next/server'
|
||||
|
||||
export async function GET(request) {
|
||||
return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 })
|
||||
}
|
||||
```
|
||||
|
||||
## `redirect()`
|
||||
|
||||
Produce a response that redirects to a [URL](https://developer.mozilla.org/docs/Web/API/URL).
|
||||
|
||||
```ts
|
||||
import { NextResponse } from 'next/server'
|
||||
|
||||
return NextResponse.redirect(new URL('/new', request.url))
|
||||
```
|
||||
|
||||
The [URL](https://developer.mozilla.org/docs/Web/API/URL) can be created and modified before being used in the `NextResponse.redirect()` method. For example, you can use the `request.nextUrl` property to get the current URL, and then modify it to redirect to a different URL.
|
||||
|
||||
```ts
|
||||
import { NextResponse } from 'next/server'
|
||||
|
||||
// Given an incoming request...
|
||||
const loginUrl = new URL('/login', request.url)
|
||||
// Add ?from=/incoming-url to the /login URL
|
||||
loginUrl.searchParams.set('from', request.nextUrl.pathname)
|
||||
// And redirect to the new URL
|
||||
return NextResponse.redirect(loginUrl)
|
||||
```
|
||||
|
||||
## `rewrite()`
|
||||
|
||||
Produce a response that rewrites (proxies) the given [URL](https://developer.mozilla.org/docs/Web/API/URL) while preserving the original URL.
|
||||
|
||||
```ts
|
||||
import { NextResponse } from 'next/server'
|
||||
|
||||
// Incoming request: /about, browser shows /about
|
||||
// Rewritten request: /proxy, browser shows /about
|
||||
return NextResponse.rewrite(new URL('/proxy', request.url))
|
||||
```
|
||||
|
||||
## `next()`
|
||||
|
||||
The `next()` method is useful for Proxy, as it allows you to return early and continue routing.
|
||||
|
||||
```ts
|
||||
import { NextResponse } from 'next/server'
|
||||
|
||||
return NextResponse.next()
|
||||
```
|
||||
|
||||
You can also forward `headers` upstream when producing the response, using `NextResponse.next({ request: { headers } })`:
|
||||
|
||||
```ts
|
||||
import { NextResponse } from 'next/server'
|
||||
|
||||
// Given an incoming request...
|
||||
const newHeaders = new Headers(request.headers)
|
||||
// Add a new header
|
||||
newHeaders.set('x-version', '123')
|
||||
// Forward the modified request headers upstream
|
||||
return NextResponse.next({
|
||||
request: {
|
||||
// New request headers
|
||||
headers: newHeaders,
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
This forwards `newHeaders` upstream to the target page, route, or server action, and does not expose them to the client. While this pattern is useful for passing data upstream, it should be used with caution because the headers containing this data may be forwarded to external services.
|
||||
|
||||
In contrast, `NextResponse.next({ headers })` is a shorthand for sending headers from proxy to the client. This is **NOT** good practice and should be avoided. Among other reasons because setting response headers like `Content-Type`, can override framework expectations (for example, the `Content-Type` used by Server Actions), leading to failed submissions or broken streaming responses.
|
||||
|
||||
```ts
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
|
||||
async function proxy(request: NextRequest) {
|
||||
const headers = await injectAuth(request.headers)
|
||||
// DO NOT forward headers like this
|
||||
return NextResponse.next({ headers })
|
||||
}
|
||||
```
|
||||
|
||||
In general, avoid copying all incoming request headers because doing so can leak sensitive data to clients or upstream services.
|
||||
|
||||
Prefer a defensive approach by creating a subset of incoming request headers using an allow-list. For example, you might discard custom `x-*` headers and only forward known-safe headers:
|
||||
|
||||
```ts
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
|
||||
function proxy(request: NextRequest) {
|
||||
const incoming = new Headers(request.headers)
|
||||
const forwarded = new Headers()
|
||||
|
||||
for (const [name, value] of incoming) {
|
||||
const headerName = name.toLowerCase()
|
||||
// Keep only known-safe headers, discard custom x-* and other sensitive ones
|
||||
if (
|
||||
!headerName.startsWith('x-') &&
|
||||
headerName !== 'authorization' &&
|
||||
headerName !== 'cookie'
|
||||
) {
|
||||
// Preserve original header name casing
|
||||
forwarded.set(name, value)
|
||||
}
|
||||
}
|
||||
|
||||
return NextResponse.next({
|
||||
request: {
|
||||
headers: forwarded,
|
||||
},
|
||||
})
|
||||
}
|
||||
```
|
||||
Generated
Vendored
+39
@@ -0,0 +1,39 @@
|
||||
---
|
||||
title: notFound
|
||||
description: API Reference for the notFound function.
|
||||
---
|
||||
|
||||
The `notFound` function allows you to render the [`not-found file`](/docs/app/api-reference/file-conventions/not-found) within a route segment as well as inject a [`<meta name="robots" content="noindex" />`](/docs/app/api-reference/file-conventions/loading#status-codes) tag for search engines.
|
||||
|
||||
## `notFound()`
|
||||
|
||||
Invoking the `notFound()` function throws a `NEXT_HTTP_ERROR_FALLBACK;404` error and terminates rendering of the route segment in which it was thrown. Specifying a [**not-found** file](/docs/app/api-reference/file-conventions/not-found) allows you to gracefully handle such errors by rendering a Not Found UI within the segment.
|
||||
|
||||
```jsx filename="app/user/[id]/page.js"
|
||||
import { notFound } from 'next/navigation'
|
||||
|
||||
async function fetchUser(id) {
|
||||
const res = await fetch('https://...')
|
||||
if (!res.ok) return undefined
|
||||
return res.json()
|
||||
}
|
||||
|
||||
export default async function Profile({ params }) {
|
||||
const { id } = await params
|
||||
const user = await fetchUser(id)
|
||||
|
||||
if (!user) {
|
||||
notFound()
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
> **Good to know**: `notFound()` does not require you to use `return notFound()` due to using the TypeScript [`never`](https://www.typescriptlang.org/docs/handbook/2/functions.html#never) type.
|
||||
|
||||
## Version History
|
||||
|
||||
| Version | Changes |
|
||||
| --------- | ---------------------- |
|
||||
| `v13.0.0` | `notFound` introduced. |
|
||||
Generated
Vendored
+72
@@ -0,0 +1,72 @@
|
||||
---
|
||||
title: permanentRedirect
|
||||
description: API Reference for the permanentRedirect function.
|
||||
related:
|
||||
links:
|
||||
- app/api-reference/functions/redirect
|
||||
---
|
||||
|
||||
The `permanentRedirect` function allows you to redirect the user to another URL. `permanentRedirect` can be used in Server Components, Client Components, [Route Handlers](/docs/app/api-reference/file-conventions/route), and [Server Functions](/docs/app/getting-started/mutating-data).
|
||||
|
||||
When used in a streaming context, this will insert a meta tag to emit the redirect on the client side. When used in a server action, it will serve a 303 HTTP redirect response to the caller. Otherwise, it will serve a 308 (Permanent) HTTP redirect response to the caller.
|
||||
|
||||
If a resource doesn't exist, you can use the [`notFound` function](/docs/app/api-reference/functions/not-found) instead.
|
||||
|
||||
> **Good to know**: If you prefer to return a 307 (Temporary) HTTP redirect instead of 308 (Permanent), you can use the [`redirect` function](/docs/app/api-reference/functions/redirect) instead.
|
||||
|
||||
## Parameters
|
||||
|
||||
The `permanentRedirect` function accepts two arguments:
|
||||
|
||||
```js
|
||||
permanentRedirect(path, type)
|
||||
```
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ------------------------------------------------------------- | ----------------------------------------------------------- |
|
||||
| `path` | `string` | The URL to redirect to. Can be a relative or absolute path. |
|
||||
| `type` | `'replace'` (default) or `'push'` (default in Server Actions) | The type of redirect to perform. |
|
||||
|
||||
By default, `permanentRedirect` will use `push` (adding a new entry to the browser history stack) in [Server Actions](/docs/app/getting-started/mutating-data) and `replace` (replacing the current URL in the browser history stack) everywhere else. You can override this behavior by specifying the `type` parameter.
|
||||
|
||||
The `RedirectType` object contains the available options for the `type` parameter.
|
||||
|
||||
```ts
|
||||
import { permanentRedirect, RedirectType } from 'next/navigation'
|
||||
|
||||
permanentRedirect('/redirect-to', RedirectType.replace)
|
||||
// or
|
||||
permanentRedirect('/redirect-to', RedirectType.push)
|
||||
```
|
||||
|
||||
The `type` parameter has no effect when used in Server Components.
|
||||
|
||||
## Returns
|
||||
|
||||
`permanentRedirect` does not return a value.
|
||||
|
||||
## Example
|
||||
|
||||
Invoking the `permanentRedirect()` function throws a `NEXT_REDIRECT` error and terminates rendering of the route segment in which it was thrown.
|
||||
|
||||
```jsx filename="app/team/[id]/page.js"
|
||||
import { permanentRedirect } from 'next/navigation'
|
||||
|
||||
async function fetchTeam(id) {
|
||||
const res = await fetch('https://...')
|
||||
if (!res.ok) return undefined
|
||||
return res.json()
|
||||
}
|
||||
|
||||
export default async function Profile({ params }) {
|
||||
const { id } = await params
|
||||
const team = await fetchTeam(id)
|
||||
if (!team) {
|
||||
permanentRedirect('/login')
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
> **Good to know**: `permanentRedirect` does not require you to use `return permanentRedirect()` as it uses the TypeScript [`never`](https://www.typescriptlang.org/docs/handbook/2/functions.html#never) type.
|
||||
Generated
Vendored
+222
@@ -0,0 +1,222 @@
|
||||
---
|
||||
title: redirect
|
||||
description: API Reference for the redirect function.
|
||||
related:
|
||||
links:
|
||||
- app/api-reference/functions/permanentRedirect
|
||||
---
|
||||
|
||||
The `redirect` function allows you to redirect the user to another URL. `redirect` can be used while rendering in [Server and Client Components](/docs/app/getting-started/server-and-client-components), [Route Handlers](/docs/app/api-reference/file-conventions/route), and [Server Functions](/docs/app/getting-started/mutating-data).
|
||||
|
||||
When used in a [streaming context](/docs/app/getting-started/linking-and-navigating#streaming), this will insert a meta tag to emit the redirect on the client side. When used in a server action, it will serve a 303 HTTP redirect response to the caller. Otherwise, it will serve a 307 HTTP redirect response to the caller.
|
||||
|
||||
If a resource doesn't exist, you can use the [`notFound` function](/docs/app/api-reference/functions/not-found) instead.
|
||||
|
||||
## Reference
|
||||
|
||||
### Parameters
|
||||
|
||||
The `redirect` function accepts two arguments:
|
||||
|
||||
```js
|
||||
redirect(path, type)
|
||||
```
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ------------------------------------------------------------- | ----------------------------------------------------------- |
|
||||
| `path` | `string` | The URL to redirect to. Can be a relative or absolute path. |
|
||||
| `type` | `'replace'` (default) or `'push'` (default in Server Actions) | The type of redirect to perform. |
|
||||
|
||||
By default, `redirect` will use `push` (adding a new entry to the browser history stack) in [Server Actions](/docs/app/getting-started/mutating-data) and `replace` (replacing the current URL in the browser history stack) everywhere else. You can override this behavior by specifying the `type` parameter.
|
||||
|
||||
The `RedirectType` object contains the available options for the `type` parameter.
|
||||
|
||||
```ts
|
||||
import { redirect, RedirectType } from 'next/navigation'
|
||||
|
||||
redirect('/redirect-to', RedirectType.replace)
|
||||
// or
|
||||
redirect('/redirect-to', RedirectType.push)
|
||||
```
|
||||
|
||||
The `type` parameter has no effect when used in Server Components.
|
||||
|
||||
### Returns
|
||||
|
||||
`redirect` does not return a value.
|
||||
|
||||
## Behavior
|
||||
|
||||
- In Server Actions and Route Handlers, redirect should be called **outside** the `try` block when using `try/catch` statements.
|
||||
- If you prefer to return a 308 (Permanent) HTTP redirect instead of 307 (Temporary), you can use the [`permanentRedirect` function](/docs/app/api-reference/functions/permanentRedirect) instead.
|
||||
- `redirect` throws an error so it should be called **outside** the `try` block when using `try/catch` statements.
|
||||
- `redirect` can be called in Client Components during the rendering process but not in event handlers. You can use the [`useRouter` hook](/docs/app/api-reference/functions/use-router) instead.
|
||||
- `redirect` also accepts absolute URLs and can be used to redirect to external links.
|
||||
- If you'd like to redirect before the render process, use [`next.config.js`](/docs/app/guides/redirecting#redirects-in-nextconfigjs) or [Proxy](/docs/app/guides/redirecting#nextresponseredirect-in-proxy).
|
||||
|
||||
## Example
|
||||
|
||||
### Server Component
|
||||
|
||||
Invoking the `redirect()` function throws a `NEXT_REDIRECT` error and terminates rendering of the route segment in which it was thrown.
|
||||
|
||||
```tsx filename="app/team/[id]/page.tsx" switcher
|
||||
import { redirect } from 'next/navigation'
|
||||
|
||||
async function fetchTeam(id: string) {
|
||||
const res = await fetch('https://...')
|
||||
if (!res.ok) return undefined
|
||||
return res.json()
|
||||
}
|
||||
|
||||
export default async function Profile({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ id: string }>
|
||||
}) {
|
||||
const { id } = await params
|
||||
const team = await fetchTeam(id)
|
||||
|
||||
if (!team) {
|
||||
redirect('/login')
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/team/[id]/page.js" switcher
|
||||
import { redirect } from 'next/navigation'
|
||||
|
||||
async function fetchTeam(id) {
|
||||
const res = await fetch('https://...')
|
||||
if (!res.ok) return undefined
|
||||
return res.json()
|
||||
}
|
||||
|
||||
export default async function Profile({ params }) {
|
||||
const { id } = await params
|
||||
const team = await fetchTeam(id)
|
||||
|
||||
if (!team) {
|
||||
redirect('/login')
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
> **Good to know**: `redirect` does not require you to use `return redirect()` as it uses the TypeScript [`never`](https://www.typescriptlang.org/docs/handbook/2/functions.html#never) type.
|
||||
|
||||
### Client Component
|
||||
|
||||
`redirect` can be directly used in a Client Component.
|
||||
|
||||
```tsx filename="components/client-redirect.tsx" switcher
|
||||
'use client'
|
||||
|
||||
import { redirect, usePathname } from 'next/navigation'
|
||||
|
||||
export function ClientRedirect() {
|
||||
const pathname = usePathname()
|
||||
|
||||
if (pathname.startsWith('/admin') && !pathname.includes('/login')) {
|
||||
redirect('/admin/login')
|
||||
}
|
||||
|
||||
return <div>Login Page</div>
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="components/client-redirect.jsx" switcher
|
||||
'use client'
|
||||
|
||||
import { redirect, usePathname } from 'next/navigation'
|
||||
|
||||
export function ClientRedirect() {
|
||||
const pathname = usePathname()
|
||||
|
||||
if (pathname.startsWith('/admin') && !pathname.includes('/login')) {
|
||||
redirect('/admin/login')
|
||||
}
|
||||
|
||||
return <div>Login Page</div>
|
||||
}
|
||||
```
|
||||
|
||||
> **Good to know**: When using `redirect` in a Client Component on initial page load during Server-Side Rendering (SSR), it will perform a server-side redirect.
|
||||
|
||||
`redirect` can be used in a Client Component through a Server Action. If you need to use an event handler to redirect the user, you can use the [`useRouter`](/docs/app/api-reference/functions/use-router) hook.
|
||||
|
||||
```tsx filename="app/client-redirect.tsx" switcher
|
||||
'use client'
|
||||
|
||||
import { navigate } from './actions'
|
||||
|
||||
export function ClientRedirect() {
|
||||
return (
|
||||
<form action={navigate}>
|
||||
<input type="text" name="id" />
|
||||
<button>Submit</button>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/client-redirect.jsx" switcher
|
||||
'use client'
|
||||
|
||||
import { navigate } from './actions'
|
||||
|
||||
export function ClientRedirect() {
|
||||
return (
|
||||
<form action={navigate}>
|
||||
<input type="text" name="id" />
|
||||
<button>Submit</button>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```ts filename="app/actions.ts" switcher
|
||||
'use server'
|
||||
|
||||
import { redirect } from 'next/navigation'
|
||||
|
||||
export async function navigate(data: FormData) {
|
||||
redirect(`/posts/${data.get('id')}`)
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="app/actions.js" switcher
|
||||
'use server'
|
||||
|
||||
import { redirect } from 'next/navigation'
|
||||
|
||||
export async function navigate(data) {
|
||||
redirect(`/posts/${data.get('id')}`)
|
||||
}
|
||||
```
|
||||
|
||||
## FAQ
|
||||
|
||||
### Why does `redirect` use 307 and 308?
|
||||
|
||||
When using `redirect()` you may notice that the status codes used are `307` for a temporary redirect, and `308` for a permanent redirect. While traditionally a `302` was used for a temporary redirect, and a `301` for a permanent redirect, many browsers changed the request method of the redirect, from a `POST` to `GET` request when using a `302`, regardless of the origins request method.
|
||||
|
||||
Taking the following example of a redirect from `/users` to `/people`, if you make a `POST` request to `/users` to create a new user, and are conforming to a `302` temporary redirect, the request method will be changed from a `POST` to a `GET` request. This doesn't make sense, as to create a new user, you should be making a `POST` request to `/people`, and not a `GET` request.
|
||||
|
||||
The introduction of the `307` status code means that the request method is preserved as `POST`.
|
||||
|
||||
- `302` - Temporary redirect, will change the request method from `POST` to `GET`
|
||||
- `307` - Temporary redirect, will preserve the request method as `POST`
|
||||
|
||||
The `redirect()` method uses a `307` by default, instead of a `302` temporary redirect, meaning your requests will _always_ be preserved as `POST` requests.
|
||||
|
||||
[Learn more](https://developer.mozilla.org/docs/Web/HTTP/Redirections) about HTTP Redirects.
|
||||
|
||||
## Version History
|
||||
|
||||
| Version | Changes |
|
||||
| --------- | ---------------------- |
|
||||
| `v13.0.0` | `redirect` introduced. |
|
||||
Generated
Vendored
+69
@@ -0,0 +1,69 @@
|
||||
---
|
||||
title: refresh
|
||||
description: API Reference for the refresh function.
|
||||
---
|
||||
|
||||
`refresh` allows you to refresh the client router from within a [Server Action](/docs/app/getting-started/mutating-data).
|
||||
|
||||
## Usage
|
||||
|
||||
`refresh` can **only** be called from within Server Actions. It cannot be used in Route Handlers, Client Components, or any other context.
|
||||
|
||||
## Parameters
|
||||
|
||||
```tsx
|
||||
refresh(): void;
|
||||
```
|
||||
|
||||
## Returns
|
||||
|
||||
`refresh` does not return a value.
|
||||
|
||||
## Examples
|
||||
|
||||
```ts filename="app/actions.ts" switcher
|
||||
'use server'
|
||||
|
||||
import { refresh } from 'next/cache'
|
||||
|
||||
export async function createPost(formData: FormData) {
|
||||
const title = formData.get('title')
|
||||
const content = formData.get('content')
|
||||
|
||||
// Create the post in your database
|
||||
const post = await db.post.create({
|
||||
data: { title, content },
|
||||
})
|
||||
|
||||
refresh()
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="app/actions.js" switcher
|
||||
'use server'
|
||||
|
||||
import { refresh } from 'next/cache'
|
||||
|
||||
export async function createPost(formData) {
|
||||
const title = formData.get('title')
|
||||
const content = formData.get('content')
|
||||
|
||||
// Create the post in your database
|
||||
const post = await db.post.create({
|
||||
data: { title, content },
|
||||
})
|
||||
|
||||
refresh()
|
||||
}
|
||||
```
|
||||
|
||||
### Error when used outside Server Actions
|
||||
|
||||
```ts filename="app/api/posts/route.ts" switcher
|
||||
import { refresh } from 'next/cache'
|
||||
|
||||
export async function POST() {
|
||||
// This will throw an error
|
||||
refresh()
|
||||
}
|
||||
```
|
||||
Generated
Vendored
+226
@@ -0,0 +1,226 @@
|
||||
---
|
||||
title: revalidatePath
|
||||
description: API Reference for the revalidatePath function.
|
||||
---
|
||||
|
||||
`revalidatePath` allows you to invalidate [cached data](/docs/app/getting-started/caching) on-demand for a specific path.
|
||||
|
||||
## Usage
|
||||
|
||||
`revalidatePath` can be called in Server Functions and Route Handlers.
|
||||
|
||||
`revalidatePath` cannot be called in Client Components or Proxy, as it only works in server environments.
|
||||
|
||||
> **Good to know**:
|
||||
>
|
||||
> - **Server Functions**: Updates the UI immediately (if viewing the affected path). Currently, it also causes all previously visited pages to refresh when navigated to again. This behavior is temporary and will be updated in the future to apply only to the specific path.
|
||||
> - **Route Handlers**: Marks the path for revalidation. The revalidation is done on the next visit to the specified path. This means calling `revalidatePath` with a dynamic route segment will not immediately trigger many revalidations at once. The invalidation only happens when the path is next visited.
|
||||
|
||||
## Parameters
|
||||
|
||||
```tsx
|
||||
revalidatePath(path: string, type?: 'page' | 'layout'): void;
|
||||
```
|
||||
|
||||
- `path`: Either a string that represents your route file structure. This can be a literal path like `/product/123`, or a route pattern with dynamic segments like `/product/[slug]`. Do not append `/page` or `/layout`, use the `type` parameter instead. Must not exceed 1024 characters. This value is case-sensitive. You do not need to include a trailing slash, regardless of your [`trailingSlash`](/docs/app/api-reference/config/next-config-js/trailingSlash) config.
|
||||
- `type`: (optional) `'page'` or `'layout'` string to change the type of path to revalidate. If `path` contains a dynamic segment, for example `/product/[slug]`, this parameter is required. If `path` is a literal path like `/product/1`, omit `type`.
|
||||
|
||||
Use a literal path when you want to refresh a [single page](#revalidating-a-specific-path). Use a route pattern plus `type` to refresh [all matching pages](#revalidating-a-page-path).
|
||||
|
||||
## Returns
|
||||
|
||||
`revalidatePath` does not return a value.
|
||||
|
||||
## What can be invalidated
|
||||
|
||||
The path parameter can point to pages, layouts, or route handlers:
|
||||
|
||||
- **Pages**: Invalidates the specific page
|
||||
- **Layouts**: Invalidates the layout (the `layout.tsx` at that segment), all nested layouts beneath it, and all pages beneath them
|
||||
- **Route Handlers**: Invalidates cached data accessed within route handlers. For example `revalidatePath("/api/data")` invalidates this GET handler:
|
||||
|
||||
```ts filename="app/api/data/route.ts"
|
||||
export async function GET() {
|
||||
const data = await fetch('https://api.vercel.app/blog', {
|
||||
cache: 'force-cache',
|
||||
})
|
||||
|
||||
return Response.json(await data.json())
|
||||
}
|
||||
```
|
||||
|
||||
## Using `revalidatePath` with rewrites
|
||||
|
||||
When using [rewrites](/docs/app/api-reference/config/next-config-js/rewrites), you must pass the **destination** path (the actual route file location), not the source path that appears in the browser's address bar.
|
||||
|
||||
For example, if you have a rewrite from `/blog` to `/news`:
|
||||
|
||||
```js filename="next.config.js"
|
||||
module.exports = {
|
||||
async rewrites() {
|
||||
return [
|
||||
{
|
||||
source: '/blog',
|
||||
destination: '/news',
|
||||
},
|
||||
]
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
To revalidate this page, use the destination path:
|
||||
|
||||
```ts
|
||||
// Correct: use the destination path
|
||||
revalidatePath('/news')
|
||||
|
||||
// Incorrect: the source path won't match the cache entry
|
||||
revalidatePath('/blog')
|
||||
```
|
||||
|
||||
This is because `revalidatePath` operates on the route file structure, not the URL visible to users. Cache entries are tagged based on which route file renders them.
|
||||
|
||||
## Relationship with `revalidateTag` and `updateTag`
|
||||
|
||||
`revalidatePath`, [`revalidateTag`](/docs/app/api-reference/functions/revalidateTag) and [`updateTag`](/docs/app/api-reference/functions/updateTag) serve different purposes:
|
||||
|
||||
- **`revalidatePath`**: Invalidates a specific page or layout path
|
||||
- **`revalidateTag`**: Marks data with specific tags as **stale**. Applies across all pages that use those tags
|
||||
- **`updateTag`**: Expires data with specific tags. Applies across all pages that use those tags
|
||||
|
||||
When you call `revalidatePath`, only the specified path gets fresh data on the next visit. Other pages that use the same data tags will continue to serve cached data until those specific tags are also revalidated:
|
||||
|
||||
```tsx
|
||||
// Page A: /blog
|
||||
const posts = await fetch('https://api.vercel.app/blog', {
|
||||
next: { tags: ['posts'] },
|
||||
})
|
||||
|
||||
// Page B: /dashboard
|
||||
const recentPosts = await fetch('https://api.vercel.app/blog?limit=5', {
|
||||
next: { tags: ['posts'] },
|
||||
})
|
||||
```
|
||||
|
||||
After calling `revalidatePath('/blog')`:
|
||||
|
||||
- **Page A (/blog)**: Shows fresh data (page re-rendered)
|
||||
- **Page B (/dashboard)**: Still shows stale data (cache tag 'posts' not invalidated)
|
||||
|
||||
Learn about the difference between [`revalidateTag` and `updateTag`](/docs/app/api-reference/functions/updateTag#differences-from-revalidatetag).
|
||||
|
||||
### Building revalidation utilities
|
||||
|
||||
`revalidatePath` and `updateTag` are complementary primitives that are often used together in utility functions to ensure comprehensive data consistency across your application:
|
||||
|
||||
```ts
|
||||
'use server'
|
||||
|
||||
import { revalidatePath, updateTag } from 'next/cache'
|
||||
|
||||
export async function updatePost() {
|
||||
await updatePostInDatabase()
|
||||
|
||||
revalidatePath('/blog') // Refresh the blog page
|
||||
updateTag('posts') // Refresh all pages using 'posts' tag
|
||||
}
|
||||
```
|
||||
|
||||
This pattern ensures that both the specific page and any other pages using the same data remain consistent.
|
||||
|
||||
## Examples
|
||||
|
||||
### Revalidating a specific path
|
||||
|
||||
```ts
|
||||
import { revalidatePath } from 'next/cache'
|
||||
revalidatePath('/blog/post-1')
|
||||
```
|
||||
|
||||
This will invalidate one specific path for revalidation on the next page visit.
|
||||
|
||||
### Revalidating a Page path
|
||||
|
||||
```ts
|
||||
import { revalidatePath } from 'next/cache'
|
||||
revalidatePath('/blog/[slug]', 'page')
|
||||
// or with route groups
|
||||
revalidatePath('/(main)/blog/[slug]', 'page')
|
||||
```
|
||||
|
||||
This will invalidate any path that matches the provided `page` file for revalidation on the next page visit. This will _not_ invalidate pages beneath the specific page. For example, `/blog/[slug]` won't invalidate `/blog/[slug]/[author]`.
|
||||
|
||||
### Revalidating a Layout path
|
||||
|
||||
```ts
|
||||
import { revalidatePath } from 'next/cache'
|
||||
revalidatePath('/blog/[slug]', 'layout')
|
||||
// or with route groups
|
||||
revalidatePath('/(main)/post/[slug]', 'layout')
|
||||
```
|
||||
|
||||
This will invalidate any path that matches the provided `layout` file for revalidation on the next page visit. This will cause pages beneath with the same layout to be invalidated and revalidated on the next visit. For example, in the above case, `/blog/[slug]/[another]` would also be invalidated and revalidated on the next visit.
|
||||
|
||||
### Revalidating all data
|
||||
|
||||
```ts
|
||||
import { revalidatePath } from 'next/cache'
|
||||
|
||||
revalidatePath('/', 'layout')
|
||||
```
|
||||
|
||||
This will purge the [Client Cache](/docs/app/glossary#client-cache), and invalidate all cached data for revalidation on the next page visit.
|
||||
|
||||
### Server Function
|
||||
|
||||
```ts filename="app/actions.ts" switcher
|
||||
'use server'
|
||||
|
||||
import { revalidatePath } from 'next/cache'
|
||||
|
||||
export default async function submit() {
|
||||
await submitForm()
|
||||
revalidatePath('/')
|
||||
}
|
||||
```
|
||||
|
||||
### Route Handler
|
||||
|
||||
```ts filename="app/api/revalidate/route.ts" switcher
|
||||
import { revalidatePath } from 'next/cache'
|
||||
import type { NextRequest } from 'next/server'
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
const path = request.nextUrl.searchParams.get('path')
|
||||
|
||||
if (path) {
|
||||
revalidatePath(path)
|
||||
return Response.json({ revalidated: true, now: Date.now() })
|
||||
}
|
||||
|
||||
return Response.json({
|
||||
revalidated: false,
|
||||
now: Date.now(),
|
||||
message: 'Missing path to revalidate',
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="app/api/revalidate/route.js" switcher
|
||||
import { revalidatePath } from 'next/cache'
|
||||
|
||||
export async function GET(request) {
|
||||
const path = request.nextUrl.searchParams.get('path')
|
||||
|
||||
if (path) {
|
||||
revalidatePath(path)
|
||||
return Response.json({ revalidated: true, now: Date.now() })
|
||||
}
|
||||
|
||||
return Response.json({
|
||||
revalidated: false,
|
||||
now: Date.now(),
|
||||
message: 'Missing path to revalidate',
|
||||
})
|
||||
}
|
||||
```
|
||||
Generated
Vendored
+136
@@ -0,0 +1,136 @@
|
||||
---
|
||||
title: revalidateTag
|
||||
description: API Reference for the revalidateTag function.
|
||||
---
|
||||
|
||||
`revalidateTag` allows you to invalidate cached data on-demand for a specific cache tag.
|
||||
|
||||
This function is ideal for content where a slight delay in updates is acceptable, such as blog posts, product catalogs, or documentation. Users receive stale content while fresh data loads in the background.
|
||||
|
||||
## Usage
|
||||
|
||||
`revalidateTag` can be called in Server Functions and Route Handlers.
|
||||
|
||||
`revalidateTag` cannot be called in Client Components or Proxy, as it only works in server environments.
|
||||
|
||||
### Revalidation Behavior
|
||||
|
||||
The revalidation behavior depends on whether you provide the second argument:
|
||||
|
||||
- **With `profile="max"` (recommended)**: The tag entry is marked as stale, and the next time a resource with that tag is visited, it will use stale-while-revalidate semantics. This means the stale content is served while fresh content is fetched in the background.
|
||||
- **With a custom cache life profile**: For advanced usage, you can specify any cache life profile that your application has defined, allowing for custom revalidation behaviors tailored to your specific caching requirements.
|
||||
- **Without the second argument (deprecated)**: The tag entry is expired immediately, and the next request to that resource will be a blocking revalidate/cache miss. This behavior is now deprecated, and you should either use `profile="max"` or migrate to [`updateTag`](/docs/app/api-reference/functions/updateTag).
|
||||
|
||||
> **Good to know**: When using `profile="max"`, `revalidateTag` marks tagged data as stale, but fresh data is only fetched when pages using that tag are next visited. This means calling `revalidateTag` will not immediately trigger many revalidations at once. The invalidation only happens when any page using that tag is next visited.
|
||||
|
||||
## Parameters
|
||||
|
||||
```ts
|
||||
revalidateTag(tag: string, profile: string | { expire?: number }): void;
|
||||
```
|
||||
|
||||
- `tag`: A string representing the cache tag associated with the data you want to revalidate. Must not exceed 256 characters. This value is case-sensitive.
|
||||
- `profile`: A string that specifies the revalidation behavior. The recommended value is `"max"` which provides stale-while-revalidate semantics, or any of the other default or custom profiles defined in [`cacheLife`](/docs/app/api-reference/config/next-config-js/cacheLife). Alternatively, you can pass an object with an `expire` property for custom expiration behavior.
|
||||
|
||||
Tags must first be assigned to cached data. You can do this in two ways:
|
||||
|
||||
- Using the [`next.tags`](/docs/app/api-reference/functions/fetch) option with `fetch` for caching external API requests:
|
||||
|
||||
```tsx
|
||||
fetch(url, { next: { tags: ['posts'] } })
|
||||
```
|
||||
|
||||
- Using [`cacheTag`](/docs/app/api-reference/functions/cacheTag) inside cached functions or components with the `'use cache'` directive:
|
||||
|
||||
```tsx
|
||||
import { cacheTag } from 'next/cache'
|
||||
|
||||
async function getData() {
|
||||
'use cache'
|
||||
cacheTag('posts')
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
> **Good to know**: The single-argument form `revalidateTag(tag)` is deprecated. It currently works if TypeScript errors are suppressed, but this behavior may be removed in a future version. Update to the two-argument signature.
|
||||
|
||||
## Returns
|
||||
|
||||
`revalidateTag` does not return a value.
|
||||
|
||||
## Relationship with `revalidatePath`
|
||||
|
||||
`revalidateTag` invalidates data with specific tags across all pages that use those tags, while [`revalidatePath`](/docs/app/api-reference/functions/revalidatePath) invalidates specific page or layout paths.
|
||||
|
||||
> **Good to know**: These functions serve different purposes and may need to be used together for comprehensive data consistency. For detailed examples and considerations, see [relationship with revalidateTag and updateTag](/docs/app/api-reference/functions/revalidatePath#relationship-with-revalidatetag-and-updatetag) for more information.
|
||||
|
||||
## Examples
|
||||
|
||||
The following examples demonstrate how to use `revalidateTag` in different contexts. In both cases, we're using `profile="max"` to mark data as stale and use stale-while-revalidate semantics, which is the recommended approach for most use cases.
|
||||
|
||||
### Server Action
|
||||
|
||||
```ts filename="app/actions.ts" switcher
|
||||
'use server'
|
||||
|
||||
import { revalidateTag } from 'next/cache'
|
||||
|
||||
export default async function submit() {
|
||||
await addPost()
|
||||
revalidateTag('posts', 'max')
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="app/actions.js" switcher
|
||||
'use server'
|
||||
|
||||
import { revalidateTag } from 'next/cache'
|
||||
|
||||
export default async function submit() {
|
||||
await addPost()
|
||||
revalidateTag('posts', 'max')
|
||||
}
|
||||
```
|
||||
|
||||
### Route Handler
|
||||
|
||||
```ts filename="app/api/revalidate/route.ts" switcher
|
||||
import type { NextRequest } from 'next/server'
|
||||
import { revalidateTag } from 'next/cache'
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
const tag = request.nextUrl.searchParams.get('tag')
|
||||
|
||||
if (tag) {
|
||||
revalidateTag(tag, 'max')
|
||||
return Response.json({ revalidated: true, now: Date.now() })
|
||||
}
|
||||
|
||||
return Response.json({
|
||||
revalidated: false,
|
||||
now: Date.now(),
|
||||
message: 'Missing tag to revalidate',
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="app/api/revalidate/route.js" switcher
|
||||
import { revalidateTag } from 'next/cache'
|
||||
|
||||
export async function GET(request) {
|
||||
const tag = request.nextUrl.searchParams.get('tag')
|
||||
|
||||
if (tag) {
|
||||
revalidateTag(tag, 'max')
|
||||
return Response.json({ revalidated: true, now: Date.now() })
|
||||
}
|
||||
|
||||
return Response.json({
|
||||
revalidated: false,
|
||||
now: Date.now(),
|
||||
message: 'Missing tag to revalidate',
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
> **Good to know**: For webhooks or third-party services that need immediate expiration, you can pass `{ expire: 0 }` as the second argument: `revalidateTag(tag, { expire: 0 })`. This pattern is necessary when external systems call your Route Handlers and require data to expire immediately. For all other cases, it's recommended to use [`updateTag`](/docs/app/api-reference/functions/updateTag) in Server Actions for immediate updates instead.
|
||||
Generated
Vendored
+234
@@ -0,0 +1,234 @@
|
||||
---
|
||||
title: unauthorized
|
||||
description: API Reference for the unauthorized function.
|
||||
version: experimental
|
||||
related:
|
||||
links:
|
||||
- app/api-reference/file-conventions/unauthorized
|
||||
---
|
||||
|
||||
The `unauthorized` function throws an error that renders a Next.js 401 error page. It's useful for handling authorization errors in your application. You can customize the UI using the [`unauthorized.js` file](/docs/app/api-reference/file-conventions/unauthorized).
|
||||
|
||||
To start using `unauthorized`, enable the experimental [`authInterrupts`](/docs/app/api-reference/config/next-config-js/authInterrupts) configuration option in your `next.config.js` file:
|
||||
|
||||
```ts filename="next.config.ts" switcher
|
||||
import type { NextConfig } from 'next'
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
experimental: {
|
||||
authInterrupts: true,
|
||||
},
|
||||
}
|
||||
|
||||
export default nextConfig
|
||||
```
|
||||
|
||||
```js filename="next.config.js" switcher
|
||||
module.exports = {
|
||||
experimental: {
|
||||
authInterrupts: true,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
`unauthorized` can be invoked in [Server Components](/docs/app/getting-started/server-and-client-components), [Server Functions](/docs/app/getting-started/mutating-data), and [Route Handlers](/docs/app/api-reference/file-conventions/route).
|
||||
|
||||
```tsx filename="app/dashboard/page.tsx" switcher
|
||||
import { verifySession } from '@/app/lib/dal'
|
||||
import { unauthorized } from 'next/navigation'
|
||||
|
||||
export default async function DashboardPage() {
|
||||
const session = await verifySession()
|
||||
|
||||
if (!session) {
|
||||
unauthorized()
|
||||
}
|
||||
|
||||
// Render the dashboard for authenticated users
|
||||
return (
|
||||
<main>
|
||||
<h1>Welcome to the Dashboard</h1>
|
||||
<p>Hi, {session.user.name}.</p>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/dashboard/page.js" switcher
|
||||
import { verifySession } from '@/app/lib/dal'
|
||||
import { unauthorized } from 'next/navigation'
|
||||
|
||||
export default async function DashboardPage() {
|
||||
const session = await verifySession()
|
||||
|
||||
if (!session) {
|
||||
unauthorized()
|
||||
}
|
||||
|
||||
// Render the dashboard for authenticated users
|
||||
return (
|
||||
<main>
|
||||
<h1>Welcome to the Dashboard</h1>
|
||||
<p>Hi, {session.user.name}.</p>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Good to know
|
||||
|
||||
- The `unauthorized` function cannot be called in the [root layout](/docs/app/api-reference/file-conventions/layout#root-layout).
|
||||
|
||||
## Examples
|
||||
|
||||
### Displaying login UI to unauthenticated users
|
||||
|
||||
You can use `unauthorized` function to display the `unauthorized.js` file with a login UI.
|
||||
|
||||
```tsx filename="app/dashboard/page.tsx" switcher
|
||||
import { verifySession } from '@/app/lib/dal'
|
||||
import { unauthorized } from 'next/navigation'
|
||||
|
||||
export default async function DashboardPage() {
|
||||
const session = await verifySession()
|
||||
|
||||
if (!session) {
|
||||
unauthorized()
|
||||
}
|
||||
|
||||
return <div>Dashboard</div>
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/dashboard/page.js" switcher
|
||||
import { verifySession } from '@/app/lib/dal'
|
||||
import { unauthorized } from 'next/navigation'
|
||||
|
||||
export default async function DashboardPage() {
|
||||
const session = await verifySession()
|
||||
|
||||
if (!session) {
|
||||
unauthorized()
|
||||
}
|
||||
|
||||
return <div>Dashboard</div>
|
||||
}
|
||||
```
|
||||
|
||||
```tsx filename="app/unauthorized.tsx" switcher
|
||||
import Login from '@/app/components/Login'
|
||||
|
||||
export default function UnauthorizedPage() {
|
||||
return (
|
||||
<main>
|
||||
<h1>401 - Unauthorized</h1>
|
||||
<p>Please log in to access this page.</p>
|
||||
<Login />
|
||||
</main>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/unauthorized.js" switcher
|
||||
import Login from '@/app/components/Login'
|
||||
|
||||
export default function UnauthorizedPage() {
|
||||
return (
|
||||
<main>
|
||||
<h1>401 - Unauthorized</h1>
|
||||
<p>Please log in to access this page.</p>
|
||||
<Login />
|
||||
</main>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Mutations with Server Actions
|
||||
|
||||
You can invoke `unauthorized` in Server Actions to ensure only authenticated users can perform specific mutations.
|
||||
|
||||
```ts filename="app/actions/update-profile.ts" switcher
|
||||
'use server'
|
||||
|
||||
import { verifySession } from '@/app/lib/dal'
|
||||
import { unauthorized } from 'next/navigation'
|
||||
import db from '@/app/lib/db'
|
||||
|
||||
export async function updateProfile(data: FormData) {
|
||||
const session = await verifySession()
|
||||
|
||||
// If the user is not authenticated, return a 401
|
||||
if (!session) {
|
||||
unauthorized()
|
||||
}
|
||||
|
||||
// Proceed with mutation
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="app/actions/update-profile.js" switcher
|
||||
'use server'
|
||||
|
||||
import { verifySession } from '@/app/lib/dal'
|
||||
import { unauthorized } from 'next/navigation'
|
||||
import db from '@/app/lib/db'
|
||||
|
||||
export async function updateProfile(data) {
|
||||
const session = await verifySession()
|
||||
|
||||
// If the user is not authenticated, return a 401
|
||||
if (!session) {
|
||||
unauthorized()
|
||||
}
|
||||
|
||||
// Proceed with mutation
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### Fetching data with Route Handlers
|
||||
|
||||
You can use `unauthorized` in Route Handlers to ensure only authenticated users can access the endpoint.
|
||||
|
||||
```tsx filename="app/api/profile/route.ts" switcher
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { verifySession } from '@/app/lib/dal'
|
||||
import { unauthorized } from 'next/navigation'
|
||||
|
||||
export async function GET(req: NextRequest): Promise<NextResponse> {
|
||||
// Verify the user's session
|
||||
const session = await verifySession()
|
||||
|
||||
// If no session exists, return a 401 and render unauthorized.tsx
|
||||
if (!session) {
|
||||
unauthorized()
|
||||
}
|
||||
|
||||
// Fetch data
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/api/profile/route.js" switcher
|
||||
import { verifySession } from '@/app/lib/dal'
|
||||
import { unauthorized } from 'next/navigation'
|
||||
|
||||
export async function GET() {
|
||||
const session = await verifySession()
|
||||
|
||||
// If the user is not authenticated, return a 401 and render unauthorized.tsx
|
||||
if (!session) {
|
||||
unauthorized()
|
||||
}
|
||||
|
||||
// Fetch data
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
## Version History
|
||||
|
||||
| Version | Changes |
|
||||
| --------- | -------------------------- |
|
||||
| `v15.1.0` | `unauthorized` introduced. |
|
||||
Generated
Vendored
+98
@@ -0,0 +1,98 @@
|
||||
---
|
||||
title: unstable_cache
|
||||
description: API Reference for the unstable_cache function.
|
||||
---
|
||||
|
||||
> **Note:**
|
||||
> This API has been replaced by [`use cache`](/docs/app/api-reference/directives/use-cache) in Next.js 16.
|
||||
> We recommend opting into [Cache Components](/docs/app/getting-started/caching) and replacing `unstable_cache` with the `use cache` directive.
|
||||
|
||||
`unstable_cache` allows you to cache the results of expensive operations, like database queries, and reuse them across multiple requests.
|
||||
|
||||
```jsx
|
||||
import { getUser } from './data';
|
||||
import { unstable_cache } from 'next/cache';
|
||||
|
||||
const getCachedUser = unstable_cache(
|
||||
async (id) => getUser(id),
|
||||
['my-app-user']
|
||||
);
|
||||
|
||||
export default async function Component({ userID }) {
|
||||
const user = await getCachedUser(userID);
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
> **Good to know**:
|
||||
>
|
||||
> - Accessing uncached data sources such as `headers` or `cookies` inside a cache scope is not supported. If you need this data inside a cached function use `headers` outside of the cached function and pass the required uncached data in as an argument.
|
||||
> - This API uses Next.js' built-in cache to persist the result across requests and deployments. See [Caching and Revalidating](/docs/app/getting-started/caching).
|
||||
|
||||
## Parameters
|
||||
|
||||
```jsx
|
||||
const data = unstable_cache(fetchData, keyParts, options)()
|
||||
```
|
||||
|
||||
- `fetchData`: This is an asynchronous function that fetches the data you want to cache. It must be a function that returns a `Promise`.
|
||||
- `keyParts`: This is an extra array of keys that further adds identification to the cache. By default, `unstable_cache` already uses the arguments and the stringified version of your function as the cache key. It is optional in most cases; the only time you need to use it is when you use external variables without passing them as parameters. However, it is important to add closures used within the function if you do not pass them as parameters.
|
||||
- `options`: This is an object that controls how the cache behaves. It can contain the following properties:
|
||||
- `tags`: An array of tags that can be used to control cache invalidation. Next.js will not use this to uniquely identify the function.
|
||||
- `revalidate`: The number of seconds after which the cache should be revalidated. Omit or pass `false` to cache indefinitely or until matching `revalidateTag()` or `revalidatePath()` methods are called.
|
||||
|
||||
## Returns
|
||||
|
||||
`unstable_cache` returns a function that when invoked, returns a Promise that resolves to the cached data. If the data is not in the cache, the provided function will be invoked, and its result will be cached and returned.
|
||||
|
||||
## Example
|
||||
|
||||
```tsx filename="app/page.tsx" switcher
|
||||
import { unstable_cache } from 'next/cache'
|
||||
|
||||
export default async function Page({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ userId: string }>
|
||||
}) {
|
||||
const { userId } = await params
|
||||
const getCachedUser = unstable_cache(
|
||||
async () => {
|
||||
return { id: userId }
|
||||
},
|
||||
[userId], // add the user ID to the cache key
|
||||
{
|
||||
tags: ['users'],
|
||||
revalidate: 60,
|
||||
}
|
||||
)
|
||||
|
||||
//...
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/page.jsx" switcher
|
||||
import { unstable_cache } from 'next/cache';
|
||||
|
||||
export default async function Page({ params } }) {
|
||||
const { userId } = await params
|
||||
const getCachedUser = unstable_cache(
|
||||
async () => {
|
||||
return { id: userId };
|
||||
},
|
||||
[userId], // add the user ID to the cache key
|
||||
{
|
||||
tags: ["users"],
|
||||
revalidate: 60,
|
||||
}
|
||||
);
|
||||
|
||||
//...
|
||||
}
|
||||
```
|
||||
|
||||
## Version History
|
||||
|
||||
| Version | Changes |
|
||||
| --------- | ---------------------------- |
|
||||
| `v14.0.0` | `unstable_cache` introduced. |
|
||||
Generated
Vendored
+47
@@ -0,0 +1,47 @@
|
||||
---
|
||||
title: unstable_noStore
|
||||
description: API Reference for the unstable_noStore function.
|
||||
version: legacy
|
||||
---
|
||||
|
||||
**In version 15, we recommend using [`connection`](/docs/app/api-reference/functions/connection) instead of `unstable_noStore`.**
|
||||
|
||||
`unstable_noStore` can be used to declaratively opt out of prerendering and indicate a particular component should not be cached.
|
||||
|
||||
```jsx
|
||||
import { unstable_noStore as noStore } from 'next/cache';
|
||||
|
||||
export default async function ServerComponent() {
|
||||
noStore();
|
||||
const result = await db.query(...);
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
> **Good to know**:
|
||||
>
|
||||
> - `unstable_noStore` is equivalent to `cache: 'no-store'` on a `fetch`
|
||||
> - `unstable_noStore` is preferred over `export const dynamic = 'force-dynamic'` as it is more granular and can be used on a per-component basis
|
||||
|
||||
- Using `unstable_noStore` inside [`unstable_cache`](/docs/app/api-reference/functions/unstable_cache) will not opt out of static generation. Instead, it will defer to the cache configuration to determine whether to cache the result or not.
|
||||
|
||||
## Usage
|
||||
|
||||
If you prefer not to pass additional options to `fetch`, like `cache: 'no-store'`, `next: { revalidate: 0 }` or in cases where `fetch` is not available, you can use `noStore()` as a replacement for all of these use cases.
|
||||
|
||||
```jsx
|
||||
import { unstable_noStore as noStore } from 'next/cache';
|
||||
|
||||
export default async function ServerComponent() {
|
||||
noStore();
|
||||
const result = await db.query(...);
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
## Version History
|
||||
|
||||
| Version | Changes |
|
||||
| --------- | ----------------------------------------------- |
|
||||
| `v15.0.0` | `unstable_noStore` deprecated for `connection`. |
|
||||
| `v14.0.0` | `unstable_noStore` introduced. |
|
||||
Generated
Vendored
+65
@@ -0,0 +1,65 @@
|
||||
---
|
||||
title: unstable_rethrow
|
||||
description: API Reference for the unstable_rethrow function.
|
||||
version: unstable
|
||||
---
|
||||
|
||||
`unstable_rethrow` can be used to avoid catching internal errors thrown by Next.js when attempting to handle errors thrown in your application code.
|
||||
|
||||
For example, calling the `notFound` function will throw an internal Next.js error and render the [`not-found.js`](/docs/app/api-reference/file-conventions/not-found) component. However, if used inside the `try` block of a `try/catch` statement, the error will be caught, preventing `not-found.js` from rendering:
|
||||
|
||||
```tsx filename="@/app/ui/component.tsx"
|
||||
import { notFound } from 'next/navigation'
|
||||
|
||||
export default async function Page() {
|
||||
try {
|
||||
const post = await fetch('https://.../posts/1').then((res) => {
|
||||
if (res.status === 404) notFound()
|
||||
if (!res.ok) throw new Error(res.statusText)
|
||||
return res.json()
|
||||
})
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can use `unstable_rethrow` API to re-throw the internal error and continue with the expected behavior:
|
||||
|
||||
```tsx filename="@/app/ui/component.tsx"
|
||||
import { notFound, unstable_rethrow } from 'next/navigation'
|
||||
|
||||
export default async function Page() {
|
||||
try {
|
||||
const post = await fetch('https://.../posts/1').then((res) => {
|
||||
if (res.status === 404) notFound()
|
||||
if (!res.ok) throw new Error(res.statusText)
|
||||
return res.json()
|
||||
})
|
||||
} catch (err) {
|
||||
unstable_rethrow(err)
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The following Next.js APIs rely on throwing an error which should be rethrown and handled by Next.js itself:
|
||||
|
||||
- [`notFound()`](/docs/app/api-reference/functions/not-found)
|
||||
- [`redirect()`](/docs/app/guides/redirecting#redirect-function)
|
||||
- [`permanentRedirect()`](/docs/app/guides/redirecting#permanentredirect-function)
|
||||
|
||||
If a route segment is marked to throw an error unless it's static, a Request-time API call will also throw an error that should similarly not be caught by the developer. Note that Partial Prerendering (PPR) affects this behavior as well. These APIs are:
|
||||
|
||||
- [`cookies`](/docs/app/api-reference/functions/cookies)
|
||||
- [`headers`](/docs/app/api-reference/functions/headers)
|
||||
- [`searchParams`](/docs/app/api-reference/file-conventions/page#searchparams-optional)
|
||||
- `fetch(..., { cache: 'no-store' })`
|
||||
- `fetch(..., { next: { revalidate: 0 } })`
|
||||
|
||||
> **Good to know**:
|
||||
>
|
||||
> - This method should be called at the top of the catch block, passing the error object as its only argument. It can also be used within a `.catch` handler of a promise.
|
||||
> - You may be able to avoid using `unstable_rethrow` if you encapsulate your API calls that throw and let the **caller** handle the exception.
|
||||
> - Only use `unstable_rethrow` if your caught exceptions may include both application errors and framework-controlled exceptions (like `redirect()` or `notFound()`).
|
||||
> - Any resource cleanup (like clearing intervals, timers, etc) would have to either happen prior to the call to `unstable_rethrow` or within a `finally` block.
|
||||
Generated
Vendored
+153
@@ -0,0 +1,153 @@
|
||||
---
|
||||
title: updateTag
|
||||
description: API Reference for the updateTag function.
|
||||
---
|
||||
|
||||
`updateTag` allows you to update cached data on-demand for a specific cache tag from within [Server Actions](/docs/app/getting-started/mutating-data).
|
||||
|
||||
This function is designed for **read-your-own-writes** scenarios, where a user makes a change (like creating a post), and the UI immediately shows the change, rather than stale data.
|
||||
|
||||
## Usage
|
||||
|
||||
`updateTag` can **only** be called from within [Server Actions](/docs/app/getting-started/mutating-data). It cannot be used in Route Handlers, Client Components, or any other context.
|
||||
|
||||
If you need to invalidate cache tags in Route Handlers or other contexts, use [`revalidateTag`](/docs/app/api-reference/functions/revalidateTag) instead.
|
||||
|
||||
> **Good to know**: `updateTag` immediately expires the cached data for the specified tag. The next request will wait to fetch fresh data rather than serving stale content from the cache, ensuring users see their changes immediately.
|
||||
|
||||
## Parameters
|
||||
|
||||
```tsx
|
||||
updateTag(tag: string): void;
|
||||
```
|
||||
|
||||
- `tag`: A string representing the cache tag associated with the data you want to update. Must not exceed 256 characters. This value is case-sensitive.
|
||||
|
||||
Tags must first be assigned to cached data. You can do this in two ways:
|
||||
|
||||
- Using the [`next.tags`](/docs/app/api-reference/functions/fetch) option with `fetch` for caching external API requests:
|
||||
|
||||
```tsx
|
||||
fetch(url, { next: { tags: ['posts'] } })
|
||||
```
|
||||
|
||||
- Using [`cacheTag`](/docs/app/api-reference/functions/cacheTag) inside cached functions or components with the `'use cache'` directive:
|
||||
|
||||
```tsx
|
||||
import { cacheTag } from 'next/cache'
|
||||
|
||||
async function getData() {
|
||||
'use cache'
|
||||
cacheTag('posts')
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
## Returns
|
||||
|
||||
`updateTag` does not return a value.
|
||||
|
||||
## Differences from revalidateTag
|
||||
|
||||
While both `updateTag` and `revalidateTag` invalidate cached data, they serve different purposes:
|
||||
|
||||
- **`updateTag`**:
|
||||
- Can only be used in Server Actions
|
||||
- Next request waits for fresh data (no stale content served)
|
||||
- Designed for read-your-own-writes scenarios
|
||||
|
||||
- **`revalidateTag`**:
|
||||
- Can be used in Server Actions and Route Handlers
|
||||
- With `profile="max"` (recommended): Serves cached data while fetching fresh data in the background (stale-while-revalidate)
|
||||
- With custom profile: Can be configured to any cache life profile for advanced usage
|
||||
- Without profile: legacy behavior which is equivalent to `updateTag`
|
||||
|
||||
## Examples
|
||||
|
||||
### Server Action with Read-Your-Own-Writes
|
||||
|
||||
```ts filename="app/actions.ts" switcher
|
||||
'use server'
|
||||
|
||||
import { updateTag } from 'next/cache'
|
||||
import { redirect } from 'next/navigation'
|
||||
|
||||
export async function createPost(formData: FormData) {
|
||||
const title = formData.get('title')
|
||||
const content = formData.get('content')
|
||||
|
||||
// Create the post in your database
|
||||
const post = await db.post.create({
|
||||
data: { title, content },
|
||||
})
|
||||
|
||||
// Invalidate cache tags so the new post is immediately visible
|
||||
// 'posts' tag: affects any page that displays a list of posts
|
||||
updateTag('posts')
|
||||
// 'post-{id}' tag: affects the individual post detail page
|
||||
updateTag(`post-${post.id}`)
|
||||
|
||||
// Redirect to the new post - user will see fresh data, not cached
|
||||
redirect(`/posts/${post.id}`)
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="app/actions.js" switcher
|
||||
'use server'
|
||||
|
||||
import { updateTag } from 'next/cache'
|
||||
import { redirect } from 'next/navigation'
|
||||
|
||||
export async function createPost(formData) {
|
||||
const title = formData.get('title')
|
||||
const content = formData.get('content')
|
||||
|
||||
// Create the post in your database
|
||||
const post = await db.post.create({
|
||||
data: { title, content },
|
||||
})
|
||||
|
||||
// Invalidate cache tags so the new post is immediately visible
|
||||
// 'posts' tag: affects any page that displays a list of posts
|
||||
updateTag('posts')
|
||||
// 'post-{id}' tag: affects the individual post detail page
|
||||
updateTag(`post-${post.id}`)
|
||||
|
||||
// Redirect to the new post - user will see fresh data, not cached
|
||||
redirect(`/posts/${post.id}`)
|
||||
}
|
||||
```
|
||||
|
||||
### Error when used outside Server Actions
|
||||
|
||||
```ts filename="app/api/posts/route.ts" switcher
|
||||
import { updateTag } from 'next/cache'
|
||||
|
||||
export async function POST() {
|
||||
// This will throw an error
|
||||
updateTag('posts')
|
||||
// Error: updateTag can only be called from within a Server Action
|
||||
|
||||
// Use revalidateTag instead in Route Handlers
|
||||
revalidateTag('posts', 'max')
|
||||
}
|
||||
```
|
||||
|
||||
## When to use updateTag
|
||||
|
||||
Use `updateTag` when:
|
||||
|
||||
- You're in a Server Action
|
||||
- You need immediate cache invalidation for read-your-own-writes
|
||||
- You want to ensure the next request sees updated data
|
||||
|
||||
Use `revalidateTag` instead when:
|
||||
|
||||
- You're in a Route Handler or other non-action context
|
||||
- You want stale-while-revalidate semantics
|
||||
- You're building a webhook or API endpoint for cache invalidation
|
||||
|
||||
## Related
|
||||
|
||||
- [`revalidateTag`](/docs/app/api-reference/functions/revalidateTag) - For invalidating tags in Route Handlers
|
||||
- [`revalidatePath`](/docs/app/api-reference/functions/revalidatePath) - For invalidating specific paths
|
||||
Generated
Vendored
+242
@@ -0,0 +1,242 @@
|
||||
---
|
||||
title: useLinkStatus
|
||||
description: API Reference for the useLinkStatus hook.
|
||||
related:
|
||||
title: Next Steps
|
||||
description: Learn more about the features mentioned in this page by reading the API Reference.
|
||||
links:
|
||||
- app/api-reference/components/link
|
||||
- app/api-reference/file-conventions/loading
|
||||
---
|
||||
|
||||
The `useLinkStatus` hook lets you track the **pending** state of a `<Link>`. Use it for subtle, inline feedback, for example a shimmer effect over the clicked link, while navigation completes. Prefer route-level fallbacks with `loading.js`, and prefetching for instant transitions.
|
||||
|
||||
`useLinkStatus` is useful when:
|
||||
|
||||
- [Prefetching](/docs/app/getting-started/linking-and-navigating#prefetching) is disabled or in progress meaning navigation is blocked.
|
||||
- The destination route is dynamic **and** doesn't include a [`loading.js`](/docs/app/api-reference/file-conventions/loading) file that would allow an instant navigation.
|
||||
|
||||
```tsx filename="app/hint.tsx" switcher
|
||||
'use client'
|
||||
|
||||
import Link from 'next/link'
|
||||
import { useLinkStatus } from 'next/link'
|
||||
|
||||
function Hint() {
|
||||
const { pending } = useLinkStatus()
|
||||
return (
|
||||
<span aria-hidden className={`link-hint ${pending ? 'is-pending' : ''}`} />
|
||||
)
|
||||
}
|
||||
|
||||
export default function Header() {
|
||||
return (
|
||||
<header>
|
||||
<Link href="/dashboard" prefetch={false}>
|
||||
<span className="label">Dashboard</span> <Hint />
|
||||
</Link>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/hint.js" switcher
|
||||
'use client'
|
||||
|
||||
import Link from 'next/link'
|
||||
import { useLinkStatus } from 'next/link'
|
||||
|
||||
function Hint() {
|
||||
const { pending } = useLinkStatus()
|
||||
return (
|
||||
<span aria-hidden className={`link-hint ${pending ? 'is-pending' : ''}`} />
|
||||
)
|
||||
}
|
||||
|
||||
export default function Header() {
|
||||
return (
|
||||
<header>
|
||||
<Link href="/dashboard" prefetch={false}>
|
||||
<span className="label">Dashboard</span> <Hint />
|
||||
</Link>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
> **Good to know**:
|
||||
>
|
||||
> - `useLinkStatus` must be used within a descendant component of a `Link` component
|
||||
> - The hook is most useful when `prefetch={false}` is set on the `Link` component
|
||||
> - If the linked route has been prefetched, the pending state will be skipped
|
||||
> - When clicking multiple links in quick succession, only the last link's pending state is shown
|
||||
> - This hook is not supported in the Pages Router and always returns `{ pending: false }`
|
||||
> - Inline indicators can easily introduce layout shifts. Prefer a fixed-size, always-rendered hint element and toggle its opacity, or use an animation.
|
||||
|
||||
## You might not need `useLinkStatus`
|
||||
|
||||
Before adding inline feedback, consider if:
|
||||
|
||||
- The destination is static and prefetched in production, so the pending phase may be skipped.
|
||||
- The route has a `loading.js` file, enabling instant transitions with a route-level fallback.
|
||||
|
||||
Navigation is typically fast. Use `useLinkStatus` as a quick patch when you identify a slow transition, then iterate to fix the root cause with prefetching or a `loading.js` fallback.
|
||||
|
||||
## Parameters
|
||||
|
||||
```tsx
|
||||
const { pending } = useLinkStatus()
|
||||
```
|
||||
|
||||
`useLinkStatus` does not take any parameters.
|
||||
|
||||
## Returns
|
||||
|
||||
`useLinkStatus` returns an object with a single property:
|
||||
|
||||
| Property | Type | Description |
|
||||
| -------- | ------- | -------------------------------------------- |
|
||||
| pending | boolean | `true` before history updates, `false` after |
|
||||
|
||||
## Example
|
||||
|
||||
### Inline link hint
|
||||
|
||||
Add a subtle, fixed-size hint that doesn’t affect layout to confirm a click when prefetching hasn’t completed.
|
||||
|
||||
```tsx filename="app/components/loading-indicator.tsx" switcher
|
||||
'use client'
|
||||
|
||||
import { useLinkStatus } from 'next/link'
|
||||
|
||||
export default function LoadingIndicator() {
|
||||
const { pending } = useLinkStatus()
|
||||
return (
|
||||
<span aria-hidden className={`link-hint ${pending ? 'is-pending' : ''}`} />
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/components/loading-indicator.js" switcher
|
||||
'use client'
|
||||
|
||||
import { useLinkStatus } from 'next/link'
|
||||
|
||||
export default function LoadingIndicator() {
|
||||
const { pending } = useLinkStatus()
|
||||
return (
|
||||
<span aria-hidden className={`link-hint ${pending ? 'is-pending' : ''}`} />
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```tsx filename="app/shop/layout.tsx" switcher
|
||||
import Link from 'next/link'
|
||||
import LoadingIndicator from './components/loading-indicator'
|
||||
|
||||
const links = [
|
||||
{ href: '/shop/electronics', label: 'Electronics' },
|
||||
{ href: '/shop/clothing', label: 'Clothing' },
|
||||
{ href: '/shop/books', label: 'Books' },
|
||||
]
|
||||
|
||||
function Menubar() {
|
||||
return (
|
||||
<div>
|
||||
{links.map((link) => (
|
||||
<Link key={link.label} href={link.href}>
|
||||
<span className="label">{link.label}</span> <LoadingIndicator />
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<div>
|
||||
<Menubar />
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/shop/layout.js" switcher
|
||||
import Link from 'next/link'
|
||||
import LoadingIndicator from './components/loading-indicator'
|
||||
|
||||
const links = [
|
||||
{ href: '/shop/electronics', label: 'Electronics' },
|
||||
{ href: '/shop/clothing', label: 'Clothing' },
|
||||
{ href: '/shop/books', label: 'Books' },
|
||||
]
|
||||
|
||||
function Menubar() {
|
||||
return (
|
||||
<div>
|
||||
{links.map((link) => (
|
||||
<Link key={link.label} href={link.href}>
|
||||
<span className="label">{link.label}</span> <LoadingIndicator />
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default function Layout({ children }) {
|
||||
return (
|
||||
<div>
|
||||
<Menubar />
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Gracefully handling fast navigation
|
||||
|
||||
If the navigation to a new route is fast, users may see an unnecessary flash of the hint. One way to improve the user experience and only show the hint when the navigation takes time to complete is to add an initial animation delay (e.g. 100ms) and start the animation as invisible (e.g. `opacity: 0`).
|
||||
|
||||
```css filename="app/styles/global.css"
|
||||
.link-hint {
|
||||
display: inline-block;
|
||||
width: 0.6em;
|
||||
height: 0.6em;
|
||||
margin-left: 0.25rem;
|
||||
border-radius: 9999px;
|
||||
background: currentColor;
|
||||
opacity: 0;
|
||||
visibility: hidden; /* reserve space without showing the hint */
|
||||
}
|
||||
|
||||
.link-hint.is-pending {
|
||||
/* Animation 1: fade in after 100ms and keep final opacity */
|
||||
/* Animation 2: subtle pulsing while pending */
|
||||
visibility: visible;
|
||||
animation-name: fadeIn, pulse;
|
||||
animation-duration: 200ms, 1s;
|
||||
/* Appear only if navigation actually takes time */
|
||||
animation-delay: 100ms, 100ms;
|
||||
animation-timing-function: ease, ease-in-out;
|
||||
animation-iteration-count: 1, infinite;
|
||||
animation-fill-mode: forwards, none;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
to {
|
||||
opacity: 0.35;
|
||||
}
|
||||
}
|
||||
@keyframes pulse {
|
||||
50% {
|
||||
opacity: 0.15;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Version History
|
||||
|
||||
| Version | Changes |
|
||||
| --------- | --------------------------- |
|
||||
| `v15.3.0` | `useLinkStatus` introduced. |
|
||||
Generated
Vendored
+73
@@ -0,0 +1,73 @@
|
||||
---
|
||||
title: useParams
|
||||
description: API Reference for the useParams hook.
|
||||
---
|
||||
|
||||
`useParams` is a **Client Component** hook that lets you read a route's [dynamic params](/docs/app/api-reference/file-conventions/dynamic-routes) filled in by the current URL.
|
||||
|
||||
```tsx filename="app/example-client-component.tsx" switcher
|
||||
'use client'
|
||||
|
||||
import { useParams } from 'next/navigation'
|
||||
|
||||
export default function ExampleClientComponent() {
|
||||
const params = useParams<{ tag: string; item: string }>()
|
||||
|
||||
// Route -> /shop/[tag]/[item]
|
||||
// URL -> /shop/shoes/nike-air-max-97
|
||||
// `params` -> { tag: 'shoes', item: 'nike-air-max-97' }
|
||||
console.log(params)
|
||||
|
||||
return '...'
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/example-client-component.js" switcher
|
||||
'use client'
|
||||
|
||||
import { useParams } from 'next/navigation'
|
||||
|
||||
export default function ExampleClientComponent() {
|
||||
const params = useParams()
|
||||
|
||||
// Route -> /shop/[tag]/[item]
|
||||
// URL -> /shop/shoes/nike-air-max-97
|
||||
// `params` -> { tag: 'shoes', item: 'nike-air-max-97' }
|
||||
console.log(params)
|
||||
|
||||
return '...'
|
||||
}
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
```tsx
|
||||
const params = useParams()
|
||||
```
|
||||
|
||||
`useParams` does not take any parameters.
|
||||
|
||||
## Returns
|
||||
|
||||
`useParams` returns an object containing the current route's filled in [dynamic parameters](/docs/app/api-reference/file-conventions/dynamic-routes).
|
||||
|
||||
- Each property in the object is an active dynamic segment.
|
||||
- The properties name is the segment's name, and the properties value is what the segment is filled in with.
|
||||
- The properties value will either be a `string` or array of `string`'s depending on the [type of dynamic segment](/docs/app/api-reference/file-conventions/dynamic-routes).
|
||||
- If the route contains no dynamic parameters, `useParams` returns an empty object.
|
||||
- If used in Pages Router, `useParams` will return `null` on the initial render and updates with properties following the rules above once the router is ready.
|
||||
|
||||
For example:
|
||||
|
||||
| Route | URL | `useParams()` |
|
||||
| ------------------------------- | ----------- | ------------------------- |
|
||||
| `app/shop/page.js` | `/shop` | `{}` |
|
||||
| `app/shop/[slug]/page.js` | `/shop/1` | `{ slug: '1' }` |
|
||||
| `app/shop/[tag]/[item]/page.js` | `/shop/1/2` | `{ tag: '1', item: '2' }` |
|
||||
| `app/shop/[...slug]/page.js` | `/shop/1/2` | `{ slug: ['1', '2'] }` |
|
||||
|
||||
## Version History
|
||||
|
||||
| Version | Changes |
|
||||
| --------- | ----------------------- |
|
||||
| `v13.3.0` | `useParams` introduced. |
|
||||
Generated
Vendored
+156
@@ -0,0 +1,156 @@
|
||||
---
|
||||
title: usePathname
|
||||
description: API Reference for the usePathname hook.
|
||||
---
|
||||
|
||||
`usePathname` is a **Client Component** hook that lets you read the current URL's **pathname**.
|
||||
|
||||
> **Good to know**: When [`cacheComponents`](/docs/app/api-reference/config/next-config-js/cacheComponents) is enabled `usePathname` may require a `Suspense` boundary around it if your route has a dynamic param. If you use `generateStaticParams` the `Suspense` boundary is optional
|
||||
|
||||
```tsx filename="app/example-client-component.tsx" switcher
|
||||
'use client'
|
||||
|
||||
import { usePathname } from 'next/navigation'
|
||||
|
||||
export default function ExampleClientComponent() {
|
||||
const pathname = usePathname()
|
||||
return <p>Current pathname: {pathname}</p>
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/example-client-component.js" switcher
|
||||
'use client'
|
||||
|
||||
import { usePathname } from 'next/navigation'
|
||||
|
||||
export default function ExampleClientComponent() {
|
||||
const pathname = usePathname()
|
||||
return <p>Current pathname: {pathname}</p>
|
||||
}
|
||||
```
|
||||
|
||||
`usePathname` intentionally requires using a [Client Component](/docs/app/getting-started/server-and-client-components). It's important to note Client Components are not a de-optimization. They are an integral part of the [Server Components](/docs/app/getting-started/server-and-client-components) architecture.
|
||||
|
||||
For example, a Client Component with `usePathname` will be rendered into HTML on the initial page load. When navigating to a new route, this component does not need to be re-fetched. Instead, the component is downloaded once (in the client JavaScript bundle), and re-renders based on the current state.
|
||||
|
||||
> **Good to know**:
|
||||
>
|
||||
> - Reading the current URL from a [Server Component](/docs/app/getting-started/server-and-client-components) is not supported. This design is intentional to support layout state being preserved across page navigations.
|
||||
> - If your page is being statically prerendered and your app has [rewrites](/docs/app/api-reference/config/next-config-js/rewrites) in `next.config` or a [Proxy](/docs/app/api-reference/file-conventions/proxy) file, reading the pathname with `usePathname()` can result in hydration mismatch errors—because the initial value comes from the server and may not match the actual browser pathname after routing. See our [example](#avoid-hydration-mismatch-with-rewrites) for a way to mitigate this issue.
|
||||
|
||||
<details>
|
||||
|
||||
<summary>Compatibility with Pages Router</summary>
|
||||
|
||||
If you have components that use `usePathname` and they are imported into routes within the Pages Router, be aware that `usePathname` may return `null` if the router is not yet initialized. This can occur in cases such as [fallback routes](/docs/pages/api-reference/functions/get-static-paths#fallback-true) or during [Automatic Static Optimization](https://nextjs.org/docs/pages/building-your-application/rendering/static#automatic-static-optimization) in the Pages Router.
|
||||
|
||||
To enhance compatibility between routing systems, if your project contains both an `app` and a `pages` directory, Next.js will automatically adjust the return type of `usePathname`.
|
||||
|
||||
</details>
|
||||
|
||||
## Parameters
|
||||
|
||||
```tsx
|
||||
const pathname = usePathname()
|
||||
```
|
||||
|
||||
`usePathname` does not take any parameters.
|
||||
|
||||
## Returns
|
||||
|
||||
`usePathname` returns a string of the current URL's pathname. For example:
|
||||
|
||||
| URL | Returned value |
|
||||
| ------------------- | --------------------- |
|
||||
| `/` | `'/'` |
|
||||
| `/dashboard` | `'/dashboard'` |
|
||||
| `/dashboard?v=2` | `'/dashboard'` |
|
||||
| `/blog/hello-world` | `'/blog/hello-world'` |
|
||||
|
||||
## Examples
|
||||
|
||||
### Do something in response to a route change
|
||||
|
||||
```tsx filename="app/example-client-component.tsx" switcher
|
||||
'use client'
|
||||
|
||||
import { useEffect } from 'react'
|
||||
import { usePathname, useSearchParams } from 'next/navigation'
|
||||
|
||||
function ExampleClientComponent() {
|
||||
const pathname = usePathname()
|
||||
const searchParams = useSearchParams()
|
||||
useEffect(() => {
|
||||
// Do something here...
|
||||
}, [pathname, searchParams])
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/example-client-component.js" switcher
|
||||
'use client'
|
||||
|
||||
import { useEffect } from 'react'
|
||||
import { usePathname, useSearchParams } from 'next/navigation'
|
||||
|
||||
function ExampleClientComponent() {
|
||||
const pathname = usePathname()
|
||||
const searchParams = useSearchParams()
|
||||
useEffect(() => {
|
||||
// Do something here...
|
||||
}, [pathname, searchParams])
|
||||
}
|
||||
```
|
||||
|
||||
### Avoid hydration mismatch with rewrites
|
||||
|
||||
When a page is prerendered, the HTML is generated for the source pathname. If the page is then reached through a rewrite using `next.config` or `Proxy`, the browser URL may differ, and `usePathname()` will read the rewritten pathname on the client.
|
||||
|
||||
To avoid hydration mismatches, design the UI so that only a small, isolated part depends on the client pathname. Render a stable fallback on the server and update that part after mount.
|
||||
|
||||
```tsx filename="app/example-client-component.tsx" switcher
|
||||
'use client'
|
||||
|
||||
import { useEffect, useState } from 'react'
|
||||
import { usePathname } from 'next/navigation'
|
||||
|
||||
export default function PathnameBadge() {
|
||||
const pathname = usePathname()
|
||||
const [clientPathname, setClientPathname] = useState('')
|
||||
|
||||
useEffect(() => {
|
||||
setClientPathname(pathname)
|
||||
}, [pathname])
|
||||
|
||||
return (
|
||||
<p>
|
||||
Current pathname: <span>{clientPathname}</span>
|
||||
</p>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/example-client-component.js" switcher
|
||||
'use client'
|
||||
|
||||
import { useEffect, useState } from 'react'
|
||||
import { usePathname } from 'next/navigation'
|
||||
|
||||
export default function PathnameBadge() {
|
||||
const pathname = usePathname()
|
||||
const [clientPathname, setClientPathname] = useState('')
|
||||
|
||||
useEffect(() => {
|
||||
setClientPathname(pathname)
|
||||
}, [pathname])
|
||||
|
||||
return (
|
||||
<p>
|
||||
Current pathname: <span>{clientPathname}</span>
|
||||
</p>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
| Version | Changes |
|
||||
| --------- | ------------------------- |
|
||||
| `v13.0.0` | `usePathname` introduced. |
|
||||
Generated
Vendored
+250
@@ -0,0 +1,250 @@
|
||||
---
|
||||
title: useReportWebVitals
|
||||
description: API Reference for the useReportWebVitals function.
|
||||
---
|
||||
|
||||
{/* The content of this doc is shared between the app and pages router. You can use the `<PagesOnly>Content</PagesOnly>` component to add content that is specific to the Pages Router. Any shared content should not be wrapped in a component. */}
|
||||
|
||||
The `useReportWebVitals` hook allows you to report [Core Web Vitals](https://web.dev/vitals/), and can be used in combination with your analytics service.
|
||||
|
||||
New functions passed to `useReportWebVitals` are called with the available metrics up to that point. To prevent reporting duplicated data, ensure that the callback function reference does not change (as shown in the code examples below).
|
||||
|
||||
<PagesOnly>
|
||||
|
||||
```jsx filename="pages/_app.js"
|
||||
import { useReportWebVitals } from 'next/web-vitals'
|
||||
|
||||
const logWebVitals = (metric) => {
|
||||
console.log(metric)
|
||||
}
|
||||
|
||||
function MyApp({ Component, pageProps }) {
|
||||
useReportWebVitals(logWebVitals)
|
||||
|
||||
return <Component {...pageProps} />
|
||||
}
|
||||
```
|
||||
|
||||
</PagesOnly>
|
||||
|
||||
<AppOnly>
|
||||
|
||||
```jsx filename="app/_components/web-vitals.js"
|
||||
'use client'
|
||||
|
||||
import { useReportWebVitals } from 'next/web-vitals'
|
||||
|
||||
const logWebVitals = (metric) => {
|
||||
console.log(metric)
|
||||
}
|
||||
|
||||
export function WebVitals() {
|
||||
useReportWebVitals(logWebVitals)
|
||||
|
||||
return null
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/layout.js"
|
||||
import { WebVitals } from './_components/web-vitals'
|
||||
|
||||
export default function Layout({ children }) {
|
||||
return (
|
||||
<html>
|
||||
<body>
|
||||
<WebVitals />
|
||||
{children}
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
> Since the `useReportWebVitals` hook requires the `'use client'` directive, the most performant approach is to create a separate component that the root layout imports. This confines the client boundary exclusively to the `WebVitals` component.
|
||||
|
||||
</AppOnly>
|
||||
|
||||
## useReportWebVitals
|
||||
|
||||
The `metric` object passed as the hook's argument consists of a number of properties:
|
||||
|
||||
- `id`: Unique identifier for the metric in the context of the current page load
|
||||
- `name`: The name of the performance metric. Possible values include names of [Web Vitals](#web-vitals) metrics (TTFB, FCP, LCP, FID, CLS) specific to a web application.
|
||||
- `delta`: The difference between the current value and the previous value of the metric. The value is typically in milliseconds and represents the change in the metric's value over time.
|
||||
- `entries`: An array of [Performance Entries](https://developer.mozilla.org/docs/Web/API/PerformanceEntry) associated with the metric. These entries provide detailed information about the performance events related to the metric.
|
||||
- `navigationType`: Indicates the navigation type that triggered metric collection. Values are derived from [PerformanceNavigationTiming.type](https://developer.mozilla.org/docs/Web/API/PerformanceNavigationTiming/type) and may include `"navigate"`, `"reload"`, `"prerender"`, `"back-forward"` (normalized from `"back_forward"`), `"back-forward-cache"` (BFCache restore), and `"restore"` (page restored after discard).
|
||||
- `rating`: A qualitative rating of the metric value, providing an assessment of the performance. Possible values are `"good"`, `"needs-improvement"`, and `"poor"`. The rating is typically determined by comparing the metric value against predefined thresholds that indicate acceptable or suboptimal performance.
|
||||
- `value`: The actual value or duration of the performance entry, typically in milliseconds. The value provides a quantitative measure of the performance aspect being tracked by the metric. The source of the value depends on the specific metric being measured and can come from various [Performance API](https://developer.mozilla.org/docs/Web/API/Performance_API)s.
|
||||
|
||||
## Web Vitals
|
||||
|
||||
[Web Vitals](https://web.dev/vitals/) are a set of useful metrics that aim to capture the user
|
||||
experience of a web page. The following web vitals are all included:
|
||||
|
||||
- [Time to First Byte](https://developer.mozilla.org/docs/Glossary/Time_to_first_byte) (TTFB)
|
||||
- [First Contentful Paint](https://developer.mozilla.org/docs/Glossary/First_contentful_paint) (FCP)
|
||||
- [Largest Contentful Paint](https://web.dev/lcp/) (LCP)
|
||||
- [First Input Delay](https://web.dev/fid/) (FID)
|
||||
- [Cumulative Layout Shift](https://web.dev/cls/) (CLS)
|
||||
- [Interaction to Next Paint](https://web.dev/inp/) (INP)
|
||||
|
||||
You can handle all the results of these metrics using the `name` property.
|
||||
|
||||
<PagesOnly>
|
||||
|
||||
```jsx filename="pages/_app.js"
|
||||
import { useReportWebVitals } from 'next/web-vitals'
|
||||
|
||||
const handleWebVitals = (metric) => {
|
||||
switch (metric.name) {
|
||||
case 'FCP': {
|
||||
// handle FCP results
|
||||
}
|
||||
case 'LCP': {
|
||||
// handle LCP results
|
||||
}
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
function MyApp({ Component, pageProps }) {
|
||||
useReportWebVitals(handleWebVitals)
|
||||
|
||||
return <Component {...pageProps} />
|
||||
}
|
||||
```
|
||||
|
||||
</PagesOnly>
|
||||
|
||||
<AppOnly>
|
||||
|
||||
```tsx filename="app/components/web-vitals.tsx" switcher
|
||||
'use client'
|
||||
|
||||
import { useReportWebVitals } from 'next/web-vitals'
|
||||
|
||||
type ReportWebVitalsCallback = Parameters<typeof useReportWebVitals>[0]
|
||||
|
||||
const handleWebVitals: ReportWebVitalsCallback = (metric) => {
|
||||
switch (metric.name) {
|
||||
case 'FCP': {
|
||||
// handle FCP results
|
||||
}
|
||||
case 'LCP': {
|
||||
// handle LCP results
|
||||
}
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
export function WebVitals() {
|
||||
useReportWebVitals(handleWebVitals)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/components/web-vitals.js" switcher
|
||||
'use client'
|
||||
|
||||
import { useReportWebVitals } from 'next/web-vitals'
|
||||
|
||||
const handleWebVitals = (metric) => {
|
||||
switch (metric.name) {
|
||||
case 'FCP': {
|
||||
// handle FCP results
|
||||
}
|
||||
case 'LCP': {
|
||||
// handle LCP results
|
||||
}
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
export function WebVitals() {
|
||||
useReportWebVitals(handleWebVitals)
|
||||
}
|
||||
```
|
||||
|
||||
</AppOnly>
|
||||
|
||||
<PagesOnly>
|
||||
|
||||
## Custom Metrics
|
||||
|
||||
In addition to the core metrics listed above, there are some additional custom metrics that
|
||||
measure the time it takes for the page to hydrate and render:
|
||||
|
||||
- `Next.js-hydration`: Length of time it takes for the page to start and finish hydrating (in ms)
|
||||
- `Next.js-route-change-to-render`: Length of time it takes for a page to start rendering after a
|
||||
route change (in ms)
|
||||
- `Next.js-render`: Length of time it takes for a page to finish render after a route change (in ms)
|
||||
|
||||
You can handle all the results of these metrics separately:
|
||||
|
||||
```jsx filename="pages/_app.js"
|
||||
import { useReportWebVitals } from 'next/web-vitals'
|
||||
|
||||
function handleCustomMetrics(metric) {
|
||||
switch (metric.name) {
|
||||
case 'Next.js-hydration':
|
||||
// handle hydration results
|
||||
break
|
||||
case 'Next.js-route-change-to-render':
|
||||
// handle route-change to render results
|
||||
break
|
||||
case 'Next.js-render':
|
||||
// handle render results
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
function MyApp({ Component, pageProps }) {
|
||||
useReportWebVitals(handleCustomMetrics)
|
||||
|
||||
return <Component {...pageProps} />
|
||||
}
|
||||
```
|
||||
|
||||
These metrics work in all browsers that support the [User Timing API](https://caniuse.com/#feat=user-timing).
|
||||
|
||||
</PagesOnly>
|
||||
|
||||
## Sending results to external systems
|
||||
|
||||
You can send results to any endpoint to measure and track
|
||||
real user performance on your site. For example:
|
||||
|
||||
```js
|
||||
function postWebVitals(metric) {
|
||||
const body = JSON.stringify(metric)
|
||||
const url = 'https://example.com/analytics'
|
||||
|
||||
// Use `navigator.sendBeacon()` if available, falling back to `fetch()`.
|
||||
if (navigator.sendBeacon) {
|
||||
navigator.sendBeacon(url, body)
|
||||
} else {
|
||||
fetch(url, { body, method: 'POST', keepalive: true })
|
||||
}
|
||||
}
|
||||
|
||||
useReportWebVitals(postWebVitals)
|
||||
```
|
||||
|
||||
> **Good to know**: If you use [Google Analytics](https://analytics.google.com/analytics/web/), using the
|
||||
> `id` value can allow you to construct metric distributions manually (to calculate percentiles,
|
||||
> etc.)
|
||||
|
||||
> ```js
|
||||
> useReportWebVitals(metric => {
|
||||
> // Use `window.gtag` if you initialized Google Analytics as this example:
|
||||
> // https://github.com/vercel/next.js/blob/canary/examples/with-google-analytics
|
||||
> window.gtag('event', metric.name, {
|
||||
> value: Math.round(metric.name === 'CLS' ? metric.value * 1000 : metric.value), // values must be integers
|
||||
> event_label: metric.id, // id unique to current page load
|
||||
> non_interaction: true, // avoids affecting bounce rate.
|
||||
> });
|
||||
> }
|
||||
> ```
|
||||
>
|
||||
> Read more about [sending results to Google Analytics](https://github.com/GoogleChrome/web-vitals#send-the-results-to-google-analytics).
|
||||
Generated
Vendored
+164
@@ -0,0 +1,164 @@
|
||||
---
|
||||
title: useRouter
|
||||
description: API reference for the useRouter hook.
|
||||
---
|
||||
|
||||
The `useRouter` hook allows you to programmatically change routes inside [Client Components](/docs/app/getting-started/server-and-client-components).
|
||||
|
||||
> **Recommendation:** Use the [`<Link>` component](/docs/app/api-reference/components/link) for navigation unless you have a specific requirement for using `useRouter`.
|
||||
|
||||
```tsx filename="app/example-client-component.tsx" switcher
|
||||
'use client'
|
||||
|
||||
import { useRouter } from 'next/navigation'
|
||||
|
||||
export default function Page() {
|
||||
const router = useRouter()
|
||||
|
||||
return (
|
||||
<button type="button" onClick={() => router.push('/dashboard')}>
|
||||
Dashboard
|
||||
</button>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/example-client-component.js" switcher
|
||||
'use client'
|
||||
|
||||
import { useRouter } from 'next/navigation'
|
||||
|
||||
export default function Page() {
|
||||
const router = useRouter()
|
||||
|
||||
return (
|
||||
<button type="button" onClick={() => router.push('/dashboard')}>
|
||||
Dashboard
|
||||
</button>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## `useRouter()`
|
||||
|
||||
- `router.push(href: string, { scroll: boolean, transitionTypes: string[] })`: Perform a client-side navigation to the provided route. Adds a new entry into the [browser's history stack](https://developer.mozilla.org/docs/Web/API/History_API). The optional `transitionTypes` are passed to [`React.addTransitionType`](https://react.dev/reference/react/addTransitionType) inside the navigation Transition.
|
||||
- `router.replace(href: string, { scroll: boolean, transitionTypes: string[] })`: Perform a client-side navigation to the provided route without adding a new entry into the browser’s history stack. The optional `transitionTypes` are passed to [`React.addTransitionType`](https://react.dev/reference/react/addTransitionType) inside the navigation Transition.
|
||||
- `router.refresh()`: Refresh the current route. Making a new request to the server, re-fetching data requests, and re-rendering Server Components. The client will merge the updated React Server Component payload without losing unaffected client-side React (e.g. `useState`) or browser state (e.g. scroll position). This clears the [Client Cache](/docs/app/glossary#client-cache) for the current route, but does **not** invalidate the server-side cache. Use [`revalidatePath`](/docs/app/api-reference/functions/revalidatePath) or [`revalidateTag`](/docs/app/api-reference/functions/revalidateTag) to invalidate server-side cached data.
|
||||
- `router.prefetch(href: string, options?: { onInvalidate?: () => void })`: [Prefetch](/docs/app/getting-started/linking-and-navigating#prefetching) the provided route for faster client-side transitions. The optional `onInvalidate` callback is called when the [prefetched data becomes stale](/docs/app/guides/prefetching#extending-or-ejecting-link).
|
||||
- `router.back()`: Navigate back to the previous route in the browser’s history stack.
|
||||
- `router.forward()`: Navigate forwards to the next page in the browser’s history stack.
|
||||
|
||||
> **Good to know**:
|
||||
>
|
||||
> - You must not send untrusted or unsanitized URLs to `router.push` or `router.replace`, as this can open your site to cross-site scripting (XSS) vulnerabilities. For example, `javascript:` URLs sent to `router.push` or `router.replace` will be executed in the context of your page.
|
||||
> - The `<Link>` component automatically prefetch routes as they become visible in the viewport.
|
||||
> - `refresh()` could re-produce the same result if fetch requests are cached. Other Request-time APIs like `cookies` and `headers` could also change the response.
|
||||
> - The `onInvalidate` callback is called at most once per prefetch request. It signals when you may want to trigger a new prefetch for updated route data.
|
||||
|
||||
### Migrating from `next/router`
|
||||
|
||||
- The `useRouter` hook should be imported from `next/navigation` and not `next/router` when using the App Router
|
||||
- The `pathname` string has been removed and is replaced by [`usePathname()`](/docs/app/api-reference/functions/use-pathname)
|
||||
- The `query` object has been removed and is replaced by [`useSearchParams()`](/docs/app/api-reference/functions/use-search-params)
|
||||
- `router.events` has been replaced. [See below.](#router-events)
|
||||
|
||||
[View the full migration guide](/docs/app/guides/migrating/app-router-migration).
|
||||
|
||||
## Examples
|
||||
|
||||
### Router events
|
||||
|
||||
You can listen for page changes by composing other Client Component hooks like `usePathname` and `useSearchParams`.
|
||||
|
||||
```jsx filename="app/components/navigation-events.js"
|
||||
'use client'
|
||||
|
||||
import { useEffect } from 'react'
|
||||
import { usePathname, useSearchParams } from 'next/navigation'
|
||||
|
||||
export function NavigationEvents() {
|
||||
const pathname = usePathname()
|
||||
const searchParams = useSearchParams()
|
||||
|
||||
useEffect(() => {
|
||||
const url = `${pathname}?${searchParams}`
|
||||
console.log(url)
|
||||
// You can now use the current URL
|
||||
// ...
|
||||
}, [pathname, searchParams])
|
||||
|
||||
return '...'
|
||||
}
|
||||
```
|
||||
|
||||
Which can be imported into a layout.
|
||||
|
||||
```jsx filename="app/layout.js" highlight={2,10-12}
|
||||
import { Suspense } from 'react'
|
||||
import { NavigationEvents } from './components/navigation-events'
|
||||
|
||||
export default function Layout({ children }) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>
|
||||
{children}
|
||||
|
||||
<Suspense fallback={null}>
|
||||
<NavigationEvents />
|
||||
</Suspense>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
> **Good to know**: `<NavigationEvents>` is wrapped in a [`Suspense` boundary](/docs/app/api-reference/file-conventions/loading#examples) because[`useSearchParams()`](/docs/app/api-reference/functions/use-search-params) causes client-side rendering up to the closest `Suspense` boundary during [prerendering](/docs/app/glossary#prerendering). [Learn more](/docs/app/api-reference/functions/use-search-params#behavior).
|
||||
|
||||
### Disabling scroll to top
|
||||
|
||||
By default, Next.js will scroll to the top of the page when navigating to a new route. You can disable this behavior by passing `scroll: false` to `router.push()` or `router.replace()`.
|
||||
|
||||
```tsx filename="app/example-client-component.tsx" switcher
|
||||
'use client'
|
||||
|
||||
import { useRouter } from 'next/navigation'
|
||||
|
||||
export default function Page() {
|
||||
const router = useRouter()
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => router.push('/dashboard', { scroll: false })}
|
||||
>
|
||||
Dashboard
|
||||
</button>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/example-client-component.jsx" switcher
|
||||
'use client'
|
||||
|
||||
import { useRouter } from 'next/navigation'
|
||||
|
||||
export default function Page() {
|
||||
const router = useRouter()
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => router.push('/dashboard', { scroll: false })}
|
||||
>
|
||||
Dashboard
|
||||
</button>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Version History
|
||||
|
||||
| Version | Changes |
|
||||
| --------- | ----------------------------------------------------------------- |
|
||||
| `v15.4.0` | Optional `onInvalidate` callback for `router.prefetch` introduced |
|
||||
| `v13.0.0` | `useRouter` from `next/navigation` introduced. |
|
||||
Generated
Vendored
+382
@@ -0,0 +1,382 @@
|
||||
---
|
||||
title: useSearchParams
|
||||
description: API Reference for the useSearchParams hook.
|
||||
---
|
||||
|
||||
`useSearchParams` is a **Client Component** hook that lets you read the current URL's **query string**.
|
||||
|
||||
`useSearchParams` returns a **read-only** version of the [`URLSearchParams`](https://developer.mozilla.org/docs/Web/API/URLSearchParams) interface.
|
||||
|
||||
```tsx filename="app/dashboard/search-bar.tsx" switcher
|
||||
'use client'
|
||||
|
||||
import { useSearchParams } from 'next/navigation'
|
||||
|
||||
export default function SearchBar() {
|
||||
const searchParams = useSearchParams()
|
||||
|
||||
const search = searchParams.get('search')
|
||||
|
||||
// URL -> `/dashboard?search=my-project`
|
||||
// `search` -> 'my-project'
|
||||
return <>Search: {search}</>
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/dashboard/search-bar.js" switcher
|
||||
'use client'
|
||||
|
||||
import { useSearchParams } from 'next/navigation'
|
||||
|
||||
export default function SearchBar() {
|
||||
const searchParams = useSearchParams()
|
||||
|
||||
const search = searchParams.get('search')
|
||||
|
||||
// URL -> `/dashboard?search=my-project`
|
||||
// `search` -> 'my-project'
|
||||
return <>Search: {search}</>
|
||||
}
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
```tsx
|
||||
const searchParams = useSearchParams()
|
||||
```
|
||||
|
||||
`useSearchParams` does not take any parameters.
|
||||
|
||||
## Returns
|
||||
|
||||
`useSearchParams` returns a **read-only** version of the [`URLSearchParams`](https://developer.mozilla.org/docs/Web/API/URLSearchParams) interface, which includes utility methods for reading the URL's query string:
|
||||
|
||||
- [`URLSearchParams.get()`](https://developer.mozilla.org/docs/Web/API/URLSearchParams/get): Returns the first value associated with the search parameter. For example:
|
||||
|
||||
| URL | `searchParams.get("a")` |
|
||||
| -------------------- | --------------------------------------------------------------------------------------------------------------- |
|
||||
| `/dashboard?a=1` | `'1'` |
|
||||
| `/dashboard?a=` | `''` |
|
||||
| `/dashboard?b=3` | `null` |
|
||||
| `/dashboard?a=1&a=2` | `'1'` _- use [`getAll()`](https://developer.mozilla.org/docs/Web/API/URLSearchParams/getAll) to get all values_ |
|
||||
|
||||
- [`URLSearchParams.has()`](https://developer.mozilla.org/docs/Web/API/URLSearchParams/has): Returns a boolean value indicating if the given parameter exists. For example:
|
||||
|
||||
| URL | `searchParams.has("a")` |
|
||||
| ---------------- | ----------------------- |
|
||||
| `/dashboard?a=1` | `true` |
|
||||
| `/dashboard?b=3` | `false` |
|
||||
|
||||
- Learn more about other **read-only** methods of [`URLSearchParams`](https://developer.mozilla.org/docs/Web/API/URLSearchParams), including the [`getAll()`](https://developer.mozilla.org/docs/Web/API/URLSearchParams/getAll), [`keys()`](https://developer.mozilla.org/docs/Web/API/URLSearchParams/keys), [`values()`](https://developer.mozilla.org/docs/Web/API/URLSearchParams/values), [`entries()`](https://developer.mozilla.org/docs/Web/API/URLSearchParams/entries), [`forEach()`](https://developer.mozilla.org/docs/Web/API/URLSearchParams/forEach), and [`toString()`](https://developer.mozilla.org/docs/Web/API/URLSearchParams/toString).
|
||||
|
||||
> **Good to know**:
|
||||
>
|
||||
> - `useSearchParams` is a [Client Component](/docs/app/getting-started/server-and-client-components) hook and is **not supported** in [Server Components](/docs/app/getting-started/server-and-client-components) to prevent stale values during [partial rendering](/docs/app/getting-started/linking-and-navigating#client-side-transitions).
|
||||
> - If you want to fetch data in a Server Component based on search params, it's often a better option to read the [`searchParams` prop](/docs/app/api-reference/file-conventions/page#searchparams-optional) of the corresponding Page. You can then pass it down by props to any component (Server or Client) within that Page.
|
||||
> - If an application includes the `/pages` directory, `useSearchParams` will return `ReadonlyURLSearchParams | null`. The `null` value is for compatibility during migration since search params cannot be known during prerendering of a page that doesn't use `getServerSideProps`
|
||||
|
||||
## Behavior
|
||||
|
||||
### Prerendering
|
||||
|
||||
If a route is [prerendered](/docs/app/glossary#prerendering), calling `useSearchParams` will cause the Client Component tree up to the closest [`Suspense` boundary](/docs/app/api-reference/file-conventions/loading#examples) to be client-side rendered.
|
||||
|
||||
This allows a part of the route to be prerendered while the dynamic part that uses `useSearchParams` is client-side rendered.
|
||||
|
||||
We recommend wrapping the Client Component that uses `useSearchParams` in a `<Suspense/>` boundary. This will allow any Client Components above it to be prerendered and sent as part of initial HTML. [Example](/docs/app/api-reference/functions/use-search-params#prerendering).
|
||||
|
||||
For example:
|
||||
|
||||
```tsx filename="app/dashboard/search-bar.tsx" switcher
|
||||
'use client'
|
||||
|
||||
import { useSearchParams } from 'next/navigation'
|
||||
|
||||
export default function SearchBar() {
|
||||
const searchParams = useSearchParams()
|
||||
|
||||
const search = searchParams.get('search')
|
||||
|
||||
// This will not be logged on the server during prerendering
|
||||
console.log(search)
|
||||
|
||||
return <>Search: {search}</>
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/dashboard/search-bar.js" switcher
|
||||
'use client'
|
||||
|
||||
import { useSearchParams } from 'next/navigation'
|
||||
|
||||
export default function SearchBar() {
|
||||
const searchParams = useSearchParams()
|
||||
|
||||
const search = searchParams.get('search')
|
||||
|
||||
// This will not be logged on the server during prerendering
|
||||
console.log(search)
|
||||
|
||||
return <>Search: {search}</>
|
||||
}
|
||||
```
|
||||
|
||||
```tsx filename="app/dashboard/page.tsx" switcher
|
||||
import { Suspense } from 'react'
|
||||
import SearchBar from './search-bar'
|
||||
|
||||
// This component passed as a fallback to the Suspense boundary
|
||||
// will be rendered in place of the search bar in the initial HTML.
|
||||
// When the value is available during React hydration the fallback
|
||||
// will be replaced with the `<SearchBar>` component.
|
||||
function SearchBarFallback() {
|
||||
return <>placeholder</>
|
||||
}
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<>
|
||||
<nav>
|
||||
<Suspense fallback={<SearchBarFallback />}>
|
||||
<SearchBar />
|
||||
</Suspense>
|
||||
</nav>
|
||||
<h1>Dashboard</h1>
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/dashboard/page.js" switcher
|
||||
import { Suspense } from 'react'
|
||||
import SearchBar from './search-bar'
|
||||
|
||||
// This component passed as a fallback to the Suspense boundary
|
||||
// will be rendered in place of the search bar in the initial HTML.
|
||||
// When the value is available during React hydration the fallback
|
||||
// will be replaced with the `<SearchBar>` component.
|
||||
function SearchBarFallback() {
|
||||
return <>placeholder</>
|
||||
}
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<>
|
||||
<nav>
|
||||
<Suspense fallback={<SearchBarFallback />}>
|
||||
<SearchBar />
|
||||
</Suspense>
|
||||
</nav>
|
||||
<h1>Dashboard</h1>
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
> **Good to know**:
|
||||
>
|
||||
> - In development, routes are rendered on-demand, so `useSearchParams` doesn't suspend and things may appear to work without `Suspense`.
|
||||
> - During production builds, a static page that calls `useSearchParams` from a Client Component must be wrapped in a `Suspense` boundary, otherwise the build fails with the [Missing Suspense boundary with useSearchParams](/docs/messages/missing-suspense-with-csr-bailout) error.
|
||||
> - If you intend the route to be dynamically rendered, prefer using the [`connection`](/docs/app/api-reference/functions/connection) function first in a Server Component to wait for an incoming request, this excludes everything below from prerendering. See what makes a route dynamic in the [Dynamic Rendering guide](/docs/app/glossary#dynamic-rendering).
|
||||
> - If you're already in a Server Component Page, consider using the [`searchParams` prop](/docs/app/api-reference/file-conventions/page#searchparams-optional) and passing the values to Client Components.
|
||||
> - You can also pass the Page [`searchParams` prop](/docs/app/api-reference/file-conventions/page#searchparams-optional) directly to a Client Component and unwrap it with React's `use()`. Although this will suspend, so the Client Component should be wrapped with a `Suspense` boundary.
|
||||
|
||||
### Dynamic Rendering
|
||||
|
||||
If a route is dynamically rendered, `useSearchParams` will be available on the server during the initial server render of the Client Component.
|
||||
|
||||
For example:
|
||||
|
||||
```tsx filename="app/dashboard/search-bar.tsx" switcher
|
||||
'use client'
|
||||
|
||||
import { useSearchParams } from 'next/navigation'
|
||||
|
||||
export default function SearchBar() {
|
||||
const searchParams = useSearchParams()
|
||||
|
||||
const search = searchParams.get('search')
|
||||
|
||||
// This will be logged on the server during the initial render
|
||||
// and on the client on subsequent navigations.
|
||||
console.log(search)
|
||||
|
||||
return <>Search: {search}</>
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/dashboard/search-bar.js" switcher
|
||||
'use client'
|
||||
|
||||
import { useSearchParams } from 'next/navigation'
|
||||
|
||||
export default function SearchBar() {
|
||||
const searchParams = useSearchParams()
|
||||
|
||||
const search = searchParams.get('search')
|
||||
|
||||
// This will be logged on the server during the initial render
|
||||
// and on the client on subsequent navigations.
|
||||
console.log(search)
|
||||
|
||||
return <>Search: {search}</>
|
||||
}
|
||||
```
|
||||
|
||||
```tsx filename="app/dashboard/page.tsx" switcher
|
||||
import { connection } from 'next/server'
|
||||
import SearchBar from './search-bar'
|
||||
|
||||
export default async function Page() {
|
||||
await connection()
|
||||
return (
|
||||
<>
|
||||
<nav>
|
||||
<SearchBar />
|
||||
</nav>
|
||||
<h1>Dashboard</h1>
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/dashboard/page.js" switcher
|
||||
import { connection } from 'next/server'
|
||||
import SearchBar from './search-bar'
|
||||
|
||||
export default async function Page() {
|
||||
await connection()
|
||||
return (
|
||||
<>
|
||||
<nav>
|
||||
<SearchBar />
|
||||
</nav>
|
||||
<h1>Dashboard</h1>
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
> **Good to know**:
|
||||
>
|
||||
> - Previously, setting `export const dynamic = 'force-dynamic'` on the page was used to force dynamic rendering. Prefer using [`connection()`](/docs/app/api-reference/functions/connection) instead, as it semantically ties dynamic rendering to the incoming request.
|
||||
|
||||
### Server Components
|
||||
|
||||
#### Pages
|
||||
|
||||
To access search params in [Pages](/docs/app/api-reference/file-conventions/page) (Server Components), use the [`searchParams`](/docs/app/api-reference/file-conventions/page#searchparams-optional) prop.
|
||||
|
||||
#### Layouts
|
||||
|
||||
Unlike Pages, [Layouts](/docs/app/api-reference/file-conventions/layout) (Server Components) **do not** receive the `searchParams` prop. This is because a shared layout is [not re-rendered during navigation](/docs/app/getting-started/linking-and-navigating#client-side-transitions) which could lead to stale `searchParams` between navigations. View [detailed explanation](/docs/app/api-reference/file-conventions/layout#query-params).
|
||||
|
||||
Instead, use the Page [`searchParams`](/docs/app/api-reference/file-conventions/page) prop or the [`useSearchParams`](/docs/app/api-reference/functions/use-search-params) hook in a Client Component, which is re-rendered on the client with the latest `searchParams`.
|
||||
|
||||
## Examples
|
||||
|
||||
### Updating `searchParams`
|
||||
|
||||
You can use [`useRouter`](/docs/app/api-reference/functions/use-router) or [`Link`](/docs/app/api-reference/components/link) to set new `searchParams`. After a navigation is performed, the current [`page.js`](/docs/app/api-reference/file-conventions/page) will receive an updated [`searchParams` prop](/docs/app/api-reference/file-conventions/page#searchparams-optional).
|
||||
|
||||
```tsx filename="app/example-client-component.tsx" switcher
|
||||
'use client'
|
||||
|
||||
export default function ExampleClientComponent() {
|
||||
const router = useRouter()
|
||||
const pathname = usePathname()
|
||||
const searchParams = useSearchParams()
|
||||
|
||||
// Get a new searchParams string by merging the current
|
||||
// searchParams with a provided key/value pair
|
||||
const createQueryString = useCallback(
|
||||
(name: string, value: string) => {
|
||||
const params = new URLSearchParams(searchParams.toString())
|
||||
params.set(name, value)
|
||||
|
||||
return params.toString()
|
||||
},
|
||||
[searchParams]
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<p>Sort By</p>
|
||||
|
||||
{/* using useRouter */}
|
||||
<button
|
||||
onClick={() => {
|
||||
// <pathname>?sort=asc
|
||||
router.push(pathname + '?' + createQueryString('sort', 'asc'))
|
||||
}}
|
||||
>
|
||||
ASC
|
||||
</button>
|
||||
|
||||
{/* using <Link> */}
|
||||
<Link
|
||||
href={
|
||||
// <pathname>?sort=desc
|
||||
pathname + '?' + createQueryString('sort', 'desc')
|
||||
}
|
||||
>
|
||||
DESC
|
||||
</Link>
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/example-client-component.js" switcher
|
||||
'use client'
|
||||
|
||||
export default function ExampleClientComponent() {
|
||||
const router = useRouter()
|
||||
const pathname = usePathname()
|
||||
const searchParams = useSearchParams()
|
||||
|
||||
// Get a new searchParams string by merging the current
|
||||
// searchParams with a provided key/value pair
|
||||
const createQueryString = useCallback(
|
||||
(name, value) => {
|
||||
const params = new URLSearchParams(searchParams)
|
||||
params.set(name, value)
|
||||
|
||||
return params.toString()
|
||||
},
|
||||
[searchParams]
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<p>Sort By</p>
|
||||
|
||||
{/* using useRouter */}
|
||||
<button
|
||||
onClick={() => {
|
||||
// <pathname>?sort=asc
|
||||
router.push(pathname + '?' + createQueryString('sort', 'asc'))
|
||||
}}
|
||||
>
|
||||
ASC
|
||||
</button>
|
||||
|
||||
{/* using <Link> */}
|
||||
<Link
|
||||
href={
|
||||
// <pathname>?sort=desc
|
||||
pathname + '?' + createQueryString('sort', 'desc')
|
||||
}
|
||||
>
|
||||
DESC
|
||||
</Link>
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Version History
|
||||
|
||||
| Version | Changes |
|
||||
| --------- | ----------------------------- |
|
||||
| `v13.0.0` | `useSearchParams` introduced. |
|
||||
Generated
Vendored
+179
@@ -0,0 +1,179 @@
|
||||
---
|
||||
title: useSelectedLayoutSegment
|
||||
description: API Reference for the useSelectedLayoutSegment hook.
|
||||
---
|
||||
|
||||
`useSelectedLayoutSegment` is a **Client Component** hook that lets you read the active route segment **one level below** the Layout it is called from.
|
||||
|
||||
It is useful for navigation UI, such as tabs inside a parent layout that change style depending on the active child segment.
|
||||
|
||||
```tsx filename="app/example-client-component.tsx" switcher
|
||||
'use client'
|
||||
|
||||
import { useSelectedLayoutSegment } from 'next/navigation'
|
||||
|
||||
export default function ExampleClientComponent() {
|
||||
const segment = useSelectedLayoutSegment()
|
||||
|
||||
return <p>Active segment: {segment}</p>
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/example-client-component.js" switcher
|
||||
'use client'
|
||||
|
||||
import { useSelectedLayoutSegment } from 'next/navigation'
|
||||
|
||||
export default function ExampleClientComponent() {
|
||||
const segment = useSelectedLayoutSegment()
|
||||
|
||||
return <p>Active segment: {segment}</p>
|
||||
}
|
||||
```
|
||||
|
||||
> **Good to know**:
|
||||
>
|
||||
> - Since `useSelectedLayoutSegment` is a [Client Component](/docs/app/getting-started/server-and-client-components) hook, and Layouts are [Server Components](/docs/app/getting-started/server-and-client-components) by default, `useSelectedLayoutSegment` is usually called via a Client Component that is imported into a Layout.
|
||||
> - `useSelectedLayoutSegment` only returns the segment one level down. To return all active segments, see [`useSelectedLayoutSegments`](/docs/app/api-reference/functions/use-selected-layout-segments)
|
||||
> - For [catch-all](/docs/app/api-reference/file-conventions/dynamic-routes#catch-all-segments) routes, the matched segments are returned as a single joined string. For example, given `app/blog/[...slug]/page.js`, calling from `app/blog/layout.js` when visiting `/blog/a/b/c` returns `'a/b/c'`.
|
||||
|
||||
## Parameters
|
||||
|
||||
```tsx
|
||||
const segment = useSelectedLayoutSegment(parallelRoutesKey?: string)
|
||||
```
|
||||
|
||||
`useSelectedLayoutSegment` _optionally_ accepts a [`parallelRoutesKey`](/docs/app/api-reference/file-conventions/parallel-routes#with-useselectedlayoutsegments), which allows you to read the active route segment within that slot.
|
||||
|
||||
## Returns
|
||||
|
||||
`useSelectedLayoutSegment` returns a string of the active segment or `null` if one doesn't exist.
|
||||
|
||||
For example, given the Layouts and URLs below, the returned segment would be:
|
||||
|
||||
| Layout | Visited URL | Returned Segment |
|
||||
| ------------------------- | ------------------------------ | ---------------- |
|
||||
| `app/layout.js` | `/` | `null` |
|
||||
| `app/layout.js` | `/dashboard` | `'dashboard'` |
|
||||
| `app/dashboard/layout.js` | `/dashboard` | `null` |
|
||||
| `app/dashboard/layout.js` | `/dashboard/settings` | `'settings'` |
|
||||
| `app/dashboard/layout.js` | `/dashboard/analytics` | `'analytics'` |
|
||||
| `app/dashboard/layout.js` | `/dashboard/analytics/monthly` | `'analytics'` |
|
||||
|
||||
For catch-all routes (`[...slug]`), the returned segment contains all matched path segments joined as a single string:
|
||||
|
||||
| Layout | Visited URL | Returned Segment |
|
||||
| -------------------- | ------------- | ---------------- |
|
||||
| `app/blog/layout.js` | `/blog/a/b/c` | `'a/b/c'` |
|
||||
|
||||
## Examples
|
||||
|
||||
### Creating an active link component
|
||||
|
||||
You can use `useSelectedLayoutSegment` to create an active link component that changes style depending on the active segment. For example, a featured posts list in the sidebar of a blog:
|
||||
|
||||
```tsx filename="app/blog/blog-nav-link.tsx" switcher
|
||||
'use client'
|
||||
|
||||
import Link from 'next/link'
|
||||
import { useSelectedLayoutSegment } from 'next/navigation'
|
||||
|
||||
// This *client* component will be imported into a blog layout
|
||||
export default function BlogNavLink({
|
||||
slug,
|
||||
children,
|
||||
}: {
|
||||
slug: string
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
// Navigating to `/blog/hello-world` will return 'hello-world'
|
||||
// for the selected layout segment
|
||||
const segment = useSelectedLayoutSegment()
|
||||
const isActive = slug === segment
|
||||
|
||||
return (
|
||||
<Link
|
||||
href={`/blog/${slug}`}
|
||||
// Change style depending on whether the link is active
|
||||
style={{ fontWeight: isActive ? 'bold' : 'normal' }}
|
||||
>
|
||||
{children}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/blog/blog-nav-link.js" switcher
|
||||
'use client'
|
||||
|
||||
import Link from 'next/link'
|
||||
import { useSelectedLayoutSegment } from 'next/navigation'
|
||||
|
||||
// This *client* component will be imported into a blog layout
|
||||
export default function BlogNavLink({ slug, children }) {
|
||||
// Navigating to `/blog/hello-world` will return 'hello-world'
|
||||
// for the selected layout segment
|
||||
const segment = useSelectedLayoutSegment()
|
||||
const isActive = slug === segment
|
||||
|
||||
return (
|
||||
<Link
|
||||
href={`/blog/${slug}`}
|
||||
// Change style depending on whether the link is active
|
||||
style={{ fontWeight: isActive ? 'bold' : 'normal' }}
|
||||
>
|
||||
{children}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```tsx filename="app/blog/layout.tsx" switcher
|
||||
// Import the Client Component into a parent Layout (Server Component)
|
||||
import { BlogNavLink } from './blog-nav-link'
|
||||
import getFeaturedPosts from './get-featured-posts'
|
||||
|
||||
export default async function Layout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
const featuredPosts = await getFeaturedPosts()
|
||||
return (
|
||||
<div>
|
||||
{featuredPosts.map((post) => (
|
||||
<div key={post.id}>
|
||||
<BlogNavLink slug={post.slug}>{post.title}</BlogNavLink>
|
||||
</div>
|
||||
))}
|
||||
<div>{children}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/blog/layout.js" switcher
|
||||
// Import the Client Component into a parent Layout (Server Component)
|
||||
import { BlogNavLink } from './blog-nav-link'
|
||||
import getFeaturedPosts from './get-featured-posts'
|
||||
|
||||
export default async function Layout({ children }) {
|
||||
const featuredPosts = await getFeaturedPosts()
|
||||
return (
|
||||
<div>
|
||||
{featuredPosts.map((post) => (
|
||||
<div key={post.id}>
|
||||
<BlogNavLink slug={post.slug}>{post.title}</BlogNavLink>
|
||||
</div>
|
||||
))}
|
||||
<div>{children}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Version History
|
||||
|
||||
| Version | Changes |
|
||||
| --------- | -------------------------------------- |
|
||||
| `v13.0.0` | `useSelectedLayoutSegment` introduced. |
|
||||
Generated
Vendored
+85
@@ -0,0 +1,85 @@
|
||||
---
|
||||
title: useSelectedLayoutSegments
|
||||
description: API Reference for the useSelectedLayoutSegments hook.
|
||||
---
|
||||
|
||||
`useSelectedLayoutSegments` is a **Client Component** hook that lets you read the active route segments **below** the Layout it is called from.
|
||||
|
||||
It is useful for creating UI in parent Layouts that need knowledge of active child segments such as breadcrumbs.
|
||||
|
||||
```tsx filename="app/example-client-component.tsx" switcher
|
||||
'use client'
|
||||
|
||||
import { useSelectedLayoutSegments } from 'next/navigation'
|
||||
|
||||
export default function ExampleClientComponent() {
|
||||
const segments = useSelectedLayoutSegments()
|
||||
|
||||
return (
|
||||
<ul>
|
||||
{segments.map((segment, index) => (
|
||||
<li key={index}>{segment}</li>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/example-client-component.js" switcher
|
||||
'use client'
|
||||
|
||||
import { useSelectedLayoutSegments } from 'next/navigation'
|
||||
|
||||
export default function ExampleClientComponent() {
|
||||
const segments = useSelectedLayoutSegments()
|
||||
|
||||
return (
|
||||
<ul>
|
||||
{segments.map((segment, index) => (
|
||||
<li key={index}>{segment}</li>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
> **Good to know**:
|
||||
>
|
||||
> - Since `useSelectedLayoutSegments` is a [Client Component](/docs/app/getting-started/server-and-client-components) hook, and Layouts are [Server Components](/docs/app/getting-started/server-and-client-components) by default, `useSelectedLayoutSegments` is usually called via a Client Component that is imported into a Layout.
|
||||
> - The returned segments include [Route Groups](/docs/app/api-reference/file-conventions/route-groups), which you might not want to be included in your UI. You can use the [`filter`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter) array method to remove items that start with a bracket.
|
||||
> - For [catch-all](/docs/app/api-reference/file-conventions/dynamic-routes#catch-all-segments) routes, the matched segments are returned as a single joined string within the array. For example, given `app/blog/[...slug]/page.js`, calling from `app/layout.js` when visiting `/blog/a/b/c` returns `['blog', 'a/b/c']`, not `['blog', 'a', 'b', 'c']`.
|
||||
|
||||
## Parameters
|
||||
|
||||
```tsx
|
||||
const segments = useSelectedLayoutSegments(parallelRoutesKey?: string)
|
||||
```
|
||||
|
||||
`useSelectedLayoutSegments` _optionally_ accepts a [`parallelRoutesKey`](/docs/app/api-reference/file-conventions/parallel-routes#with-useselectedlayoutsegments), which allows you to read the active route segment within that slot.
|
||||
|
||||
## Returns
|
||||
|
||||
`useSelectedLayoutSegments` returns an array of strings containing the active segments one level down from the layout the hook was called from. Or an empty array if none exist.
|
||||
|
||||
For example, given the Layouts and URLs below, the returned segments would be:
|
||||
|
||||
| Layout | Visited URL | Returned Segments |
|
||||
| ------------------------- | --------------------- | --------------------------- |
|
||||
| `app/layout.js` | `/` | `[]` |
|
||||
| `app/layout.js` | `/dashboard` | `['dashboard']` |
|
||||
| `app/layout.js` | `/dashboard/settings` | `['dashboard', 'settings']` |
|
||||
| `app/dashboard/layout.js` | `/dashboard` | `[]` |
|
||||
| `app/dashboard/layout.js` | `/dashboard/settings` | `['settings']` |
|
||||
|
||||
For catch-all routes (`[...slug]`), all matched path segments are returned as a single joined string within the array:
|
||||
|
||||
| Layout | Visited URL | Returned Segments |
|
||||
| -------------------- | ------------- | ------------------- |
|
||||
| `app/layout.js` | `/blog/a/b/c` | `['blog', 'a/b/c']` |
|
||||
| `app/blog/layout.js` | `/blog/a/b/c` | `['a/b/c']` |
|
||||
|
||||
## Version History
|
||||
|
||||
| Version | Changes |
|
||||
| --------- | --------------------------------------- |
|
||||
| `v13.0.0` | `useSelectedLayoutSegments` introduced. |
|
||||
Generated
Vendored
+79
@@ -0,0 +1,79 @@
|
||||
---
|
||||
title: userAgent
|
||||
description: The userAgent helper extends the Web Request API with additional properties and methods to interact with the user agent object from the request.
|
||||
---
|
||||
|
||||
{/* The content of this doc is shared between the app and pages router. You can use the `<PagesOnly>Content</PagesOnly>` component to add content that is specific to the Pages Router. Any shared content should not be wrapped in a component. */}
|
||||
|
||||
The `userAgent` helper extends the [Web Request API](https://developer.mozilla.org/docs/Web/API/Request) with additional properties and methods to interact with the user agent object from the request.
|
||||
|
||||
```ts filename="proxy.ts" switcher
|
||||
import { NextRequest, NextResponse, userAgent } from 'next/server'
|
||||
|
||||
export function proxy(request: NextRequest) {
|
||||
const url = request.nextUrl
|
||||
const { device } = userAgent(request)
|
||||
|
||||
// device.type can be: 'mobile', 'tablet', 'console', 'smarttv',
|
||||
// 'wearable', 'embedded', or undefined (for desktop browsers)
|
||||
const viewport = device.type || 'desktop'
|
||||
|
||||
url.searchParams.set('viewport', viewport)
|
||||
return NextResponse.rewrite(url)
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="proxy.js" switcher
|
||||
import { NextResponse, userAgent } from 'next/server'
|
||||
|
||||
export function proxy(request) {
|
||||
const url = request.nextUrl
|
||||
const { device } = userAgent(request)
|
||||
|
||||
// device.type can be: 'mobile', 'tablet', 'console', 'smarttv',
|
||||
// 'wearable', 'embedded', or undefined (for desktop browsers)
|
||||
const viewport = device.type || 'desktop'
|
||||
|
||||
url.searchParams.set('viewport', viewport)
|
||||
return NextResponse.rewrite(url)
|
||||
}
|
||||
```
|
||||
|
||||
## `isBot`
|
||||
|
||||
A boolean indicating whether the request comes from a known bot.
|
||||
|
||||
## `browser`
|
||||
|
||||
An object containing information about the browser used in the request.
|
||||
|
||||
- `name`: A string representing the browser's name, or `undefined` if not identifiable.
|
||||
- `version`: A string representing the browser's version, or `undefined`.
|
||||
|
||||
## `device`
|
||||
|
||||
An object containing information about the device used in the request.
|
||||
|
||||
- `model`: A string representing the model of the device, or `undefined`.
|
||||
- `type`: A string representing the type of the device, such as `console`, `mobile`, `tablet`, `smarttv`, `wearable`, `embedded`, or `undefined`.
|
||||
- `vendor`: A string representing the vendor of the device, or `undefined`.
|
||||
|
||||
## `engine`
|
||||
|
||||
An object containing information about the browser's engine.
|
||||
|
||||
- `name`: A string representing the engine's name. Possible values include: `Amaya`, `Blink`, `EdgeHTML`, `Flow`, `Gecko`, `Goanna`, `iCab`, `KHTML`, `Links`, `Lynx`, `NetFront`, `NetSurf`, `Presto`, `Tasman`, `Trident`, `w3m`, `WebKit` or `undefined`.
|
||||
- `version`: A string representing the engine's version, or `undefined`.
|
||||
|
||||
## `os`
|
||||
|
||||
An object containing information about the operating system.
|
||||
|
||||
- `name`: A string representing the name of the OS, or `undefined`.
|
||||
- `version`: A string representing the version of the OS, or `undefined`.
|
||||
|
||||
## `cpu`
|
||||
|
||||
An object containing information about the CPU architecture.
|
||||
|
||||
- `architecture`: A string representing the architecture of the CPU. Possible values include: `68k`, `amd64`, `arm`, `arm64`, `armhf`, `avr`, `ia32`, `ia64`, `irix`, `irix64`, `mips`, `mips64`, `pa-risc`, `ppc`, `sparc`, `sparc64` or `undefined`
|
||||
Generated
Vendored
+79
@@ -0,0 +1,79 @@
|
||||
---
|
||||
title: adapterPath
|
||||
description: Configure a custom adapter for Next.js to hook into the build process.
|
||||
---
|
||||
|
||||
Next.js provides a built-in adapters API. It allows deployment platforms or build systems to integrate with the Next.js build process.
|
||||
|
||||
For a full reference implementation, see the [`nextjs/adapter-vercel`](https://github.com/nextjs/adapter-vercel) adapter.
|
||||
|
||||
## Configuration
|
||||
|
||||
To use an adapter, specify the path to your adapter module in `adapterPath`:
|
||||
|
||||
```js filename="next.config.js"
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
adapterPath: require.resolve('./my-adapter.js'),
|
||||
}
|
||||
|
||||
module.exports = nextConfig
|
||||
```
|
||||
|
||||
Alternatively `NEXT_ADAPTER_PATH` can be set to enable zero-config usage in deployment platforms.
|
||||
|
||||
## Adapters
|
||||
|
||||
For full adapter implementation details, use the dedicated Adapters section:
|
||||
|
||||
- [Configuration](/docs/app/api-reference/adapters/configuration)
|
||||
- [Creating an Adapter](/docs/app/api-reference/adapters/creating-an-adapter)
|
||||
- [API Reference](/docs/app/api-reference/adapters/api-reference)
|
||||
- [Testing Adapters](/docs/app/api-reference/adapters/testing-adapters)
|
||||
- [Routing with `@next/routing`](/docs/app/api-reference/adapters/routing-with-next-routing)
|
||||
- [Implementing PPR in an Adapter](/docs/app/api-reference/adapters/implementing-ppr-in-an-adapter)
|
||||
- [Runtime Integration](/docs/app/api-reference/adapters/runtime-integration)
|
||||
- [Invoking Entrypoints](/docs/app/api-reference/adapters/invoking-entrypoints)
|
||||
- [Output Types](/docs/app/api-reference/adapters/output-types)
|
||||
- [Routing Information](/docs/app/api-reference/adapters/routing-information)
|
||||
- [Use Cases](/docs/app/api-reference/adapters/use-cases)
|
||||
|
||||
## Creating an Adapter
|
||||
|
||||
See [Creating an Adapter](/docs/app/api-reference/adapters/creating-an-adapter).
|
||||
|
||||
## API Reference
|
||||
|
||||
See [API Reference](/docs/app/api-reference/adapters/api-reference).
|
||||
|
||||
## Testing Adapters
|
||||
|
||||
See [Testing Adapters](/docs/app/api-reference/adapters/testing-adapters).
|
||||
|
||||
## Routing with `@next/routing`
|
||||
|
||||
See [Routing with `@next/routing`](/docs/app/api-reference/adapters/routing-with-next-routing).
|
||||
|
||||
## Implementing PPR in an Adapter
|
||||
|
||||
See [Implementing PPR in an Adapter](/docs/app/api-reference/adapters/implementing-ppr-in-an-adapter).
|
||||
|
||||
## Runtime Integration
|
||||
|
||||
See [Runtime Integration](/docs/app/api-reference/adapters/runtime-integration).
|
||||
|
||||
## Invoking Entrypoints
|
||||
|
||||
See [Invoking Entrypoints](/docs/app/api-reference/adapters/invoking-entrypoints).
|
||||
|
||||
## Output Types
|
||||
|
||||
See [Output Types](/docs/app/api-reference/adapters/output-types).
|
||||
|
||||
## Routing Information
|
||||
|
||||
See [Routing Information](/docs/app/api-reference/adapters/routing-information).
|
||||
|
||||
## Use Cases
|
||||
|
||||
See [Use Cases](/docs/app/api-reference/adapters/use-cases).
|
||||
Generated
Vendored
+18
@@ -0,0 +1,18 @@
|
||||
---
|
||||
title: allowedDevOrigins
|
||||
description: Use `allowedDevOrigins` to configure additional origins that can request the dev server.
|
||||
---
|
||||
|
||||
{/* The content of this doc is shared between the app and pages router. You can use the `<PagesOnly>Content</PagesOnly>` component to add content that is specific to the Pages Router. Any shared content should not be wrapped in a component. */}
|
||||
|
||||
Next.js blocks cross-origin requests to dev-only assets and endpoints during development by default to prevent unauthorized access.
|
||||
|
||||
To configure a Next.js application to allow requests from origins other than the hostname the server was initialized with (`localhost` by default), use the `allowedDevOrigins` config option.
|
||||
|
||||
`allowedDevOrigins` lets you set additional origins that can request the dev server in development mode. For example, to use `local-origin.dev` instead of only `localhost`, open `next.config.js` and add the `allowedDevOrigins` config:
|
||||
|
||||
```js filename="next.config.js"
|
||||
module.exports = {
|
||||
allowedDevOrigins: ['local-origin.dev', '*.local-origin.dev'],
|
||||
}
|
||||
```
|
||||
Generated
Vendored
+11
@@ -0,0 +1,11 @@
|
||||
---
|
||||
title: appDir
|
||||
description: Enable the App Router to use layouts, streaming, and more.
|
||||
version: legacy
|
||||
---
|
||||
|
||||
> **Good to know**: This option is **no longer** needed as of Next.js 13.4. The App Router is now stable.
|
||||
|
||||
The App Router ([`app` directory](/docs/app)) enables support for [layouts](/docs/app/api-reference/file-conventions/layout), [Server Components](/docs/app/getting-started/server-and-client-components), [streaming](/docs/app/api-reference/file-conventions/loading), and [colocated data fetching](/docs/app/getting-started/fetching-data).
|
||||
|
||||
Using the `app` directory will automatically enable [React Strict Mode](https://react.dev/reference/react/StrictMode). Learn how to [incrementally adopt `app`](/docs/app/guides/migrating/app-router-migration#migrating-from-pages-to-app).
|
||||
Generated
Vendored
+76
@@ -0,0 +1,76 @@
|
||||
---
|
||||
title: assetPrefix
|
||||
description: Learn how to use the assetPrefix config option to configure your CDN.
|
||||
---
|
||||
|
||||
{/* The content of this doc is shared between the app and pages router. You can use the `<PagesOnly>Content</PagesOnly>` component to add content that is specific to the Pages Router. Any shared content should not be wrapped in a component. */}
|
||||
|
||||
<AppOnly>
|
||||
|
||||
> **Attention**: [Deploying to Vercel](/docs/app/getting-started/deploying) automatically configures a global CDN for your Next.js project.
|
||||
> You do not need to manually setup an Asset Prefix.
|
||||
|
||||
</AppOnly>
|
||||
|
||||
<PagesOnly>
|
||||
|
||||
> **Attention**: [Deploying to Vercel](/docs/pages/getting-started/deploying) automatically configures a global CDN for your Next.js project.
|
||||
> You do not need to manually setup an Asset Prefix.
|
||||
|
||||
</PagesOnly>
|
||||
|
||||
> **Good to know**: Next.js 9.5+ added support for a customizable [Base Path](/docs/app/api-reference/config/next-config-js/basePath), which is better
|
||||
> suited for hosting your application on a sub-path like `/docs`.
|
||||
> We do not suggest you use a custom Asset Prefix for this use case.
|
||||
|
||||
## Set up a CDN
|
||||
|
||||
To set up a [CDN](https://en.wikipedia.org/wiki/Content_delivery_network), you can set up an asset prefix and configure your CDN's origin to resolve to the domain that Next.js is hosted on.
|
||||
|
||||
Open `next.config.mjs` and add the `assetPrefix` config based on the [phase](/docs/app/api-reference/config/next-config-js#async-configuration):
|
||||
|
||||
```js filename="next.config.mjs"
|
||||
// @ts-check
|
||||
import { PHASE_DEVELOPMENT_SERVER } from 'next/constants'
|
||||
|
||||
export default (phase) => {
|
||||
const isDev = phase === PHASE_DEVELOPMENT_SERVER
|
||||
/**
|
||||
* @type {import('next').NextConfig}
|
||||
*/
|
||||
const nextConfig = {
|
||||
assetPrefix: isDev ? undefined : 'https://cdn.mydomain.com',
|
||||
}
|
||||
return nextConfig
|
||||
}
|
||||
```
|
||||
|
||||
Next.js will automatically use your asset prefix for the JavaScript and CSS files it loads from the `/_next/` path (`.next/static/` folder). For example, with the above configuration, the following request for a JS chunk:
|
||||
|
||||
```
|
||||
/_next/static/chunks/4b9b41aaa062cbbfeff4add70f256968c51ece5d.4d708494b3aed70c04f0.js
|
||||
```
|
||||
|
||||
Would instead become:
|
||||
|
||||
```
|
||||
https://cdn.mydomain.com/_next/static/chunks/4b9b41aaa062cbbfeff4add70f256968c51ece5d.4d708494b3aed70c04f0.js
|
||||
```
|
||||
|
||||
The exact configuration for uploading your files to a given CDN will depend on your CDN of choice. The only folder you need to host on your CDN is the contents of `.next/static/`, which should be uploaded as `_next/static/` as the above URL request indicates. **Do not upload the rest of your `.next/` folder**, as you should not expose your server code and other configuration to the public.
|
||||
|
||||
While `assetPrefix` covers requests to `_next/static`, it does not influence the following paths:
|
||||
|
||||
<AppOnly>
|
||||
|
||||
- Files in the [public](/docs/app/api-reference/file-conventions/public-folder) folder; if you want to serve those assets over a CDN, you'll have to introduce the prefix yourself
|
||||
|
||||
</AppOnly>
|
||||
|
||||
<PagesOnly>
|
||||
|
||||
- Files in the [public](/docs/pages/api-reference/file-conventions/public-folder) folder; if you want to serve those assets over a CDN, you'll have to introduce the prefix yourself
|
||||
- `/_next/data/` requests for `getServerSideProps` pages. These requests will always be made against the main domain since they're not static.
|
||||
- `/_next/data/` requests for `getStaticProps` pages. These requests will always be made against the main domain to support [Incremental Static Generation](/docs/pages/guides/incremental-static-regeneration), even if you're not using it (for consistency).
|
||||
|
||||
</PagesOnly>
|
||||
Generated
Vendored
+33
@@ -0,0 +1,33 @@
|
||||
---
|
||||
title: authInterrupts
|
||||
description: Learn how to enable the experimental `authInterrupts` configuration option to use `forbidden` and `unauthorized`.
|
||||
version: canary
|
||||
related:
|
||||
links:
|
||||
- app/api-reference/functions/forbidden
|
||||
- app/api-reference/functions/unauthorized
|
||||
- app/api-reference/file-conventions/forbidden
|
||||
- app/api-reference/file-conventions/unauthorized
|
||||
---
|
||||
|
||||
The `authInterrupts` configuration option allows you to use [`forbidden`](/docs/app/api-reference/functions/forbidden) and [`unauthorized`](/docs/app/api-reference/functions/unauthorized) APIs in your application. While these functions are experimental, you must enable the `authInterrupts` option in your `next.config.js` file to use them:
|
||||
|
||||
```ts filename="next.config.ts" switcher
|
||||
import type { NextConfig } from 'next'
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
experimental: {
|
||||
authInterrupts: true,
|
||||
},
|
||||
}
|
||||
|
||||
export default nextConfig
|
||||
```
|
||||
|
||||
```js filename="next.config.js" switcher
|
||||
module.exports = {
|
||||
experimental: {
|
||||
authInterrupts: true,
|
||||
},
|
||||
}
|
||||
```
|
||||
frontend/node_modules/next/dist/docs/01-app/03-api-reference/05-config/01-next-config-js/basePath.md
Generated
Vendored
+79
@@ -0,0 +1,79 @@
|
||||
---
|
||||
title: basePath
|
||||
description: Use `basePath` to deploy a Next.js application under a sub-path of a domain.
|
||||
---
|
||||
|
||||
{/* The content of this doc is shared between the app and pages router. You can use the `<PagesOnly>Content</PagesOnly>` component to add content that is specific to the Pages Router. Any shared content should not be wrapped in a component. */}
|
||||
|
||||
To deploy a Next.js application under a sub-path of a domain you can use the `basePath` config option.
|
||||
|
||||
`basePath` allows you to set a path prefix for the application. For example, to use `/docs` instead of `''` (an empty string, the default), open `next.config.js` and add the `basePath` config:
|
||||
|
||||
```js filename="next.config.js"
|
||||
module.exports = {
|
||||
basePath: '/docs',
|
||||
}
|
||||
```
|
||||
|
||||
> **Good to know**: This value must be set at build time and cannot be changed without re-building as the value is inlined in the client-side bundles.
|
||||
|
||||
### Links
|
||||
|
||||
When linking to other pages using `next/link` and `next/router` the `basePath` will be automatically applied.
|
||||
|
||||
For example, using `/about` will automatically become `/docs/about` when `basePath` is set to `/docs`.
|
||||
|
||||
```js
|
||||
export default function HomePage() {
|
||||
return (
|
||||
<>
|
||||
<Link href="/about">About Page</Link>
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
Output html:
|
||||
|
||||
```html
|
||||
<a href="/docs/about">About Page</a>
|
||||
```
|
||||
|
||||
This makes sure that you don't have to change all links in your application when changing the `basePath` value.
|
||||
|
||||
### Images
|
||||
|
||||
<AppOnly>
|
||||
|
||||
When using the [`next/image`](/docs/app/api-reference/components/image) component, you will need to add the `basePath` in front of `src`.
|
||||
|
||||
</AppOnly>
|
||||
|
||||
<PagesOnly>
|
||||
|
||||
When using the [`next/image`](/docs/pages/api-reference/components/image) component, you will need to add the `basePath` in front of `src`.
|
||||
|
||||
</PagesOnly>
|
||||
|
||||
For example, using `/docs/me.png` will properly serve your image when `basePath` is set to `/docs`.
|
||||
|
||||
```jsx
|
||||
import Image from 'next/image'
|
||||
|
||||
function Home() {
|
||||
return (
|
||||
<>
|
||||
<h1>My Homepage</h1>
|
||||
<Image
|
||||
src="/docs/me.png"
|
||||
alt="Picture of the author"
|
||||
width={500}
|
||||
height={500}
|
||||
/>
|
||||
<p>Welcome to my homepage!</p>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Home
|
||||
```
|
||||
Generated
Vendored
+52
@@ -0,0 +1,52 @@
|
||||
---
|
||||
title: cacheComponents
|
||||
description: Learn how to enable the cacheComponents flag in Next.js.
|
||||
---
|
||||
|
||||
The `cacheComponents` flag is a feature in Next.js that causes data fetching operations in the App Router to be excluded from prerenders unless they are explicitly cached. This can be useful for optimizing the performance of uncached data fetching in Server Components.
|
||||
|
||||
It is useful if your application requires fresh data fetching during runtime rather than serving from a prerendered cache.
|
||||
|
||||
It is expected to be used in conjunction with [`use cache`](/docs/app/api-reference/directives/use-cache) so that your data fetching happens at runtime by default unless you define specific parts of your application to be cached with `use cache` at the page, function, or component level.
|
||||
|
||||
## Usage
|
||||
|
||||
To enable the `cacheComponents` flag, set it to `true` in your `next.config.ts` file:
|
||||
|
||||
```ts filename="next.config.ts"
|
||||
import type { NextConfig } from 'next'
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
cacheComponents: true,
|
||||
}
|
||||
|
||||
export default nextConfig
|
||||
```
|
||||
|
||||
When `cacheComponents` is enabled, you can use the following cache functions and configurations:
|
||||
|
||||
- The [`use cache` directive](/docs/app/api-reference/directives/use-cache)
|
||||
- The [`cacheLife` function](/docs/app/api-reference/config/next-config-js/cacheLife) with `use cache`
|
||||
- The [`cacheTag` function](/docs/app/api-reference/functions/cacheTag)
|
||||
|
||||
## Navigation with Activity
|
||||
|
||||
When `cacheComponents` is enabled, Next.js uses React's [`<Activity>`](https://react.dev/reference/react/Activity) component to preserve component state during client-side navigation.
|
||||
|
||||
Rather than unmounting the previous route when you navigate away, Next.js sets the Activity mode to [`"hidden"`](https://react.dev/reference/react/Activity#activity). This means:
|
||||
|
||||
- Component state is preserved when navigating between routes
|
||||
- When you navigate back, the previous route reappears with its state intact
|
||||
- Effects are cleaned up when a route is hidden, and recreated when it becomes visible again
|
||||
|
||||
This behavior improves the navigation experience by maintaining UI state (form inputs, or expanded sections) when users navigate back and forth between routes.
|
||||
|
||||
> **Good to know**: Next.js uses heuristics to keep a few recently visited routes `"hidden"`, while older routes are removed from the DOM to prevent excessive growth.
|
||||
|
||||
Some UI patterns behave differently when components stay mounted instead of unmounting. See the [Preserving UI state guide](/docs/app/guides/preserving-ui-state) for handling common patterns like dropdowns, dialogs, and testing.
|
||||
|
||||
## Version History
|
||||
|
||||
| Version | Change |
|
||||
| ------- | --------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| 16.0.0 | `cacheComponents` introduced. This flag controls the `ppr`, `useCache`, and `dynamicIO` flags as a single, unified configuration. |
|
||||
Generated
Vendored
+509
@@ -0,0 +1,509 @@
|
||||
---
|
||||
title: cacheHandlers
|
||||
description: Configure custom cache handlers for use cache directives in Next.js.
|
||||
related:
|
||||
title: Related
|
||||
description: View related API references.
|
||||
links:
|
||||
- app/api-reference/directives/use-cache
|
||||
- app/api-reference/directives/use-cache-remote
|
||||
- app/api-reference/directives/use-cache-private
|
||||
- app/api-reference/config/next-config-js/cacheLife
|
||||
---
|
||||
|
||||
The `cacheHandlers` configuration allows you to define custom cache storage implementations for [`'use cache'`](/docs/app/api-reference/directives/use-cache) and [`'use cache: remote'`](/docs/app/api-reference/directives/use-cache-remote). This enables you to store cached components and functions in external services or customize the caching behavior. [`'use cache: private'`](/docs/app/api-reference/directives/use-cache-private) is not configurable.
|
||||
|
||||
## When to use custom cache handlers
|
||||
|
||||
**Most applications don't need custom cache handlers.** The default in-memory cache works well in the typical use case.
|
||||
|
||||
Custom cache handlers are for advanced scenarios where you need to either share cache across multiple instances or change where the cache is stored. For example, you can configure a custom `remote` handler for external storage (like a key-value store), then use `'use cache'` in your code for in-memory caching and `'use cache: remote'` for the external storage, allowing different caching strategies within the same application.
|
||||
|
||||
**Sharing cache across instances**
|
||||
|
||||
The default in-memory cache is isolated to each Next.js process. If you're running multiple servers or containers, each instance will have its own cache that isn't shared with others and is lost on restart.
|
||||
|
||||
Custom handlers let you integrate with shared storage systems (like Redis, Memcached, or DynamoDB) that all your Next.js instances can access.
|
||||
|
||||
**Changing storage type**
|
||||
|
||||
You might want to store cache differently than the default in-memory approach. You can implement a custom handler to store cache on disk, in a database, or in an external caching service. Reasons include: persistence across restarts, reducing memory usage, or integrating with existing infrastructure.
|
||||
|
||||
## Usage
|
||||
|
||||
To configure custom cache handlers:
|
||||
|
||||
1. Define your cache handler in a separate file, see [examples](#examples) for implementation details.
|
||||
2. Reference the file path in your Next config file
|
||||
|
||||
```ts filename="next.config.ts" switcher
|
||||
import type { NextConfig } from 'next'
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
cacheHandlers: {
|
||||
default: require.resolve('./cache-handlers/default-handler.js'),
|
||||
remote: require.resolve('./cache-handlers/remote-handler.js'),
|
||||
},
|
||||
}
|
||||
|
||||
export default nextConfig
|
||||
```
|
||||
|
||||
```js filename="next.config.js" switcher
|
||||
module.exports = {
|
||||
cacheHandlers: {
|
||||
default: require.resolve('./cache-handlers/default-handler.js'),
|
||||
remote: require.resolve('./cache-handlers/remote-handler.js'),
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Handler types
|
||||
|
||||
- **`default`**: Used by the `'use cache'` directive
|
||||
- **`remote`**: Used by the `'use cache: remote'` directive
|
||||
|
||||
If you don't configure `cacheHandlers`, Next.js uses an in-memory LRU (Least Recently Used) cache for both `default` and `remote`. You can view the [default implementation](https://github.com/vercel/next.js/blob/canary/packages/next/src/server/lib/cache-handlers/default.ts) as a reference.
|
||||
|
||||
You can also define additional named handlers (e.g., `sessions`, `analytics`) and reference them with `'use cache: <name>'`.
|
||||
|
||||
Note that `'use cache: private'` does not use cache handlers and cannot be customized.
|
||||
|
||||
## API Reference
|
||||
|
||||
A cache handler must implement the [`CacheHandler`](https://github.com/vercel/next.js/blob/canary/packages/next/src/server/lib/cache-handlers/types.ts) interface with the following methods:
|
||||
|
||||
### `get()`
|
||||
|
||||
Retrieve a cache entry for the given cache key.
|
||||
|
||||
```ts
|
||||
get(cacheKey: string, softTags: string[]): Promise<CacheEntry | undefined>
|
||||
```
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| ---------- | ---------- | ------------------------------------------------------------------------------------------- |
|
||||
| `cacheKey` | `string` | The unique key for the cache entry. |
|
||||
| `softTags` | `string[]` | Implicit tags derived from the route path. See [Soft Tags](#soft-tags) for how to use them. |
|
||||
|
||||
Returns a `CacheEntry` object if found, or `undefined` if not found or expired.
|
||||
|
||||
Your `get` method should retrieve the cache entry from storage, check if it has expired based on the `revalidate` time, and return `undefined` for missing or expired entries.
|
||||
|
||||
```js
|
||||
const cacheHandler = {
|
||||
async get(cacheKey, softTags) {
|
||||
const entry = cache.get(cacheKey)
|
||||
if (!entry) return undefined
|
||||
|
||||
// Check if expired
|
||||
const now = Date.now()
|
||||
if (now > entry.timestamp + entry.revalidate * 1000) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return entry
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### `set()`
|
||||
|
||||
Store a cache entry for the given cache key.
|
||||
|
||||
```ts
|
||||
set(cacheKey: string, pendingEntry: Promise<CacheEntry>): Promise<void>
|
||||
```
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| -------------- | --------------------- | ------------------------------------------- |
|
||||
| `cacheKey` | `string` | The unique key to store the entry under. |
|
||||
| `pendingEntry` | `Promise<CacheEntry>` | A promise that resolves to the cache entry. |
|
||||
|
||||
The entry may still be pending when this is called (i.e., its value stream may still be written to). Your handler should await the promise before processing the entry.
|
||||
|
||||
Returns `Promise<void>`.
|
||||
|
||||
Your `set` method must await the `pendingEntry` promise before storing it, since the cache entry may still be generating when this method is called. Once resolved, store the entry in your cache system.
|
||||
|
||||
```js
|
||||
const cacheHandler = {
|
||||
async set(cacheKey, pendingEntry) {
|
||||
// Wait for the entry to be ready
|
||||
const entry = await pendingEntry
|
||||
|
||||
// Store in your cache system
|
||||
cache.set(cacheKey, entry)
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### `refreshTags()`
|
||||
|
||||
Called periodically before starting a new request to sync with external tag services.
|
||||
|
||||
```ts
|
||||
refreshTags(): Promise<void>
|
||||
```
|
||||
|
||||
This is useful if you're coordinating cache invalidation across multiple instances or services. For in-memory caches, this can be a no-op.
|
||||
|
||||
Returns `Promise<void>`.
|
||||
|
||||
For in-memory caches, this can be a no-op. For distributed caches, use this to sync tag state from an external service or database before processing requests.
|
||||
|
||||
```js
|
||||
const cacheHandler = {
|
||||
async refreshTags() {
|
||||
// For in-memory cache, no action needed
|
||||
// For distributed cache, sync tag state from external service
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### `getExpiration()`
|
||||
|
||||
Get the maximum revalidation timestamp for a set of tags.
|
||||
|
||||
```ts
|
||||
getExpiration(tags: string[]): Promise<number>
|
||||
```
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---------- | -------------------------------------- |
|
||||
| `tags` | `string[]` | Array of tags to check expiration for. |
|
||||
|
||||
Returns:
|
||||
|
||||
- `0` if none of the tags were ever revalidated
|
||||
- A timestamp (in milliseconds) representing the most recent revalidation
|
||||
- `Infinity` to indicate soft tags should be checked in the `get` method instead
|
||||
|
||||
If you're not tracking tag revalidation timestamps, return `0`. Otherwise, find the most recent revalidation timestamp across all the provided tags. Return `Infinity` if you prefer to handle soft tag checking in the `get` method.
|
||||
|
||||
```js
|
||||
const cacheHandler = {
|
||||
async getExpiration(tags) {
|
||||
// Return 0 if not tracking tag revalidation
|
||||
return 0
|
||||
|
||||
// Or return the most recent revalidation timestamp
|
||||
// return Math.max(...tags.map(tag => tagTimestamps.get(tag) || 0));
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### `updateTags()`
|
||||
|
||||
Called when tags are revalidated or expired.
|
||||
|
||||
```ts
|
||||
updateTags(tags: string[], durations?: { expire?: number }): Promise<void>
|
||||
```
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| ----------- | --------------------- | ---------------------------------------- |
|
||||
| `tags` | `string[]` | Array of tags to update. |
|
||||
| `durations` | `{ expire?: number }` | Optional expiration duration in seconds. |
|
||||
|
||||
Your handler should update its internal state to mark these tags as invalidated.
|
||||
|
||||
Returns `Promise<void>`.
|
||||
|
||||
When tags are revalidated, your handler should invalidate all cache entries that have any of those tags. Iterate through your cache and remove entries whose tags match the provided list.
|
||||
|
||||
```js
|
||||
const cacheHandler = {
|
||||
async updateTags(tags, durations) {
|
||||
// Invalidate all cache entries with matching tags
|
||||
for (const [key, entry] of cache.entries()) {
|
||||
if (entry.tags.some((tag) => tags.includes(tag))) {
|
||||
cache.delete(key)
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## CacheEntry Type
|
||||
|
||||
The [`CacheEntry`](https://github.com/vercel/next.js/blob/canary/packages/next/src/server/lib/cache-handlers/types.ts) object has the following structure:
|
||||
|
||||
```ts
|
||||
interface CacheEntry {
|
||||
value: ReadableStream<Uint8Array>
|
||||
tags: string[]
|
||||
stale: number
|
||||
timestamp: number
|
||||
expire: number
|
||||
revalidate: number
|
||||
}
|
||||
```
|
||||
|
||||
| Property | Type | Description |
|
||||
| ------------ | ---------------------------- | ------------------------------------------------------------ |
|
||||
| `value` | `ReadableStream<Uint8Array>` | The cached data as a stream. |
|
||||
| `tags` | `string[]` | Cache tags (excluding soft tags). |
|
||||
| `stale` | `number` | Duration in seconds for client-side staleness. |
|
||||
| `timestamp` | `number` | When the entry was created (timestamp in milliseconds). |
|
||||
| `expire` | `number` | How long the entry is allowed to be used (in seconds). |
|
||||
| `revalidate` | `number` | How long until the entry should be revalidated (in seconds). |
|
||||
|
||||
> **Good to know**:
|
||||
>
|
||||
> - The `value` is a [`ReadableStream`](https://developer.mozilla.org/docs/Web/API/ReadableStream). Use [`.tee()`](https://developer.mozilla.org/docs/Web/API/ReadableStream/tee) if you need to read and store the stream data.
|
||||
> - If the stream errors with partial data, your handler must decide whether to keep the partial cache or discard it.
|
||||
|
||||
## Examples
|
||||
|
||||
### Basic in-memory cache handler
|
||||
|
||||
Here's a minimal implementation using a `Map` for storage. This example demonstrates the core concepts, but for a production-ready implementation with LRU eviction, error handling, and tag management, see the [default cache handler](https://github.com/vercel/next.js/blob/canary/packages/next/src/server/lib/cache-handlers/default.ts).
|
||||
|
||||
```js filename="cache-handlers/memory-handler.js"
|
||||
const cache = new Map()
|
||||
const pendingSets = new Map()
|
||||
|
||||
module.exports = {
|
||||
async get(cacheKey, softTags) {
|
||||
// Wait for any pending set operation to complete
|
||||
const pendingPromise = pendingSets.get(cacheKey)
|
||||
if (pendingPromise) {
|
||||
await pendingPromise
|
||||
}
|
||||
|
||||
const entry = cache.get(cacheKey)
|
||||
if (!entry) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
// Check if entry has expired
|
||||
const now = Date.now()
|
||||
if (now > entry.timestamp + entry.revalidate * 1000) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return entry
|
||||
},
|
||||
|
||||
async set(cacheKey, pendingEntry) {
|
||||
// Create a promise to track this set operation
|
||||
let resolvePending
|
||||
const pendingPromise = new Promise((resolve) => {
|
||||
resolvePending = resolve
|
||||
})
|
||||
pendingSets.set(cacheKey, pendingPromise)
|
||||
|
||||
try {
|
||||
// Wait for the entry to be ready
|
||||
const entry = await pendingEntry
|
||||
|
||||
// Store the entry in the cache
|
||||
cache.set(cacheKey, entry)
|
||||
} finally {
|
||||
resolvePending()
|
||||
pendingSets.delete(cacheKey)
|
||||
}
|
||||
},
|
||||
|
||||
async refreshTags() {
|
||||
// No-op for in-memory cache
|
||||
},
|
||||
|
||||
async getExpiration(tags) {
|
||||
// Return 0 to indicate no tags have been revalidated
|
||||
return 0
|
||||
},
|
||||
|
||||
async updateTags(tags, durations) {
|
||||
// Implement tag-based invalidation
|
||||
for (const [key, entry] of cache.entries()) {
|
||||
if (entry.tags.some((tag) => tags.includes(tag))) {
|
||||
cache.delete(key)
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### External storage pattern
|
||||
|
||||
For durable storage like Redis or a database, you'll need to serialize the cache entries. Here's a simple Redis example:
|
||||
|
||||
```js filename="cache-handlers/redis-handler.js"
|
||||
const { createClient } = require('redis')
|
||||
|
||||
const client = createClient({ url: process.env.REDIS_URL })
|
||||
client.connect()
|
||||
|
||||
module.exports = {
|
||||
async get(cacheKey, softTags) {
|
||||
// Retrieve from Redis
|
||||
const stored = await client.get(cacheKey)
|
||||
if (!stored) return undefined
|
||||
|
||||
// Deserialize the entry
|
||||
const data = JSON.parse(stored)
|
||||
|
||||
// Reconstruct the ReadableStream from stored data
|
||||
return {
|
||||
value: new ReadableStream({
|
||||
start(controller) {
|
||||
controller.enqueue(Buffer.from(data.value, 'base64'))
|
||||
controller.close()
|
||||
},
|
||||
}),
|
||||
tags: data.tags,
|
||||
stale: data.stale,
|
||||
timestamp: data.timestamp,
|
||||
expire: data.expire,
|
||||
revalidate: data.revalidate,
|
||||
}
|
||||
},
|
||||
|
||||
async set(cacheKey, pendingEntry) {
|
||||
const entry = await pendingEntry
|
||||
|
||||
// Read the stream to get the data
|
||||
const reader = entry.value.getReader()
|
||||
const chunks = []
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
const { done, value } = await reader.read()
|
||||
if (done) break
|
||||
chunks.push(value)
|
||||
}
|
||||
} finally {
|
||||
reader.releaseLock()
|
||||
}
|
||||
|
||||
// Combine chunks and serialize for Redis storage
|
||||
const data = Buffer.concat(chunks.map((chunk) => Buffer.from(chunk)))
|
||||
|
||||
await client.set(
|
||||
cacheKey,
|
||||
JSON.stringify({
|
||||
value: data.toString('base64'),
|
||||
tags: entry.tags,
|
||||
stale: entry.stale,
|
||||
timestamp: entry.timestamp,
|
||||
expire: entry.expire,
|
||||
revalidate: entry.revalidate,
|
||||
}),
|
||||
{ EX: entry.expire } // Use Redis TTL for automatic expiration
|
||||
)
|
||||
},
|
||||
|
||||
async refreshTags() {
|
||||
// No-op for basic Redis implementation
|
||||
// Could sync with external tag service if needed
|
||||
},
|
||||
|
||||
async getExpiration(tags) {
|
||||
// Return 0 to indicate no tags have been revalidated
|
||||
// Could query Redis for tag expiration timestamps if tracking them
|
||||
return 0
|
||||
},
|
||||
|
||||
async updateTags(tags, durations) {
|
||||
// Implement tag-based invalidation if needed
|
||||
// Could iterate over keys with matching tags and delete them
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Distributed Tag Coordination
|
||||
|
||||
When running multiple Next.js instances, tag invalidation must be coordinated across instances. The default in-memory handler only tracks tags locally, so calling `revalidateTag()` on one instance does not affect others.
|
||||
|
||||
To coordinate tags across instances:
|
||||
|
||||
1. **`updateTags()`** is called when `revalidateTag()` is invoked. Your handler should write the invalidation timestamp to shared storage.
|
||||
2. **`refreshTags()`** is called before each request. Your handler should read recent invalidation events from shared storage and update its local tag state.
|
||||
3. **`getExpiration()`** returns the most recent revalidation timestamp across all provided tags. The default implementation returns `Math.max(...timestamps, 0)`.
|
||||
|
||||
Here's an example using Redis for distributed tag coordination:
|
||||
|
||||
```js filename="cache-handlers/distributed-tags.js"
|
||||
const { createClient } = require('redis')
|
||||
|
||||
const client = createClient({ url: process.env.REDIS_URL })
|
||||
client.connect()
|
||||
|
||||
// Local cache of tag timestamps, synced via refreshTags
|
||||
const localTagTimestamps = new Map()
|
||||
|
||||
module.exports = {
|
||||
// ... get() and set() methods ...
|
||||
|
||||
async refreshTags() {
|
||||
// Sync tag invalidation timestamps from Redis
|
||||
// Using a dedicated set to track tag keys avoids scanning the keyspace
|
||||
const tagKeys = await client.sMembers('revalidated-tags')
|
||||
if (tagKeys.length > 0) {
|
||||
const values = await client.mGet(tagKeys.map((k) => `tag:${k}`))
|
||||
for (let i = 0; i < tagKeys.length; i++) {
|
||||
localTagTimestamps.set(tagKeys[i], Number(values[i]))
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
async getExpiration(tags) {
|
||||
const timestamps = tags.map((tag) => localTagTimestamps.get(tag) || 0)
|
||||
return Math.max(...timestamps, 0)
|
||||
},
|
||||
|
||||
async updateTags(tags, durations) {
|
||||
const now = Date.now()
|
||||
const pipeline = client.multi()
|
||||
for (const tag of tags) {
|
||||
pipeline.set(`tag:${tag}`, String(now))
|
||||
pipeline.sAdd('revalidated-tags', tag)
|
||||
localTagTimestamps.set(tag, now)
|
||||
}
|
||||
await pipeline.exec()
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
For a full explanation of the tag architecture (including soft tags and multi-instance considerations), see [How Revalidation Works](/docs/app/guides/how-revalidation-works).
|
||||
|
||||
## Soft Tags
|
||||
|
||||
Soft tags are implicit tags that Next.js automatically generates based on the route path. For example, the route `/blog/hello` generates soft tags for `/`, `/blog`, `/blog/hello`, and their corresponding layout entries. These tags are prefixed internally with `_N_T_`.
|
||||
|
||||
Soft tags enable [`revalidatePath()`](/docs/app/api-reference/functions/revalidatePath) to work through the same tag-based cache system. When `revalidatePath('/blog/hello')` is called, it invalidates all cache entries associated with that path's soft tags.
|
||||
|
||||
In the cache handler API, soft tags are passed to the [`get()`](#get) method as the `softTags` parameter. Your handler should check whether any soft tag has been invalidated (via `getExpiration()` or direct timestamp comparison) after the cache entry's `timestamp`. If a soft tag was invalidated more recently than the entry was created, the entry should be treated as stale.
|
||||
|
||||
## Handling Streams
|
||||
|
||||
The `CacheEntry.value` is a [`ReadableStream<Uint8Array>`](https://developer.mozilla.org/docs/Web/API/ReadableStream). When implementing a cache handler that stores entries externally, keep in mind:
|
||||
|
||||
- **Use `.tee()`** if you need to both store and return the stream. One branch goes to storage, the other is returned to the caller.
|
||||
- **Memory implications**: large pages produce large cache entries. For S3-like storage backends, consider streaming directly to storage without buffering the entire entry in memory.
|
||||
- **Partial writes**: the stream may error partway through rendering. Your handler should decide whether to keep partial entries or discard them. Discarding is safer, as partial entries can produce incomplete pages.
|
||||
|
||||
## Error Handling
|
||||
|
||||
Cache operations should be implemented defensively:
|
||||
|
||||
- **`set()` failure**: the response is still served to the user because `set()` is called asynchronously after the response stream is already flowing. The cache entry is lost, and the next request triggers a fresh render.
|
||||
- **`get()` failure**: your handler should catch internal errors and return `undefined` (the "cache miss" signal). The framework does not wrap `get()` in a try/catch, so an unhandled exception from `get()` will propagate as a render error.
|
||||
- **Partial writes**: if a cache entry is partially written and then read, the behavior is undefined. Use atomic writes or a write-then-rename pattern to avoid serving partial entries.
|
||||
|
||||
## Platform Support
|
||||
|
||||
| Deployment Option | Supported |
|
||||
| ------------------------------------------------------------------- | ----------------- |
|
||||
| [Node.js server](/docs/app/getting-started/deploying#nodejs-server) | Yes |
|
||||
| [Docker container](/docs/app/getting-started/deploying#docker) | Yes |
|
||||
| [Static export](/docs/app/getting-started/deploying#static-export) | No |
|
||||
| [Adapters](/docs/app/getting-started/deploying#adapters) | Platform-specific |
|
||||
|
||||
## Version History
|
||||
|
||||
| Version | Changes |
|
||||
| --------- | --------------------------- |
|
||||
| `v16.0.0` | `cacheHandlers` introduced. |
|
||||
Generated
Vendored
+81
@@ -0,0 +1,81 @@
|
||||
---
|
||||
title: cacheLife
|
||||
description: Learn how to set up cacheLife configurations in Next.js.
|
||||
related:
|
||||
title: Related
|
||||
description: View related API references.
|
||||
links:
|
||||
- app/api-reference/directives/use-cache
|
||||
- app/api-reference/config/next-config-js/cacheHandlers
|
||||
- app/api-reference/functions/cacheLife
|
||||
---
|
||||
|
||||
The `cacheLife` option allows you to define **custom cache profiles** when using the [`cacheLife`](/docs/app/api-reference/functions/cacheLife) function inside components or functions, and within the scope of the [`use cache` directive](/docs/app/api-reference/directives/use-cache).
|
||||
|
||||
## Usage
|
||||
|
||||
To define a profile, enable the [`cacheComponents` flag](/docs/app/api-reference/config/next-config-js/cacheComponents) and add the cache profile in the `cacheLife` object in the `next.config.js` file. For example, a `blog` profile:
|
||||
|
||||
```ts filename="next.config.ts" switcher
|
||||
import type { NextConfig } from 'next'
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
cacheComponents: true,
|
||||
cacheLife: {
|
||||
blog: {
|
||||
stale: 3600, // 1 hour
|
||||
revalidate: 900, // 15 minutes
|
||||
expire: 86400, // 1 day
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export default nextConfig
|
||||
```
|
||||
|
||||
```js filename="next.config.js" switcher
|
||||
module.exports = {
|
||||
cacheComponents: true,
|
||||
cacheLife: {
|
||||
blog: {
|
||||
stale: 3600, // 1 hour
|
||||
revalidate: 900, // 15 minutes
|
||||
expire: 86400, // 1 day
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
You can now use this custom `blog` configuration in your component or function as follows:
|
||||
|
||||
```tsx filename="app/actions.ts" highlight={4,5} switcher
|
||||
import { cacheLife } from 'next/cache'
|
||||
|
||||
export async function getCachedData() {
|
||||
'use cache'
|
||||
cacheLife('blog')
|
||||
const data = await fetch('/api/data')
|
||||
return data
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/actions.js" highlight={4,5} switcher
|
||||
import { cacheLife } from 'next/cache'
|
||||
|
||||
export async function getCachedData() {
|
||||
'use cache'
|
||||
cacheLife('blog')
|
||||
const data = await fetch('/api/data')
|
||||
return data
|
||||
}
|
||||
```
|
||||
|
||||
## Reference
|
||||
|
||||
The configuration object has key values with the following format:
|
||||
|
||||
| **Property** | **Value** | **Description** | **Requirement** |
|
||||
| ------------ | --------- | --------------------------------------------------------------------------------------------------------- | ------------------------------------------- |
|
||||
| `stale` | `number` | Duration the client should cache a value without checking the server. | Optional |
|
||||
| `revalidate` | `number` | Frequency at which the cache should refresh on the server; stale values may be served while revalidating. | Optional |
|
||||
| `expire` | `number` | Maximum duration for which a value can remain stale before switching to dynamic. | Optional - Must be longer than `revalidate` |
|
||||
frontend/node_modules/next/dist/docs/01-app/03-api-reference/05-config/01-next-config-js/compress.md
Generated
Vendored
+22
@@ -0,0 +1,22 @@
|
||||
---
|
||||
title: compress
|
||||
description: Next.js provides gzip compression to compress rendered content and static files, it only works with the server target. Learn more about it here.
|
||||
---
|
||||
|
||||
{/* The content of this doc is shared between the app and pages router. You can use the `<PagesOnly>Content</PagesOnly>` component to add content that is specific to the Pages Router. Any shared content should not be wrapped in a component. */}
|
||||
|
||||
By default, Next.js uses `gzip` to compress rendered content and static files when using `next start` or a custom server. This is an optimization for applications that do not have compression configured. If compression is _already_ configured in your application via a custom server, Next.js will not add compression.
|
||||
|
||||
You can check if compression is enabled and which algorithm is used by looking at the [`Accept-Encoding`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Encoding) (browser accepted options) and [`Content-Encoding`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Encoding) (currently used) headers in the response.
|
||||
|
||||
## Disabling compression
|
||||
|
||||
To disable **compression**, set the `compress` config option to `false`:
|
||||
|
||||
```js filename="next.config.js"
|
||||
module.exports = {
|
||||
compress: false,
|
||||
}
|
||||
```
|
||||
|
||||
We **do not recommend disabling compression** unless you have compression configured on your server, as compression reduces bandwidth usage and improves the performance of your application. For example, you're using [nginx](https://nginx.org/) and want to switch to `brotli`, set the `compress` option to `false` to allow nginx to handle compression.
|
||||
Generated
Vendored
+19
@@ -0,0 +1,19 @@
|
||||
---
|
||||
title: crossOrigin
|
||||
description: Use the `crossOrigin` option to add a crossOrigin tag on the `script` tags generated by `next/script`.
|
||||
---
|
||||
|
||||
{/* The content of this doc is shared between the app and pages router. You can use the `<PagesOnly>Content</PagesOnly>` component to add content that is specific to the Pages Router. Any shared content should not be wrapped in a component. */}
|
||||
|
||||
Use the `crossOrigin` option to add a [`crossOrigin` attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/crossorigin) in all `<script>` tags generated by the <AppOnly>[`next/script`](/docs/app/guides/scripts) component</AppOnly> <PagesOnly>[`next/script`](/docs/pages/guides/scripts) and [`next/head`](/docs/pages/api-reference/components/head)components</PagesOnly>, and define how cross-origin requests should be handled.
|
||||
|
||||
```js filename="next.config.js"
|
||||
module.exports = {
|
||||
crossOrigin: 'anonymous',
|
||||
}
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
- `'anonymous'`: Adds [`crossOrigin="anonymous"`](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/crossorigin#anonymous) attribute.
|
||||
- `'use-credentials'`: Adds [`crossOrigin="use-credentials"`](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/crossorigin#use-credentials).
|
||||
Generated
Vendored
+42
@@ -0,0 +1,42 @@
|
||||
---
|
||||
title: cssChunking
|
||||
description: Use the `cssChunking` option to control how CSS files are chunked in your Next.js application.
|
||||
version: experimental
|
||||
---
|
||||
|
||||
CSS Chunking is a strategy used to improve the performance of your web application by splitting and re-ordering CSS files into chunks. This allows you to load only the CSS that is needed for a specific route, instead of loading all the application's CSS at once.
|
||||
|
||||
You can control how CSS files are chunked using the `experimental.cssChunking` option in your `next.config.js` file:
|
||||
|
||||
```tsx filename="next.config.ts" switcher
|
||||
import type { NextConfig } from 'next'
|
||||
|
||||
const nextConfig = {
|
||||
experimental: {
|
||||
cssChunking: true, // default
|
||||
},
|
||||
} satisfies NextConfig
|
||||
|
||||
export default nextConfig
|
||||
```
|
||||
|
||||
```js filename="next.config.js" switcher
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
experimental: {
|
||||
cssChunking: true, // default
|
||||
},
|
||||
}
|
||||
|
||||
module.exports = nextConfig
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
- **`true` (default)**: Next.js will try to merge CSS files whenever possible, determining explicit and implicit dependencies between files from import order to reduce the number of chunks and therefore the number of requests.
|
||||
- **`false`**: Next.js will not attempt to merge or re-order your CSS files.
|
||||
- **`'strict'`**: Next.js will load CSS files in the correct order they are imported into your files, which can lead to more chunks and requests.
|
||||
|
||||
You may consider using `'strict'` if you run into unexpected CSS behavior. For example, if you import `a.css` and `b.css` in different files using a different `import` order (`a` before `b`, or `b` before `a`), `true` will merge the files in any order and assume there are no dependencies between them. However, if `b.css` depends on `a.css`, you may want to use `'strict'` to prevent the files from being merged, and instead, load them in the order they are imported - which can result in more chunks and requests.
|
||||
|
||||
For most applications, we recommend `true` as it leads to fewer requests and better performance.
|
||||
Generated
Vendored
+80
@@ -0,0 +1,80 @@
|
||||
---
|
||||
title: deploymentId
|
||||
description: Configure a deployment identifier used for version skew protection and cache busting.
|
||||
---
|
||||
|
||||
{/* The content of this doc is shared between the app and pages router. You can use the `<PagesOnly>Content</PagesOnly>` component to add content that is specific to the Pages Router. Any shared content should not be wrapped in a component. */}
|
||||
|
||||
The `deploymentId` option allows you to set an identifier for your deployment. This identifier is used for [version skew](/docs/app/guides/self-hosting#version-skew) protection and cache busting during rolling deployments.
|
||||
|
||||
```js filename="next.config.js"
|
||||
module.exports = {
|
||||
deploymentId: 'my-deployment-id',
|
||||
}
|
||||
```
|
||||
|
||||
You can also set the deployment ID using the `NEXT_DEPLOYMENT_ID` environment variable:
|
||||
|
||||
```bash
|
||||
NEXT_DEPLOYMENT_ID=my-deployment-id next build
|
||||
```
|
||||
|
||||
> **Good to know:** If both are set, the `deploymentId` value in `next.config.js` takes precedence over the `NEXT_DEPLOYMENT_ID` environment variable.
|
||||
|
||||
## How it works
|
||||
|
||||
When a `deploymentId` is configured, Next.js:
|
||||
|
||||
1. Appends `?dpl=<deploymentId>` to static asset URLs (JavaScript, CSS, images)
|
||||
2. Adds an `x-deployment-id` header to client-side navigation requests
|
||||
3. Adds an `x-nextjs-deployment-id` header to navigation responses
|
||||
4. Injects a `data-dpl-id` attribute on the `<html>` element
|
||||
|
||||
When the client detects a mismatch between its deployment ID and the server's (via the response header), it triggers a hard navigation (full page reload) instead of a client-side navigation. This ensures users always receive assets <AppOnly>and Server Functions</AppOnly> from a consistent deployment version.
|
||||
|
||||
> **Good to know:** Next.js does not read the `?dpl=` query parameter on incoming requests. The query parameter is for cache busting (ensuring browsers and CDNs fetch fresh assets), not for routing. If you need version-aware routing, consult your hosting provider or CDN's documentation for implementing deployment-based routing.
|
||||
|
||||
## Use cases
|
||||
|
||||
### Rolling deployments
|
||||
|
||||
During a rolling deployment, some server instances may be running the new version while others are still running the old version. Without a deployment ID, users might receive a mix of old and new assets, causing errors.
|
||||
|
||||
Setting a consistent `deploymentId` per deployment ensures:
|
||||
|
||||
<AppOnly>
|
||||
|
||||
- Clients always request assets from a matching deployment version
|
||||
- Mismatches trigger a full reload to fetch the correct assets
|
||||
- Server Functions work correctly across deployment boundaries
|
||||
|
||||
</AppOnly>
|
||||
|
||||
<PagesOnly>
|
||||
|
||||
- Clients always request assets from a matching deployment version
|
||||
- Mismatches trigger a full reload to fetch the correct assets
|
||||
|
||||
</PagesOnly>
|
||||
|
||||
### Multi-server environments
|
||||
|
||||
When running multiple instances of your Next.js application behind a load balancer, all instances for the same deployment should use the same `deploymentId`.
|
||||
|
||||
```js filename="next.config.js"
|
||||
module.exports = {
|
||||
deploymentId: process.env.DEPLOYMENT_VERSION || process.env.GIT_SHA,
|
||||
}
|
||||
```
|
||||
|
||||
## Version History
|
||||
|
||||
| Version | Changes |
|
||||
| ---------- | ----------------------------------------------------- |
|
||||
| `v14.1.4` | `deploymentId` stabilized as top-level config option. |
|
||||
| `v13.4.10` | `experimental.deploymentId` introduced. |
|
||||
|
||||
## Related
|
||||
|
||||
- [Self-Hosting - Version Skew](/docs/app/guides/self-hosting#version-skew)
|
||||
- [generateBuildId](/docs/app/api-reference/config/next-config-js/generateBuildId)
|
||||
Generated
Vendored
+59
@@ -0,0 +1,59 @@
|
||||
---
|
||||
title: devIndicators
|
||||
description: Configuration options for the on-screen indicator that gives context about the current route you're viewing during development.
|
||||
---
|
||||
|
||||
`devIndicators` allows you to configure the on-screen indicator that gives context about the current route you're viewing during development.
|
||||
|
||||
```ts filename="Types"
|
||||
devIndicators: false | {
|
||||
position?: 'bottom-right'
|
||||
| 'bottom-left'
|
||||
| 'top-right'
|
||||
| 'top-left', // defaults to 'bottom-left',
|
||||
},
|
||||
```
|
||||
|
||||
Setting `devIndicators` to `false` will hide the indicator, however Next.js will continue to surface any build or runtime errors that were encountered.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Indicator not marking a route as static
|
||||
|
||||
If you expect a route to be static and the indicator has marked it as dynamic, it's likely the route has opted out of prerendering.
|
||||
|
||||
You can confirm if a route is [prerendered](/docs/app/glossary#prerendering) or [dynamically rendered](/docs/app/glossary#dynamic-rendering) by building your application using `next build --debug`, and checking the output in your terminal. Static (or prerendered) routes will display a `○` symbol, whereas dynamic routes will display a `ƒ` symbol. For example:
|
||||
|
||||
```bash filename="Build Output"
|
||||
Route (app)
|
||||
┌ ○ /_not-found
|
||||
└ ƒ /products/[id]
|
||||
|
||||
○ (Static) prerendered as static content
|
||||
ƒ (Dynamic) server-rendered on demand
|
||||
```
|
||||
|
||||
<AppOnly>
|
||||
|
||||
There are two reasons a route might opt out of prerendering:
|
||||
|
||||
- The presence of [Request-time APIs](/docs/app/glossary#request-time-apis) which rely on request information.
|
||||
- An [uncached data request](/docs/app/getting-started/fetching-data), like a call to an ORM or database driver.
|
||||
|
||||
Check your route for any of these conditions, and if you are not able to statically render the route, then consider using [`loading.js`](/docs/app/api-reference/file-conventions/loading) or [`<Suspense />`](https://react.dev/reference/react/Suspense) to leverage [streaming](/docs/app/getting-started/linking-and-navigating#streaming).
|
||||
|
||||
</AppOnly>
|
||||
|
||||
<PagesOnly>
|
||||
|
||||
When exporting [`getServerSideProps`](/docs/pages/building-your-application/data-fetching/get-server-side-props) or [`getInitialProps`](/docs/pages/api-reference/functions/get-initial-props) from a page, it will be marked as dynamic.
|
||||
|
||||
</PagesOnly>
|
||||
|
||||
## Version History
|
||||
|
||||
| Version | Changes |
|
||||
| --------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `v16.0.0` | `appIsrStatus`, `buildActivity`, and `buildActivityPosition` options have been removed. |
|
||||
| `v15.2.0` | Improved on-screen indicator with new `position` option. `appIsrStatus`, `buildActivity`, and `buildActivityPosition` options have been deprecated. |
|
||||
| `v15.0.0` | Static on-screen indicator added with `appIsrStatus` option. |
|
||||
Generated
Vendored
+20
@@ -0,0 +1,20 @@
|
||||
---
|
||||
title: distDir
|
||||
description: Set a custom build directory to use instead of the default .next directory.
|
||||
---
|
||||
|
||||
{/* The content of this doc is shared between the app and pages router. You can use the `<PagesOnly>Content</PagesOnly>` component to add content that is specific to the Pages Router. Any shared content should not be wrapped in a component. */}
|
||||
|
||||
You can specify a name to use for a custom build directory to use instead of `.next`.
|
||||
|
||||
Open `next.config.js` and add the `distDir` config:
|
||||
|
||||
```js filename="next.config.js"
|
||||
module.exports = {
|
||||
distDir: 'build',
|
||||
}
|
||||
```
|
||||
|
||||
Now if you run `next build` Next.js will use `build` instead of the default `.next` folder.
|
||||
|
||||
> `distDir` **should not** leave your project directory. For example, `../build` is an **invalid** directory.
|
||||
Generated
Vendored
+65
@@ -0,0 +1,65 @@
|
||||
---
|
||||
title: env
|
||||
description: Learn to add and access environment variables in your Next.js application at build time.
|
||||
version: legacy
|
||||
---
|
||||
|
||||
{/* The content of this doc is shared between the app and pages router. You can use the `<PagesOnly>Content</PagesOnly>` component to add content that is specific to the Pages Router. Any shared content should not be wrapped in a component. */}
|
||||
|
||||
<AppOnly>
|
||||
|
||||
> Since the release of [Next.js 9.4](https://nextjs.org/blog/next-9-4) we now have a more intuitive and ergonomic experience for [adding environment variables](/docs/app/guides/environment-variables). Give it a try!
|
||||
|
||||
</AppOnly>
|
||||
|
||||
<PagesOnly>
|
||||
|
||||
> Since the release of [Next.js 9.4](https://nextjs.org/blog/next-9-4) we now have a more intuitive and ergonomic experience for [adding environment variables](/docs/pages/guides/environment-variables). Give it a try!
|
||||
|
||||
</PagesOnly>
|
||||
|
||||
<AppOnly>
|
||||
|
||||
> **Good to know**: environment variables specified in this way will **always** be included in the JavaScript bundle, prefixing the environment variable name with `NEXT_PUBLIC_` only has an effect when specifying them [through the environment or .env files](/docs/app/guides/environment-variables).
|
||||
|
||||
</AppOnly>
|
||||
|
||||
<PagesOnly>
|
||||
|
||||
> **Good to know**: environment variables specified in this way will **always** be included in the JavaScript bundle, prefixing the environment variable name with `NEXT_PUBLIC_` only has an effect when specifying them [through the environment or .env files](/docs/pages/guides/environment-variables).
|
||||
|
||||
</PagesOnly>
|
||||
|
||||
To add environment variables to the JavaScript bundle, open `next.config.js` and add the `env` config:
|
||||
|
||||
```js filename="next.config.js"
|
||||
module.exports = {
|
||||
env: {
|
||||
customKey: 'my-value',
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Now you can access `process.env.customKey` in your code. For example:
|
||||
|
||||
```jsx
|
||||
function Page() {
|
||||
return <h1>The value of customKey is: {process.env.customKey}</h1>
|
||||
}
|
||||
|
||||
export default Page
|
||||
```
|
||||
|
||||
Next.js will replace `process.env.customKey` with `'my-value'` at build time. Trying to destructure `process.env` variables won't work due to the nature of webpack [DefinePlugin](https://webpack.js.org/plugins/define-plugin/).
|
||||
|
||||
For example, the following line:
|
||||
|
||||
```jsx
|
||||
return <h1>The value of customKey is: {process.env.customKey}</h1>
|
||||
```
|
||||
|
||||
Will end up being:
|
||||
|
||||
```jsx
|
||||
return <h1>The value of customKey is: {'my-value'}</h1>
|
||||
```
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user