This commit is contained in:
Kismet Hasanaj
2026-05-02 20:07:02 +02:00
parent ce8672e283
commit 34dc9aec52
9428 changed files with 1733330 additions and 0 deletions
+90
View File
@@ -0,0 +1,90 @@
---
title: How to set up your Next.js project for AI coding agents
nav_title: AI Coding Agents
description: Learn how to configure your Next.js project so AI coding agents use up-to-date documentation instead of outdated training data.
related:
title: Next Steps
links:
- app/guides/mcp
---
Next.js ships version-matched documentation inside the `next` package, allowing AI coding agents to reference accurate, up-to-date APIs and patterns. An `AGENTS.md` file at the root of your project directs agents to these bundled docs instead of their training data.
## How it works
When you install `next`, the Next.js documentation is bundled at `node_modules/next/dist/docs/`. The bundled docs mirror the structure of the [Next.js documentation site](https://nextjs.org/docs):
```txt
node_modules/next/dist/docs/
├── 01-app/
│ ├── 01-getting-started/
│ ├── 02-guides/
│ └── 03-api-reference/
├── 02-pages/
├── 03-architecture/
└── index.mdx
```
This means agents always have access to docs that match your installed version — no network request or external lookup required.
The `AGENTS.md` file at the root of your project tells agents to read these bundled docs before writing any code. Most AI coding agents — including Claude Code, Cursor, GitHub Copilot, and others — automatically read `AGENTS.md` when they start a session.
## Getting started
### New projects
[`create-next-app`](/docs/app/api-reference/cli/create-next-app) generates `AGENTS.md` and `CLAUDE.md` automatically. No additional setup is needed:
```bash package="pnpm"
pnpm create next-app@canary
```
```bash package="npm"
npx create-next-app@canary
```
```bash package="yarn"
yarn create next-app@canary
```
```bash package="bun"
bun create next-app@canary
```
If you don't want the agent files, pass `--no-agents-md`:
```bash
npx create-next-app@canary --no-agents-md
```
### Existing projects
Ensure you are on Next.js `v16.2.0-canary.37` or later, then add the following files to the root of your project.
`AGENTS.md` contains the instructions that agents will read:
```md filename="AGENTS.md"
<!-- BEGIN:nextjs-agent-rules -->
# Next.js: ALWAYS read docs before coding
Before any Next.js work, find and read the relevant doc in `node_modules/next/dist/docs/`. Your training data is outdated — the docs are the source of truth.
<!-- END:nextjs-agent-rules -->
```
`CLAUDE.md` uses the `@` import syntax to include `AGENTS.md`, so [Claude Code](https://docs.anthropic.com/en/docs/claude-code) users get the same instructions without duplicating content:
```md filename="CLAUDE.md"
@AGENTS.md
```
## Understanding AGENTS.md
The default `AGENTS.md` contains a single, focused instruction: **read the bundled docs before writing code**. This is intentionally minimal — the goal is to redirect agents from stale training data to the accurate, version-matched documentation in `node_modules/next/dist/docs/`.
The `<!-- BEGIN:nextjs-agent-rules -->` and `<!-- END:nextjs-agent-rules -->` comment markers delimit the Next.js-managed section. You can add your own project-specific instructions outside these markers without worrying about them being overwritten by future updates.
The bundled docs include guides, API references, and file conventions for the App Router and Pages Router. When an agent encounters a task involving routing, data fetching, or any other Next.js feature, it can look up the correct API in the bundled docs rather than relying on potentially outdated training data.
> **Good to know:** To see how bundled docs and `AGENTS.md` improve agent performance on real-world Next.js tasks, visit the [benchmark results](https://nextjs.org/evals).
+238
View File
@@ -0,0 +1,238 @@
---
title: How to add analytics to your Next.js application
nav_title: Analytics
description: Measure and track page performance using Next.js Speed Insights
---
{/* 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 has built-in support for measuring and reporting performance metrics. You can either use the [`useReportWebVitals`](/docs/app/api-reference/functions/use-report-web-vitals) hook to manage reporting yourself, or alternatively, Vercel provides a [managed service](https://vercel.com/analytics?utm_source=next-site&utm_medium=docs&utm_campaign=next-website) to automatically collect and visualize metrics for you.
## Client Instrumentation
For more advanced analytics and monitoring needs, Next.js provides a `instrumentation-client.js|ts` file that runs before your application's frontend code starts executing. This is ideal for setting up global analytics, error tracking, or performance monitoring tools.
To use it, create an `instrumentation-client.js` or `instrumentation-client.ts` file in your application's root directory:
```js filename="instrumentation-client.js"
// Initialize analytics before the app starts
console.log('Analytics initialized')
// Set up global error tracking
window.addEventListener('error', (event) => {
// Send to your error tracking service
reportError(event.error)
})
```
## Build Your Own
<PagesOnly>
```jsx filename="pages/_app.js"
import { useReportWebVitals } from 'next/web-vitals'
function MyApp({ Component, pageProps }) {
useReportWebVitals((metric) => {
console.log(metric)
})
return <Component {...pageProps} />
}
```
View the [API Reference](/docs/pages/api-reference/functions/use-report-web-vitals) for more information.
</PagesOnly>
<AppOnly>
```jsx filename="app/_components/web-vitals.js"
'use client'
import { useReportWebVitals } from 'next/web-vitals'
export function WebVitals() {
useReportWebVitals((metric) => {
console.log(metric)
})
}
```
```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.
View the [API Reference](/docs/app/api-reference/functions/use-report-web-vitals) for more information.
</AppOnly>
## 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'
function MyApp({ Component, pageProps }) {
useReportWebVitals((metric) => {
switch (metric.name) {
case 'FCP': {
// handle FCP results
}
case 'LCP': {
// handle LCP results
}
// ...
}
})
return <Component {...pageProps} />
}
```
</PagesOnly>
<AppOnly>
```tsx filename="app/_components/web-vitals.tsx" switcher
'use client'
import { useReportWebVitals } from 'next/web-vitals'
export function WebVitals() {
useReportWebVitals((metric) => {
switch (metric.name) {
case 'FCP': {
// handle FCP results
}
case 'LCP': {
// handle LCP results
}
// ...
}
})
}
```
```jsx filename="app/_components/web-vitals.js" switcher
'use client'
import { useReportWebVitals } from 'next/web-vitals'
export function WebVitals() {
useReportWebVitals((metric) => {
switch (metric.name) {
case 'FCP': {
// handle FCP results
}
case 'LCP': {
// handle LCP results
}
// ...
}
})
}
```
</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:
```js
export function reportWebVitals(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
}
}
```
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
useReportWebVitals((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 })
}
})
```
> **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).
File diff suppressed because it is too large Load Diff
+915
View File
@@ -0,0 +1,915 @@
---
title: How to use Next.js as a backend for your frontend
nav_title: Backend for Frontend
description: Learn how to use Next.js as a backend framework
related:
title: API Reference
description: Learn more about Route Handlers, Proxy, and Rewrites
links:
- app/api-reference/file-conventions/route
- app/api-reference/file-conventions/proxy
- app/api-reference/config/next-config-js/rewrites
---
Next.js supports the "Backend for Frontend" pattern. This lets you create public endpoints to handle HTTP requests and return any content type—not just HTML. You can also access data sources and perform side effects like updating remote data.
If you are starting a new project, using `create-next-app` with the `--api` flag automatically includes an example `route.ts` in your new project's `app/` folder, demonstrating how to create an API endpoint.
```bash package="pnpm"
pnpm create next-app --api
```
```bash package="npm"
npx create-next-app@latest --api
```
```bash package="yarn"
yarn create next-app --api
```
```bash package="bun"
bun create next-app --api
```
> **Good to know**: Next.js backend capabilities are not a full backend replacement. They serve as an API layer that:
>
> - is publicly reachable
> - handles any HTTP request
> - can return any content type
To implement this pattern, use:
- [Route Handlers](/docs/app/api-reference/file-conventions/route)
- [`proxy`](/docs/app/api-reference/file-conventions/proxy)
- In Pages Router, [API Routes](/docs/pages/building-your-application/routing/api-routes)
## Public Endpoints
Route Handlers are public HTTP endpoints. Any client can access them.
Create a Route Handler using the `route.ts` or `route.js` file convention:
```ts filename="/app/api/route.ts" switcher
export function GET(request: Request) {}
```
```js filename="/app/api/route.js" switcher
export function GET(request) {}
```
This handles `GET` requests sent to `/api`.
Use `try/catch` blocks for operations that may throw an exception:
```ts filename="/app/api/route.ts" switcher
import { submit } from '@/lib/submit'
export async function POST(request: Request) {
try {
await submit(request)
return new Response(null, { status: 204 })
} catch (reason) {
const message =
reason instanceof Error ? reason.message : 'Unexpected error'
return new Response(message, { status: 500 })
}
}
```
```js filename="/app/api/route.js" switcher
import { submit } from '@/lib/submit'
export async function POST(request) {
try {
await submit(request)
return new Response(null, { status: 204 })
} catch (reason) {
const message =
reason instanceof Error ? reason.message : 'Unexpected error'
return new Response(message, { status: 500 })
}
}
```
Avoid exposing sensitive information in error messages sent to the client.
To restrict access, implement authentication and authorization. See [Authentication](/docs/app/guides/authentication).
## Content types
Route Handlers let you serve non-UI responses, including JSON, XML, images, files, and plain text.
Next.js uses file conventions for common endpoints:
- [`sitemap.xml`](/docs/app/api-reference/file-conventions/metadata/sitemap)
- [`opengraph-image.jpg`, `twitter-image`](/docs/app/api-reference/file-conventions/metadata/opengraph-image)
- [favicon, app icon, and apple-icon](/docs/app/api-reference/file-conventions/metadata/app-icons)
- [`manifest.json`](/docs/app/api-reference/file-conventions/metadata/manifest)
- [`robots.txt`](/docs/app/api-reference/file-conventions/metadata/robots)
You can also define custom ones, such as:
- `llms.txt`
- `rss.xml`
- `.well-known`
For example, `app/rss.xml/route.ts` creates a Route Handler for `rss.xml`.
```ts filename="/app/rss.xml/route.ts" switcher
export async function GET(request: Request) {
const rssResponse = await fetch(/* rss endpoint */)
const rssData = await rssResponse.json()
const rssFeed = `<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0">
<channel>
<title>${rssData.title}</title>
<description>${rssData.description}</description>
<link>${rssData.link}</link>
<copyright>${rssData.copyright}</copyright>
${rssData.items.map((item) => {
return `<item>
<title>${item.title}</title>
<description>${item.description}</description>
<link>${item.link}</link>
<pubDate>${item.publishDate}</pubDate>
<guid isPermaLink="false">${item.guid}</guid>
</item>`
})}
</channel>
</rss>`
const headers = new Headers({ 'content-type': 'application/xml' })
return new Response(rssFeed, { headers })
}
```
```js filename="/app/rss.xml/route.js" switcher
export async function GET(request) {
const rssResponse = await fetch(/* rss endpoint */)
const rssData = await rssResponse.json()
const rssFeed = `<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0">
<channel>
<title>${rssData.title}</title>
<description>${rssData.description}</description>
<link>${rssData.link}</link>
<copyright>${rssData.copyright}</copyright>
${rssData.items.map((item) => {
return `<item>
<title>${item.title}</title>
<description>${item.description}</description>
<link>${item.link}</link>
<pubDate>${item.publishDate}</pubDate>
<guid isPermaLink="false">${item.guid}</guid>
</item>`
})}
</channel>
</rss>`
const headers = new Headers({ 'content-type': 'application/xml' })
return new Response(rssFeed, { headers })
}
```
Sanitize any input used to generate markup.
### Content negotiation
You can use [rewrites](/docs/app/api-reference/config/next-config-js/rewrites) with header matching to serve different content types from the same URL based on the request's `Accept` header. This is known as [content negotiation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Content_negotiation).
For example, a documentation site might serve HTML pages to browsers and raw Markdown to AI agents from the same `/docs/…` URLs.
**1. Configure a rewrite that matches the `Accept` header:**
```js filename="next.config.js"
module.exports = {
async rewrites() {
return [
{
source: '/docs/:slug*',
destination: '/docs/md/:slug*',
has: [
{
type: 'header',
key: 'accept',
value: '(.*)text/markdown(.*)',
},
],
},
]
},
}
```
When a request to `/docs/getting-started` includes `Accept: text/markdown`, the rewrite routes it to `/docs/md/getting-started`. A Route Handler at that path returns the Markdown response. Clients that do not send `text/markdown` in their `Accept` header continue to receive the normal HTML page.
**2. Create a Route Handler for the Markdown response:**
```ts filename="app/docs/md/[...slug]/route.ts" switcher
import { getDocsMd, generateDocsStaticParams } from '@/lib/docs'
export async function generateStaticParams() {
return generateDocsStaticParams()
}
export async function GET(_: Request, ctx: RouteContext<'/docs/md/[...slug]'>) {
const { slug } = await ctx.params
const mdDoc = await getDocsMd({ slug })
if (mdDoc == null) {
return new Response(null, { status: 404 })
}
return new Response(mdDoc, {
headers: {
'Content-Type': 'text/markdown; charset=utf-8',
Vary: 'Accept',
},
})
}
```
```js filename="app/docs/md/[...slug]/route.js" switcher
import { getDocsMd, generateDocsStaticParams } from '@/lib/docs'
export async function generateStaticParams() {
return generateDocsStaticParams()
}
export async function GET(_, { params }) {
const { slug } = await params
const mdDoc = await getDocsMd({ slug })
if (mdDoc == null) {
return new Response(null, { status: 404 })
}
return new Response(mdDoc, {
headers: {
'Content-Type': 'text/markdown; charset=utf-8',
Vary: 'Accept',
},
})
}
```
The [`Vary: Accept`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Vary) response header tells caches that the response body depends on the `Accept` request header. Without it, a shared cache could serve a cached Markdown response to a browser (or vice versa). Most hosting providers already include the `Accept` header in their cache key, but setting `Vary` explicitly ensures correct behavior across all CDNs and proxy caches.
`generateStaticParams` lets you pre-render the Markdown variants at build time so they can be served from the edge without hitting the origin server on every request.
**3. Test it with `curl`:**
```bash
# Returns Markdown
curl -H "Accept: text/markdown" https://example.com/docs/getting-started
# Returns the normal HTML page
curl https://example.com/docs/getting-started
```
> **Good to know:**
>
> - The `/docs/md/...` route is still directly accessible without the rewrite. If you want to restrict it to only serve via the rewrite, use [`proxy`](/docs/app/api-reference/file-conventions/proxy) to block direct requests that don't include the expected `Accept` header.
> - For more advanced negotiation logic, you can use [`proxy`](/docs/app/api-reference/file-conventions/proxy) instead of rewrites for more flexibility.
### Consuming request payloads
Use Request [instance methods](https://developer.mozilla.org/en-US/docs/Web/API/Request#instance_methods) like `.json()`, `.formData()`, or `.text()` to access the request body.
`GET` and `HEAD` requests dont carry a body.
```ts filename="/app/api/echo-body/route.ts" switcher
export async function POST(request: Request) {
const res = await request.json()
return Response.json({ res })
}
```
```js filename="/app/api/echo-body/route.js" switcher
export async function POST(request) {
const res = await request.json()
return Response.json({ res })
}
```
> **Good to know**: Validate data before passing it to other systems
```ts filename="/app/api/send-email/route.ts" switcher
import { sendMail, validateInputs } from '@/lib/email-transporter'
export async function POST(request: Request) {
const formData = await request.formData()
const email = formData.get('email')
const contents = formData.get('contents')
try {
await validateInputs({ email, contents })
const info = await sendMail({ email, contents })
return Response.json({ messageId: info.messageId })
} catch (reason) {
const message =
reason instanceof Error ? reason.message : 'Unexpected exception'
return new Response(message, { status: 500 })
}
}
```
```js filename="/app/api/send-email/route.js" switcher
import { sendMail, validateInputs } from '@/lib/email-transporter'
export async function POST(request) {
const formData = await request.formData()
const email = formData.get('email')
const contents = formData.get('contents')
try {
await validateInputs({ email, contents })
const info = await sendMail({ email, contents })
return Response.json({ messageId: info.messageId })
} catch (reason) {
const message =
reason instanceof Error ? reason.message : 'Unexpected exception'
return new Response(message, { status: 500 })
}
}
```
You can only read the request body once. Clone the request if you need to read it again:
```ts filename="/app/api/clone/route.ts" switcher
export async function POST(request: Request) {
try {
const clonedRequest = request.clone()
await request.body()
await clonedRequest.body()
await request.body() // Throws error
return new Response(null, { status: 204 })
} catch {
return new Response(null, { status: 500 })
}
}
```
```js filename="/app/api/clone/route.js" switcher
export async function POST(request) {
try {
const clonedRequest = request.clone()
await request.body()
await clonedRequest.body()
await request.body() // Throws error
return new Response(null, { status: 204 })
} catch {
return new Response(null, { status: 500 })
}
}
```
## Manipulating data
Route Handlers can transform, filter, and aggregate data from one or more sources. This keeps logic out of the frontend and avoids exposing internal systems.
You can also offload heavy computations to the server and reduce client battery and data usage.
```ts file="/app/api/weather/route.ts" switcher
import { parseWeatherData } from '@/lib/weather'
export async function POST(request: Request) {
const body = await request.json()
const searchParams = new URLSearchParams({ lat: body.lat, lng: body.lng })
try {
const weatherResponse = await fetch(`${weatherEndpoint}?${searchParams}`)
if (!weatherResponse.ok) {
/* handle error */
}
const weatherData = await weatherResponse.text()
const payload = parseWeatherData.asJSON(weatherData)
return new Response(payload, { status: 200 })
} catch (reason) {
const message =
reason instanceof Error ? reason.message : 'Unexpected exception'
return new Response(message, { status: 500 })
}
}
```
```js file="/app/api/weather/route.js" switcher
import { parseWeatherData } from '@/lib/weather'
export async function POST(request) {
const body = await request.json()
const searchParams = new URLSearchParams({ lat: body.lat, lng: body.lng })
try {
const weatherResponse = await fetch(`${weatherEndpoint}?${searchParams}`)
if (!weatherResponse.ok) {
/* handle error */
}
const weatherData = await weatherResponse.text()
const payload = parseWeatherData.asJSON(weatherData)
return new Response(payload, { status: 200 })
} catch (reason) {
const message =
reason instanceof Error ? reason.message : 'Unexpected exception'
return new Response(message, { status: 500 })
}
}
```
> **Good to know**: This example uses `POST` to avoid putting geo-location data in the URL. `GET` requests may be cached or logged, which could expose sensitive info.
## Proxying to a backend
You can use a Route Handler as a `proxy` to another backend. Add validation logic before forwarding the request.
```ts filename="/app/api/[...slug]/route.ts" switcher
import { isValidRequest } from '@/lib/utils'
export async function POST(request: Request, { params }) {
const clonedRequest = request.clone()
const isValid = await isValidRequest(clonedRequest)
if (!isValid) {
return new Response(null, { status: 400, statusText: 'Bad Request' })
}
const { slug } = await params
const pathname = slug.join('/')
const proxyURL = new URL(pathname, 'https://nextjs.org')
const proxyRequest = new Request(proxyURL, request)
try {
return fetch(proxyRequest)
} catch (reason) {
const message =
reason instanceof Error ? reason.message : 'Unexpected exception'
return new Response(message, { status: 500 })
}
}
```
```js filename="/app/api/[...slug]/route.js" switcher
import { isValidRequest } from '@/lib/utils'
export async function POST(request, { params }) {
const clonedRequest = request.clone()
const isValid = await isValidRequest(clonedRequest)
if (!isValid) {
return new Response(null, { status: 400, statusText: 'Bad Request' })
}
const { slug } = await params
const pathname = slug.join('/')
const proxyURL = new URL(pathname, 'https://nextjs.org')
const proxyRequest = new Request(proxyURL, request)
try {
return fetch(proxyRequest)
} catch (reason) {
const message =
reason instanceof Error ? reason.message : 'Unexpected exception'
return new Response(message, { status: 500 })
}
}
```
Or use:
- `proxy` [rewrites](#proxy)
- [`rewrites`](/docs/app/api-reference/config/next-config-js/rewrites) in `next.config.js`.
## NextRequest and NextResponse
Next.js extends the [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) and [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) Web APIs with methods that simplify common operations. These extensions are available in both Route Handlers and Proxy.
Both provide methods for reading and manipulating cookies.
`NextRequest` includes the [`nextUrl`](/docs/app/api-reference/functions/next-request#nexturl) property, which exposes parsed values from the incoming request, for example, it makes it easier to access request pathname and search params.
`NextResponse` provides helpers like `next()`, `json()`, `redirect()`, and `rewrite()`.
You can pass `NextRequest` to any function expecting `Request`. Likewise, you can return `NextResponse` where a `Response` is expected.
```ts filename="/app/echo-pathname/route.ts" switcher
import { type NextRequest, NextResponse } from 'next/server'
export async function GET(request: NextRequest) {
const nextUrl = request.nextUrl
if (nextUrl.searchParams.get('redirect')) {
return NextResponse.redirect(new URL('/', request.url))
}
if (nextUrl.searchParams.get('rewrite')) {
return NextResponse.rewrite(new URL('/', request.url))
}
return NextResponse.json({ pathname: nextUrl.pathname })
}
```
```js filename="/app/echo-pathname/route.js" switcher
import { NextResponse } from 'next/server'
export async function GET(request) {
const nextUrl = request.nextUrl
if (nextUrl.searchParams.get('redirect')) {
return NextResponse.redirect(new URL('/', request.url))
}
if (nextUrl.searchParams.get('rewrite')) {
return NextResponse.rewrite(new URL('/', request.url))
}
return NextResponse.json({ pathname: nextUrl.pathname })
}
```
Learn more about [`NextRequest`](/docs/app/api-reference/functions/next-request) and [`NextResponse`](/docs/app/api-reference/functions/next-response).
## Webhooks and callback URLs
Use Route Handlers to receive event notifications from third-party applications.
For example, revalidate a route when content changes in a CMS. Configure the CMS to call a specific endpoint on changes.
```ts filename="/app/webhook/route.ts" switcher
import { type NextRequest, NextResponse } from 'next/server'
export async function GET(request: NextRequest) {
const token = request.nextUrl.searchParams.get('token')
if (token !== process.env.REVALIDATE_SECRET_TOKEN) {
return NextResponse.json({ success: false }, { status: 401 })
}
const tag = request.nextUrl.searchParams.get('tag')
if (!tag) {
return NextResponse.json({ success: false }, { status: 400 })
}
revalidateTag(tag)
return NextResponse.json({ success: true })
}
```
```js filename="/app/webhook/route.js" switcher
import { NextResponse } from 'next/server'
export async function GET(request) {
const token = request.nextUrl.searchParams.get('token')
if (token !== process.env.REVALIDATE_SECRET_TOKEN) {
return NextResponse.json({ success: false }, { status: 401 })
}
const tag = request.nextUrl.searchParams.get('tag')
if (!tag) {
return NextResponse.json({ success: false }, { status: 400 })
}
revalidateTag(tag)
return NextResponse.json({ success: true })
}
```
Callback URLs are another use case. When a user completes a third-party flow, the third party sends them to a callback URL. Use a Route Handler to verify the response and decide where to redirect the user.
```ts filename="/app/auth/callback/route.ts" switcher
import { type NextRequest, NextResponse } from 'next/server'
export async function GET(request: NextRequest) {
const token = request.nextUrl.searchParams.get('session_token')
const redirectUrl = request.nextUrl.searchParams.get('redirect_url')
const response = NextResponse.redirect(new URL(redirectUrl, request.url))
response.cookies.set({
value: token,
name: '_token',
path: '/',
secure: true,
httpOnly: true,
expires: undefined, // session cookie
})
return response
}
```
```js filename="/app/auth/callback/route.js" switcher
import { NextResponse } from 'next/server'
export async function GET(request) {
const token = request.nextUrl.searchParams.get('session_token')
const redirectUrl = request.nextUrl.searchParams.get('redirect_url')
const response = NextResponse.redirect(new URL(redirectUrl, request.url))
response.cookies.set({
value: token,
name: '_token',
path: '/',
secure: true,
httpOnly: true,
expires: undefined, // session cookie
})
return response
}
```
## 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/')
}
```
Learn more about redirects in [`redirect`](/docs/app/api-reference/functions/redirect) and [`permanentRedirect`](/docs/app/api-reference/functions/permanentRedirect)
## Proxy
Only one `proxy` file is allowed per project. Use `config.matcher` to target specific paths. Learn more about [`proxy`](/docs/app/api-reference/file-conventions/proxy).
Use `proxy` to generate a response before the request reaches a route path.
```ts filename="proxy.ts" switcher
import { isAuthenticated } from '@lib/auth'
export const config = {
matcher: '/api/:function*',
}
export function proxy(request: Request) {
if (!isAuthenticated(request)) {
return Response.json(
{ success: false, message: 'authentication failed' },
{ status: 401 }
)
}
}
```
```js filename="proxy.js" switcher
import { isAuthenticated } from '@lib/auth'
export const config = {
matcher: '/api/:function*',
}
export function proxy(request) {
if (!isAuthenticated(request)) {
return Response.json(
{ success: false, message: 'authentication failed' },
{ status: 401 }
)
}
}
```
You can also proxy requests using `proxy`:
```ts filename="proxy.ts" switcher
import { NextResponse } from 'next/server'
export function proxy(request: Request) {
if (request.nextUrl.pathname === '/proxy-this-path') {
const rewriteUrl = new URL('https://nextjs.org')
return NextResponse.rewrite(rewriteUrl)
}
}
```
```js filename="proxy.js" switcher
import { NextResponse } from 'next/server'
export function proxy(request) {
if (request.nextUrl.pathname === '/proxy-this-path') {
const rewriteUrl = new URL('https://nextjs.org')
return NextResponse.rewrite(rewriteUrl)
}
}
```
Another type of response `proxy` can produce are redirects:
```ts filename="proxy.ts" switcher
import { NextResponse } from 'next/server'
export function proxy(request: Request) {
if (request.nextUrl.pathname === '/v1/docs') {
request.nextUrl.pathname = '/v2/docs'
return NextResponse.redirect(request.nextUrl)
}
}
```
```js filename="proxy.js" switcher
import { NextResponse } from 'next/server'
export function proxy(request) {
if (request.nextUrl.pathname === '/v1/docs') {
request.nextUrl.pathname = '/v2/docs'
return NextResponse.redirect(request.nextUrl)
}
}
```
## Security
### Working with headers
Be deliberate about where headers go, and avoid directly passing incoming request headers to the outgoing response.
- **Upstream request headers**: In Proxy, `NextResponse.next({ request: { headers } })` modifies the headers your server receives and does not expose them to the client.
- **Response headers**: `new Response(..., { headers })`, `NextResponse.json(..., { headers })`, `NextResponse.next({ headers })`, or `response.headers.set(...)` send headers back to the client. If sensitive values were appended to these headers, they will be visible to clients.
Learn more in [NextResponse headers in Proxy](/docs/app/api-reference/functions/next-response#next).
### Rate limiting
You can implement rate limiting in your Next.js backend. In addition to code-based checks, enable any rate limiting features provided by your host.
```ts filename="/app/resource/route.ts" switcher
import { NextResponse } from 'next/server'
import { checkRateLimit } from '@/lib/rate-limit'
export async function POST(request: Request) {
const { rateLimited } = await checkRateLimit(request)
if (rateLimited) {
return NextResponse.json({ error: 'Rate limit exceeded' }, { status: 429 })
}
return new Response(null, { status: 204 })
}
```
```js filename="/app/resource/route.js" switcher
import { NextResponse } from 'next/server'
import { checkRateLimit } from '@/lib/rate-limit'
export async function POST(request) {
const { rateLimited } = await checkRateLimit(request)
if (rateLimited) {
return NextResponse.json({ error: 'Rate limit exceeded' }, { status: 429 })
}
return new Response(null, { status: 204 })
}
```
### Verify payloads
Never trust incoming request data. Validate content type and size, and sanitize against XSS before use.
Use timeouts to prevent abuse and protect server resources.
Store user-generated static assets in dedicated services. When possible, upload them from the browser and store the returned URI in your database to reduce request size.
### Access to protected resources
Always verify credentials before granting access. Do not rely on proxy alone for authentication and authorization.
Remove sensitive or unnecessary data from responses and backend logs.
Rotate credentials and API keys regularly.
## Preflight Requests
Preflight requests use the `OPTIONS` method to ask the server if a request is allowed based on origin, method, and headers.
If `OPTIONS` is not defined, Next.js adds it automatically and sets the `Allow` header based on the other defined methods.
- [CORS](/docs/app/api-reference/file-conventions/route#cors)
## Library patterns
Community libraries often use the factory pattern for Route Handlers.
```ts filename="/app/api/[...path]/route.ts"
import { createHandler } from 'third-party-library'
const handler = createHandler({
/* library-specific options */
})
export const GET = handler
// or
export { handler as POST }
```
This creates a shared handler for `GET` and `POST` requests. The library customizes behavior based on the `method` and `pathname` in the request.
Libraries can also provide a `proxy` factory.
```ts filename="proxy.ts"
import { createMiddleware } from 'third-party-library'
export default createMiddleware()
```
> **Good to know**: Third-party libraries may still refer to `proxy` as `middleware`.
## More examples
See more examples on using [Router Handlers](/docs/app/api-reference/file-conventions/route#examples) and the [`proxy`](/docs/app/api-reference/file-conventions/proxy#examples) API references.
These examples include, working with [Cookies](/docs/app/api-reference/file-conventions/route#cookies), [Headers](/docs/app/api-reference/file-conventions/route#headers), [Streaming](/docs/app/api-reference/file-conventions/route#streaming), Proxy [negative matching](/docs/app/api-reference/file-conventions/proxy#negative-matching), and other useful code snippets.
## Caveats
### Server Components
Fetch data in Server Components directly from its source, not via Route Handlers.
For Server Components prerendered at build time, using Route Handlers will fail the build step. This is because, while building there is no server listening for these requests.
For Server Components rendered on demand, fetching from Route Handlers is slower due to the extra HTTP round trip between the handler and the render process.
> A server side `fetch` request uses absolute URLs. This implies an HTTP round trip, to an external server. During development, your own development server acts as the external server. At build time there is no server, and at runtime, the server is available through your public facing domain.
Server Components cover most data-fetching needs. However, fetching data client side might be necessary for:
- Data that depends on client-only Web APIs:
- Geo-location API
- Storage API
- Audio API
- File API
- Frequently polled data
For these, use community libraries like [`swr`](https://swr.vercel.app/) or [`react-query`](https://tanstack.com/query/latest/docs/framework/react/overview).
### Server Actions
Server Actions let you run server-side code from the client. Their primary purpose is to mutate data from your frontend client.
Server Actions are queued. Using them for data fetching introduces sequential execution.
### `export` mode
`export` mode outputs a static site without a runtime server. Features that require the Next.js runtime are [not supported](/docs/app/guides/static-exports#unsupported-features), because this mode produces a static site, and no runtime server.
In `export mode`, only `GET` Route Handlers are supported, in combination with the [`dynamic`](/docs/app/guides/caching-without-cache-components#dynamic) route segment config, set to `'force-static'`.
This can be used to generate static HTML, JSON, TXT, or other files.
```js filename="app/hello-world/route.ts"
export const dynamic = 'force-static'
export function GET() {
return new Response('Hello World', { status: 200 })
}
```
### Deployment environment
Some hosts deploy Route Handlers as lambda functions. This means:
- Route Handlers cannot share data between requests.
- The environment may not support writing to File System.
- Long-running handlers may be terminated due to timeouts.
- WebSockets wont work because the connection closes on timeout, or after the response is generated.
@@ -0,0 +1,364 @@
---
title: Caching and Revalidating (Previous Model)
nav_title: Caching (Previous Model)
description: Learn how to cache and revalidate data using fetch options, unstable_cache, and route segment configs for projects not using Cache Components.
---
> This guide assumes you are **not** using [Cache Components](/docs/app/getting-started/caching) which was introduced in version 16 under the [`cacheComponents` flag](/docs/app/api-reference/config/next-config-js/cacheComponents).
## Caching `fetch` requests
By default, [`fetch`](/docs/app/api-reference/functions/fetch) requests are not cached. You can cache individual requests by setting the `cache` option to `'force-cache'`.
```tsx filename="app/page.tsx" switcher
export default async function Page() {
const data = await fetch('https://...', { cache: 'force-cache' })
}
```
```jsx filename="app/page.jsx" switcher
export default async function Page() {
const data = await fetch('https://...', { cache: 'force-cache' })
}
```
See the [`fetch` API reference](/docs/app/api-reference/functions/fetch) to learn more.
### `unstable_cache` for non-`fetch` functions
`unstable_cache` allows you to cache the result of database queries and other async functions that don't use `fetch`. Wrap `unstable_cache` around the function:
```ts filename="app/lib/data.ts" switcher
import { unstable_cache } from 'next/cache'
import { db } from '@/lib/db'
export const getCachedUser = unstable_cache(
async (id: string) => {
return db
.select()
.from(users)
.where(eq(users.id, id))
.then((res) => res[0])
},
['user'], // cache key prefix
{
tags: ['user'],
revalidate: 3600,
}
)
```
```js filename="app/lib/data.js" switcher
import { unstable_cache } from 'next/cache'
import { db } from '@/lib/db'
export const getCachedUser = unstable_cache(
async (id) => {
return db
.select()
.from(users)
.where(eq(users.id, id))
.then((res) => res[0])
},
['user'], // cache key prefix
{
tags: ['user'],
revalidate: 3600,
}
)
```
The third argument accepts:
- `tags`: an array of tags for on-demand revalidation with `revalidateTag`.
- `revalidate`: the number of seconds before the cache is revalidated.
See the [`unstable_cache` API reference](/docs/app/api-reference/functions/unstable_cache) to learn more.
### Route segment config
You can configure caching behavior at the route level by exporting config options from 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).
#### `dynamic`
Change the dynamic behavior of a layout or page to fully static or fully dynamic.
```tsx filename="layout.tsx | page.tsx | route.ts" switcher
export const dynamic = 'auto'
// 'auto' | 'force-dynamic' | 'error' | 'force-static'
```
```jsx filename="layout.js | page.js | route.js" switcher
export const dynamic = 'auto'
// 'auto' | 'force-dynamic' | 'error' | 'force-static'
```
- **`'auto'`** (default): The default option to cache as much as possible without preventing any components from opting into dynamic behavior.
- **`'force-dynamic'`**: Force [dynamic rendering](/docs/app/glossary#dynamic-rendering), which will result in routes being rendered for each user at request time. This option is equivalent to:
- Setting the option of every `fetch()` request in a layout or page to `{ cache: 'no-store', next: { revalidate: 0 } }`.
- Setting the segment config to `export const fetchCache = 'force-no-store'`
- **`'error'`**: Force prerendering and cache the data of a layout or page by causing an error if any components use Request-time APIs or uncached data. This option is equivalent to:
- `getStaticProps()` in the `pages` directory.
- Setting the option of every `fetch()` request in a layout or page to `{ cache: 'force-cache' }`.
- Setting the segment config to `fetchCache = 'only-cache'`.
- **`'force-static'`**: Force prerendering and cache the data of a layout or page by forcing [`cookies`](/docs/app/api-reference/functions/cookies), [`headers()`](/docs/app/api-reference/functions/headers) and [`useSearchParams()`](/docs/app/api-reference/functions/use-search-params) to return empty values. It is possible to [`revalidate`](#route-segment-config-revalidate), [`revalidatePath`](/docs/app/api-reference/functions/revalidatePath), or [`revalidateTag`](/docs/app/api-reference/functions/revalidateTag), in pages or layouts rendered with `force-static`.
#### `fetchCache`
<details>
<summary>This is an advanced option that should only be used if you specifically need to override the default behavior.</summary>
By default, Next.js **will cache** any `fetch()` requests that are reachable **before** any Request-time APIs are used and **will not cache** `fetch` requests that are discovered **after** Request-time APIs are used.
`fetchCache` allows you to override the default `cache` option of all `fetch` requests in a layout or page.
```tsx filename="layout.tsx | page.tsx | route.ts" switcher
export const fetchCache = 'auto'
// 'auto' | 'default-cache' | 'only-cache'
// 'force-cache' | 'force-no-store' | 'default-no-store' | 'only-no-store'
```
```jsx filename="layout.js | page.js | route.js" switcher
export const fetchCache = 'auto'
// 'auto' | 'default-cache' | 'only-cache'
// 'force-cache' | 'force-no-store' | 'default-no-store' | 'only-no-store'
```
- **`'auto'`** (default): The default option to cache `fetch` requests before Request-time APIs with the `cache` option they provide and not cache `fetch` requests after Request-time APIs.
- **`'default-cache'`**: Allow any `cache` option to be passed to `fetch` but if no option is provided then set the `cache` option to `'force-cache'`. This means that even `fetch` requests after Request-time APIs are considered static.
- **`'only-cache'`**: Ensure all `fetch` requests opt into caching by changing the default to `cache: 'force-cache'` if no option is provided and causing an error if any `fetch` requests use `cache: 'no-store'`.
- **`'force-cache'`**: Ensure all `fetch` requests opt into caching by setting the `cache` option of all `fetch` requests to `'force-cache'`.
- **`'default-no-store'`**: Allow any `cache` option to be passed to `fetch` but if no option is provided then set the `cache` option to `'no-store'`. This means that even `fetch` requests before Request-time APIs are considered dynamic.
- **`'only-no-store'`**: Ensure all `fetch` requests opt out of caching by changing the default to `cache: 'no-store'` if no option is provided and causing an error if any `fetch` requests use `cache: 'force-cache'`
- **`'force-no-store'`**: Ensure all `fetch` requests opt out of caching by setting the `cache` option of all `fetch` requests to `'no-store'`. This forces all `fetch` requests to be re-fetched every request even if they provide a `'force-cache'` option.
##### Cross-route segment behavior
- Any options set across each layout and page of a single route need to be compatible with each other.
- If both the `'only-cache'` and `'force-cache'` are provided, then `'force-cache'` wins. If both `'only-no-store'` and `'force-no-store'` are provided, then `'force-no-store'` wins. The force option changes the behavior across the route so a single segment with `'force-*'` would prevent any errors caused by `'only-*'`.
- The intention of the `'only-*'` and `'force-*'` options is to guarantee the whole route is either fully static or fully dynamic. This means:
- A combination of `'only-cache'` and `'only-no-store'` in a single route is not allowed.
- A combination of `'force-cache'` and `'force-no-store'` in a single route is not allowed.
- A parent cannot provide `'default-no-store'` if a child provides `'auto'` or `'*-cache'` since that could make the same fetch have different behavior.
- It is generally recommended to leave shared parent layouts as `'auto'` and customize the options where child segments diverge.
</details>
## Time-based revalidation
Use the `next.revalidate` option on `fetch` to revalidate data after a specified number of seconds:
```tsx filename="app/page.tsx" switcher
export default async function Page() {
const data = await fetch('https://...', { next: { revalidate: 3600 } })
}
```
```jsx filename="app/page.jsx" switcher
export default async function Page() {
const data = await fetch('https://...', { next: { revalidate: 3600 } })
}
```
For non-`fetch` functions, `unstable_cache` accepts a `revalidate` option in its configuration (see [example above](#unstable_cache-for-non-fetch-functions)).
### Route segment config `revalidate`
Set the default revalidation time for a layout or page. This option does not override the `revalidate` value set by individual `fetch` requests.
```tsx filename="layout.tsx | page.tsx | route.ts" switcher
export const revalidate = false
// false | 0 | number
```
```jsx filename="layout.js | page.js | route.js" switcher
export const revalidate = false
// false | 0 | number
```
- **`false`** (default): The default heuristic to cache any `fetch` requests that set their `cache` option to `'force-cache'` or are discovered before a Request-time API is used. Semantically equivalent to `revalidate: Infinity` which effectively means the resource should be cached indefinitely. It is still possible for individual `fetch` requests to use `cache: 'no-store'` or `revalidate: 0` to avoid being cached and make the route dynamically rendered. Or set `revalidate` to a positive number lower than the route default to increase the revalidation frequency of a route.
- **`0`**: Ensure a layout or page is always dynamically rendered even if no Request-time APIs or uncached data fetches are discovered. This option changes the default of `fetch` requests that do not set a `cache` option to `'no-store'` but leaves `fetch` requests that opt into `'force-cache'` or use a positive `revalidate` as is.
- **`number`**: (in seconds) Set the default revalidation frequency of a layout or page to `n` seconds.
> **Good to know**:
>
> - The revalidate value needs to be statically analyzable. For example `revalidate = 600` is valid, but `revalidate = 60 * 10` is not.
> - The revalidate value is not available when using `runtime = 'edge'`.
> - In Development, Pages are _always_ rendered on-demand and are never cached. This allows you to see changes immediately without waiting for a revalidation period to pass.
#### Revalidation frequency
- The lowest `revalidate` across each layout and page of a single route will determine the revalidation frequency of the _entire_ route. This ensures that child pages are revalidated as frequently as their parent layouts.
- Individual `fetch` requests can set a lower `revalidate` than the route's default `revalidate` to increase the revalidation frequency of the entire route. This allows you to dynamically opt-in to more frequent revalidation for certain routes based on some criteria.
## On-demand revalidation
To revalidate cached data after an event, use [`revalidateTag`](/docs/app/api-reference/functions/revalidateTag) or [`revalidatePath`](/docs/app/api-reference/functions/revalidatePath) in a [Server Action](/docs/app/getting-started/mutating-data) or [Route Handler](/docs/app/api-reference/file-conventions/route).
### Tagging cached data
Tag `fetch` requests with `next.tags` to enable on-demand cache invalidation:
```tsx filename="app/lib/data.ts" switcher
export async function getUserById(id: string) {
const data = await fetch(`https://...`, {
next: { tags: ['user'] },
})
}
```
```jsx filename="app/lib/data.js" switcher
export async function getUserById(id) {
const data = await fetch(`https://...`, {
next: { tags: ['user'] },
})
}
```
For non-`fetch` functions, `unstable_cache` also accepts a `tags` option (see [example above](#unstable_cache-for-non-fetch-functions)).
### `revalidateTag`
Invalidate cached data by tag using [`revalidateTag`](/docs/app/api-reference/functions/revalidateTag):
```tsx filename="app/lib/actions.ts" switcher
import { revalidateTag } from 'next/cache'
export async function updateUser(id: string) {
// Mutate data
revalidateTag('user')
}
```
```jsx filename="app/lib/actions.js" switcher
import { revalidateTag } from 'next/cache'
export async function updateUser(id) {
// Mutate data
revalidateTag('user')
}
```
### `revalidatePath`
Invalidate all cached data for a specific route path using [`revalidatePath`](/docs/app/api-reference/functions/revalidatePath):
```tsx filename="app/lib/actions.ts" switcher
import { revalidatePath } from 'next/cache'
export async function updateUser(id: string) {
// Mutate data
revalidatePath('/profile')
}
```
```jsx filename="app/lib/actions.js" switcher
import { revalidatePath } from 'next/cache'
export async function updateUser(id) {
// Mutate data
revalidatePath('/profile')
}
```
## Deduplicating requests
If you are not using `fetch` (which is [automatically memoized](/docs/app/api-reference/functions/fetch#memoization)), and instead using an ORM or database directly, you can wrap your data access with the [React `cache`](https://react.dev/reference/react/cache) function to deduplicate requests within a single render pass:
```tsx filename="app/lib/data.ts" switcher
import { cache } from 'react'
import { db, posts, eq } from '@/lib/db'
export const getPost = cache(async (id: string) => {
const post = await db.query.posts.findFirst({
where: eq(posts.id, parseInt(id)),
})
})
```
```jsx filename="app/lib/data.js" switcher
import { cache } from 'react'
import { db, posts, eq } from '@/lib/db'
export const getPost = cache(async (id) => {
const post = await db.query.posts.findFirst({
where: eq(posts.id, parseInt(id)),
})
})
```
## Preloading data
You can preload data by creating a utility function that you eagerly call above blocking requests. This lets you initiate data fetching early, so the data is already available by the time the component renders.
Combine the [`server-only` package](https://www.npmjs.com/package/server-only) with React's [`cache`](https://react.dev/reference/react/cache) to create a reusable preload utility:
```ts filename="utils/get-item.ts" switcher
import { cache } from 'react'
import 'server-only'
export const getItem = cache(async (id: string) => {
// ...
})
export const preload = (id: string) => {
void getItem(id)
}
```
```js filename="utils/get-item.js" switcher
import { cache } from 'react'
import 'server-only'
export const getItem = cache(async (id) => {
// ...
})
export const preload = (id) => {
void getItem(id)
}
```
Then call `preload()` before any blocking work so the data starts loading immediately:
```tsx filename="app/item/[id]/page.tsx" switcher
import { getItem, preload, checkIsAvailable } from '@/lib/data'
export default async function Page({
params,
}: {
params: Promise<{ id: string }>
}) {
const { id } = await params
// Start loading item data
preload(id)
// Perform another asynchronous task
const isAvailable = await checkIsAvailable()
return isAvailable ? <Item id={id} /> : null
}
async function Item({ id }: { id: string }) {
const result = await getItem(id)
// ...
}
```
```jsx filename="app/item/[id]/page.js" switcher
import { getItem, preload, checkIsAvailable } from '@/lib/data'
export default async function Page({ params }) {
const { id } = await params
// Start loading item data
preload(id)
// Perform another asynchronous task
const isAvailable = await checkIsAvailable()
return isAvailable ? <Item id={id} /> : null
}
async function Item({ id }) {
const result = await getItem(id)
// ...
}
```
+169
View File
@@ -0,0 +1,169 @@
---
title: How to configure Continuous Integration (CI) build caching
nav_title: CI Build Caching
description: Learn how to configure CI to cache Next.js builds
---
To improve build performance, Next.js saves a cache to `.next/cache` that is shared between builds.
To take advantage of this cache in Continuous Integration (CI) environments, your CI workflow will need to be configured to correctly persist the cache between builds.
> If your CI is not configured to persist `.next/cache` between builds, you may see a [No Cache Detected](/docs/messages/no-cache) error.
Here are some example cache configurations for common CI providers:
## Vercel
Next.js caching is automatically configured for you. There's no action required on your part. If you are using Turborepo on Vercel, [learn more here](https://vercel.com/docs/monorepos/turborepo).
## CircleCI
Edit your `save_cache` step in `.circleci/config.yml` to include `.next/cache`:
```yaml
steps:
- save_cache:
key: dependency-cache-{{ checksum "yarn.lock" }}
paths:
- ./node_modules
- ./.next/cache
```
If you do not have a `save_cache` key, please follow CircleCI's [documentation on setting up build caching](https://circleci.com/docs/2.0/caching/).
## Travis CI
Add or merge the following into your `.travis.yml`:
```yaml
cache:
directories:
- $HOME/.cache/yarn
- node_modules
- .next/cache
```
## GitLab CI
Add or merge the following into your `.gitlab-ci.yml`:
```yaml
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
- .next/cache/
```
## Netlify CI
Use [Netlify Plugins](https://www.netlify.com/products/build/plugins/) with [`@netlify/plugin-nextjs`](https://www.npmjs.com/package/@netlify/plugin-nextjs).
## AWS CodeBuild
Add (or merge in) the following to your `buildspec.yml`:
```yaml
cache:
paths:
- 'node_modules/**/*' # Cache `node_modules` for faster `yarn` or `npm i`
- '.next/cache/**/*' # Cache Next.js for faster application rebuilds
```
## GitHub Actions
Using GitHub's [actions/cache](https://github.com/actions/cache), add the following step in your workflow file:
```yaml
uses: actions/cache@v4
with:
# See here for caching with `yarn`, `bun` or other package managers https://github.com/actions/cache/blob/main/examples.md or you can leverage caching with actions/setup-node https://github.com/actions/setup-node
path: |
~/.npm
${{ github.workspace }}/.next/cache
# Generate a new cache whenever packages or source files change.
key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('**/*.js', '**/*.jsx', '**/*.ts', '**/*.tsx') }}
# If source files changed but packages didn't, rebuild from a prior cache.
restore-keys: |
${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-
```
## Bitbucket Pipelines
Add or merge the following into your `bitbucket-pipelines.yml` at the top level (same level as `pipelines`):
```yaml
definitions:
caches:
nextcache: .next/cache
```
Then reference it in the `caches` section of your pipeline's `step`:
```yaml
- step:
name: your_step_name
caches:
- node
- nextcache
```
## Heroku
Using Heroku's [custom cache](https://devcenter.heroku.com/articles/nodejs-support#custom-caching), add a `cacheDirectories` array in your top-level package.json:
```javascript
"cacheDirectories": [".next/cache"]
```
## Azure Pipelines
Using Azure Pipelines' [Cache task](https://docs.microsoft.com/en-us/azure/devops/pipelines/tasks/utility/cache), add the following task to your pipeline yaml file somewhere prior to the task that executes `next build`:
```yaml
- task: Cache@2
displayName: 'Cache .next/cache'
inputs:
key: next | $(Agent.OS) | yarn.lock
path: '$(System.DefaultWorkingDirectory)/.next/cache'
```
## Jenkins (Pipeline)
Using Jenkins' [Job Cacher](https://www.jenkins.io/doc/pipeline/steps/jobcacher/) plugin, add the following build step to your `Jenkinsfile` where you would normally run `next build` or `npm install`:
```yaml
stage("Restore npm packages") {
steps {
// Writes lock-file to cache based on the GIT_COMMIT hash
writeFile file: "next-lock.cache", text: "$GIT_COMMIT"
cache(caches: [
arbitraryFileCache(
path: "node_modules",
includes: "**/*",
cacheValidityDecidingFile: "package-lock.json"
)
]) {
sh "npm install"
}
}
}
stage("Build") {
steps {
// Writes lock-file to cache based on the GIT_COMMIT hash
writeFile file: "next-lock.cache", text: "$GIT_COMMIT"
cache(caches: [
arbitraryFileCache(
path: ".next/cache",
includes: "**/*",
cacheValidityDecidingFile: "next-lock.cache"
)
]) {
// aka `next build`
sh "npm run build"
}
}
}
```
+730
View File
@@ -0,0 +1,730 @@
---
title: How to set a Content Security Policy (CSP) for your Next.js application
nav_title: Content Security Policy
description: Learn how to set a Content Security Policy (CSP) for your Next.js application.
related:
links:
- app/api-reference/file-conventions/proxy
- app/api-reference/functions/headers
---
{/* 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. */}
[Content Security Policy (CSP)](https://developer.mozilla.org/docs/Web/HTTP/CSP) is important to guard your Next.js application against various security threats such as cross-site scripting (XSS), clickjacking, and other code injection attacks.
By using CSP, developers can specify which origins are permissible for content sources, scripts, stylesheets, images, fonts, objects, media (audio, video), iframes, and more.
<details>
<summary>Examples</summary>
- [Strict CSP](https://github.com/vercel/next.js/tree/canary/examples/with-strict-csp)
</details>
## Nonces
A [nonce](https://developer.mozilla.org/docs/Web/HTML/Global_attributes/nonce) is a unique, random string of characters created for a one-time use. It is used in conjunction with CSP to selectively allow certain inline scripts or styles to execute, bypassing strict CSP directives.
### Why use a nonce?
CSP can block both inline and external scripts to prevent attacks. A nonce lets you safely allow specific scripts to run—only if they include the matching nonce value.
If an attacker wanted to load a script into your page, they'd need to guess the nonce value. That's why the nonce must be unpredictable and unique for every request.
### Adding a nonce with Proxy
[Proxy](/docs/app/api-reference/file-conventions/proxy) enables you to add headers and generate nonces before the page renders.
Every time a page is viewed, a fresh nonce should be generated. This means that you **must use [dynamic rendering](/docs/app/glossary#dynamic-rendering) to add nonces**.
For example:
> **Good to know**: In development, `'unsafe-eval'` is required because React uses `eval` to provide enhanced debugging information, such as reconstructing server-side error stacks in the browser. `unsafe-eval` is not required for production. Neither React nor Next.js use `eval` in production by default.
```ts filename="proxy.ts" switcher
import { NextRequest, NextResponse } from 'next/server'
export function proxy(request: NextRequest) {
const nonce = Buffer.from(crypto.randomUUID()).toString('base64')
const isDev = process.env.NODE_ENV === 'development'
const cspHeader = `
default-src 'self';
script-src 'self' 'nonce-${nonce}' 'strict-dynamic'${isDev ? " 'unsafe-eval'" : ''};
style-src 'self' 'nonce-${nonce}';
img-src 'self' blob: data:;
font-src 'self';
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
upgrade-insecure-requests;
`
// Replace newline characters and spaces
const contentSecurityPolicyHeaderValue = cspHeader
.replace(/\s{2,}/g, ' ')
.trim()
const requestHeaders = new Headers(request.headers)
requestHeaders.set('x-nonce', nonce)
requestHeaders.set(
'Content-Security-Policy',
contentSecurityPolicyHeaderValue
)
const response = NextResponse.next({
request: {
headers: requestHeaders,
},
})
response.headers.set(
'Content-Security-Policy',
contentSecurityPolicyHeaderValue
)
return response
}
```
```js filename="proxy.js" switcher
import { NextResponse } from 'next/server'
export function proxy(request) {
const nonce = Buffer.from(crypto.randomUUID()).toString('base64')
const isDev = process.env.NODE_ENV === 'development'
const cspHeader = `
default-src 'self';
script-src 'self' 'nonce-${nonce}' 'strict-dynamic'${isDev ? " 'unsafe-eval'" : ''};
style-src 'self' 'nonce-${nonce}';
img-src 'self' blob: data:;
font-src 'self';
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
upgrade-insecure-requests;
`
// Replace newline characters and spaces
const contentSecurityPolicyHeaderValue = cspHeader
.replace(/\s{2,}/g, ' ')
.trim()
const requestHeaders = new Headers(request.headers)
requestHeaders.set('x-nonce', nonce)
requestHeaders.set(
'Content-Security-Policy',
contentSecurityPolicyHeaderValue
)
const response = NextResponse.next({
request: {
headers: requestHeaders,
},
})
response.headers.set(
'Content-Security-Policy',
contentSecurityPolicyHeaderValue
)
return response
}
```
By default, Proxy runs on all requests. You can filter Proxy to run on specific paths using a [`matcher`](/docs/app/api-reference/file-conventions/proxy#matcher).
We recommend ignoring matching prefetches (from `next/link`) and static assets that don't need the CSP header.
```ts filename="proxy.ts" switcher
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 (favicon file)
*/
{
source: '/((?!api|_next/static|_next/image|favicon.ico).*)',
missing: [
{ type: 'header', key: 'next-router-prefetch' },
{ type: 'header', key: 'purpose', value: 'prefetch' },
],
},
],
}
```
```js filename="proxy.js" switcher
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 (favicon file)
*/
{
source: '/((?!api|_next/static|_next/image|favicon.ico).*)',
missing: [
{ type: 'header', key: 'next-router-prefetch' },
{ type: 'header', key: 'purpose', value: 'prefetch' },
],
},
],
}
```
### How nonces work in Next.js
To use a nonce, your page must be **dynamically rendered**. This is because Next.js applies nonces during **server-side rendering**, based on the CSP header present in the request. Static pages are generated at build time, when no request or response headers exist—so no nonce can be injected.
Heres how nonce support works in a dynamically rendered page:
1. **Proxy generates a nonce**: Your proxy creates a unique nonce for the request, adds it to your `Content-Security-Policy` header, and also sets it in a custom `x-nonce` header.
2. **Next.js extracts the nonce**: During rendering, Next.js parses the `Content-Security-Policy` header and extracts the nonce using the `'nonce-{value}'` pattern.
3. **Nonce is applied automatically**: Next.js attaches the nonce to:
- Framework scripts (React, Next.js runtime)
- Page-specific JavaScript bundles
- Inline styles and scripts generated by Next.js
- Any `<Script>` components using the `nonce` prop
Because of this automatic behavior, you dont need to manually add a nonce to each tag.
### Forcing dynamic rendering
If you're using nonces, you may need to explicitly opt pages into dynamic rendering:
```tsx filename="app/page.tsx" switcher
import { connection } from 'next/server'
export default async function Page() {
// wait for an incoming request to render this page
await connection()
// Your page content
}
```
```jsx filename="app/page.jsx" switcher
import { connection } from 'next/server'
export default async function Page() {
// wait for an incoming request to render this page
await connection()
// Your page content
}
```
### Reading the nonce
<PagesOnly>
You can provide the nonce to your page using
[`getServerSideProps`](/docs/pages/building-your-application/data-fetching/get-server-side-props):
```tsx filename="pages/index.tsx" switcher
import Script from 'next/script'
import type { GetServerSideProps } from 'next'
export default function Page({ nonce }) {
return (
<Script
src="https://www.googletagmanager.com/gtag/js"
strategy="afterInteractive"
nonce={nonce}
/>
)
}
export const getServerSideProps: GetServerSideProps = async ({ req }) => {
const nonce = req.headers['x-nonce']
return { props: { nonce } }
}
```
```jsx filename="pages/index.jsx" switcher
import Script from 'next/script'
export default function Page({ nonce }) {
return (
<Script
src="https://www.googletagmanager.com/gtag/js"
strategy="afterInteractive"
nonce={nonce}
/>
)
}
export async function getServerSideProps({ req }) {
const nonce = req.headers['x-nonce']
return { props: { nonce } }
}
```
You can also access the nonce in `_document.tsx` for Pages Router applications:
```tsx filename="pages/_document.tsx" switcher
import Document, {
Html,
Head,
Main,
NextScript,
DocumentContext,
DocumentInitialProps,
} from 'next/document'
interface ExtendedDocumentProps extends DocumentInitialProps {
nonce?: string
}
class MyDocument extends Document<ExtendedDocumentProps> {
static async getInitialProps(
ctx: DocumentContext
): Promise<ExtendedDocumentProps> {
const initialProps = await Document.getInitialProps(ctx)
const nonce = ctx.req?.headers?.['x-nonce'] as string | undefined
return {
...initialProps,
nonce,
}
}
render() {
const { nonce } = this.props
return (
<Html lang="en">
<Head nonce={nonce} />
<body>
<Main />
<NextScript nonce={nonce} />
</body>
</Html>
)
}
}
export default MyDocument
```
```jsx filename="pages/_document.jsx" switcher
import Document, { Html, Head, Main, NextScript } from 'next/document'
class MyDocument extends Document {
static async getInitialProps(ctx) {
const initialProps = await Document.getInitialProps(ctx)
const nonce = ctx.req?.headers?.['x-nonce']
return {
...initialProps,
nonce,
}
}
render() {
const { nonce } = this.props
return (
<Html lang="en">
<Head nonce={nonce} />
<body>
<Main />
<NextScript nonce={nonce} />
</body>
</Html>
)
}
}
export default MyDocument
```
</PagesOnly>
<AppOnly>
You can read the nonce from a [Server Component](/docs/app/getting-started/server-and-client-components) using [`headers`](/docs/app/api-reference/functions/headers):
```tsx filename="app/page.tsx" switcher
import { headers } from 'next/headers'
import Script from 'next/script'
export default async function Page() {
const nonce = (await headers()).get('x-nonce')
return (
<Script
src="https://www.googletagmanager.com/gtag/js"
strategy="afterInteractive"
nonce={nonce}
/>
)
}
```
```jsx filename="app/page.jsx" switcher
import { headers } from 'next/headers'
import Script from 'next/script'
export default async function Page() {
const nonce = (await headers()).get('x-nonce')
return (
<Script
src="https://www.googletagmanager.com/gtag/js"
strategy="afterInteractive"
nonce={nonce}
/>
)
}
```
</AppOnly>
## Static vs Dynamic Rendering with CSP
Using nonces has important implications for how your Next.js application renders:
### Dynamic Rendering Requirement
When you use nonces in your CSP, **all pages must be dynamically rendered**. This means:
- Pages will build successfully but may encounter runtime errors if not properly configured for dynamic rendering
- Each request generates a fresh page with a new nonce
- Static optimization and Incremental Static Regeneration (ISR) are disabled
- Pages cannot be cached by CDNs without additional configuration
- **Partial Prerendering (PPR) is incompatible** with nonce-based CSP since static shell scripts won't have access to the nonce
### Performance Implications
The shift from static to dynamic rendering affects performance:
- **Slower initial page loads**: Pages must be generated on each request
- **Increased server load**: Every request requires server-side rendering
- **No CDN caching**: Dynamic pages cannot be cached at the edge by default
- **Higher hosting costs**: More server resources needed for dynamic rendering
### When to use nonces
Consider nonces when:
- You have strict security requirements that prohibit `'unsafe-inline'`
- Your application handles sensitive data
- You need to allow specific inline scripts while blocking others
- Compliance requirements mandate strict CSP
## Without Nonces
For applications that do not require nonces, you can set the CSP header directly in your [`next.config.js`](/docs/app/api-reference/config/next-config-js) file:
```js filename="next.config.js"
const isDev = process.env.NODE_ENV === 'development'
const cspHeader = `
default-src 'self';
script-src 'self' 'unsafe-inline'${isDev ? " 'unsafe-eval'" : ''};
style-src 'self' 'unsafe-inline';
img-src 'self' blob: data:;
font-src 'self';
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
upgrade-insecure-requests;
`
module.exports = {
async headers() {
return [
{
source: '/(.*)',
headers: [
{
key: 'Content-Security-Policy',
value: cspHeader.replace(/\n/g, ''),
},
],
},
]
},
}
```
<AppOnly>
## Subresource Integrity (Experimental)
As an alternative to nonces, Next.js offers experimental support for hash-based CSP using Subresource Integrity (SRI). This approach allows you to maintain static generation while still having a strict CSP.
> **Good to know**: This feature is experimental and available in App Router applications.
### How SRI works
Instead of using nonces, SRI generates cryptographic hashes of your JavaScript files at build time. These hashes are added as `integrity` attributes to script tags, allowing browsers to verify that files haven't been modified during transit.
### Enabling SRI
Add the experimental SRI configuration to your `next.config.js`:
```js filename="next.config.js"
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
sri: {
algorithm: 'sha256', // or 'sha384' or 'sha512'
},
},
}
module.exports = nextConfig
```
### CSP configuration with SRI
When SRI is enabled, you can continue using your existing CSP policies. SRI works independently by adding `integrity` attributes to your assets:
> **Good to know**: For dynamic rendering scenarios, you can still generate nonces with proxy if needed, combining both SRI integrity attributes and nonce-based CSP approaches.
```js filename="next.config.js"
const isDev = process.env.NODE_ENV === 'development'
const cspHeader = `
default-src 'self';
script-src 'self'${isDev ? " 'unsafe-eval'" : ''};
style-src 'self';
img-src 'self' blob: data:;
font-src 'self';
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
upgrade-insecure-requests;
`
module.exports = {
experimental: {
sri: {
algorithm: 'sha256',
},
},
async headers() {
return [
{
source: '/(.*)',
headers: [
{
key: 'Content-Security-Policy',
value: cspHeader.replace(/\n/g, ''),
},
],
},
]
},
}
```
### Benefits of SRI over nonces
- **Static generation**: Pages can be statically generated and cached
- **CDN compatibility**: Static pages work with CDN caching
- **Better performance**: No server-side rendering required for each request
- **Build-time security**: Hashes are generated at build time, ensuring integrity
### Limitations of SRI
- **Experimental**: Feature may change or be removed
- **Webpack only**: Not available with Turbopack
- **App Router only**: Not supported in Pages Router
- **Build-time only**: Cannot handle dynamically generated scripts
</AppOnly>
## Development vs Production Considerations
CSP implementation differs between development and production environments:
### Development Environment
In development, you will need to enable `'unsafe-eval'` because React uses `eval` to provide enhanced debugging information, such as reconstructing server-side error stacks in the browser to show you where errors originated on the server:
```ts filename="proxy.ts" switcher
export function proxy(request: NextRequest) {
const nonce = Buffer.from(crypto.randomUUID()).toString('base64')
const isDev = process.env.NODE_ENV === 'development'
const cspHeader = `
default-src 'self';
script-src 'self' 'nonce-${nonce}' 'strict-dynamic' ${isDev ? "'unsafe-eval'" : ''};
style-src 'self' ${isDev ? "'unsafe-inline'" : `'nonce-${nonce}'`};
img-src 'self' blob: data:;
font-src 'self';
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
upgrade-insecure-requests;
`
// Rest of proxy implementation
}
```
```js filename="proxy.js" switcher
export function proxy(request) {
const nonce = Buffer.from(crypto.randomUUID()).toString('base64')
const isDev = process.env.NODE_ENV === 'development'
const cspHeader = `
default-src 'self';
script-src 'self' 'nonce-${nonce}' 'strict-dynamic' ${isDev ? "'unsafe-eval'" : ''};
style-src 'self' ${isDev ? "'unsafe-inline'" : `'nonce-${nonce}'`};
img-src 'self' blob: data:;
font-src 'self';
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
upgrade-insecure-requests;
`
// Rest of proxy implementation
}
```
### Production Deployment
Common issues in production:
- **Nonce not applied**: Ensure your proxy runs on all necessary routes
- **Static assets blocked**: Verify your CSP allows Next.js static assets
- **Third-party scripts**: Add necessary domains to your CSP policy
## Troubleshooting
### Third-party Scripts
<AppOnly>
When using third-party scripts with CSP:
```tsx filename="app/layout.tsx" switcher
import { GoogleTagManager } from '@next/third-parties/google'
import { headers } from 'next/headers'
export default async function RootLayout({
children,
}: {
children: React.ReactNode
}) {
const nonce = (await headers()).get('x-nonce')
return (
<html lang="en">
<body>
{children}
<GoogleTagManager gtmId="GTM-XYZ" nonce={nonce} />
</body>
</html>
)
}
```
```jsx filename="app/layout.jsx" switcher
import { GoogleTagManager } from '@next/third-parties/google'
import { headers } from 'next/headers'
export default async function RootLayout({ children }) {
const nonce = (await headers()).get('x-nonce')
return (
<html lang="en">
<body>
{children}
<GoogleTagManager gtmId="GTM-XYZ" nonce={nonce} />
</body>
</html>
)
}
```
</AppOnly>
<PagesOnly>
When using third-party scripts with CSP, ensure you add the necessary domains and pass the nonce:
```tsx filename="pages/_app.tsx" switcher
import type { AppProps } from 'next/app'
import Script from 'next/script'
export default function App({ Component, pageProps }: AppProps) {
const nonce = pageProps.nonce
return (
<>
<Component {...pageProps} />
<Script
src="https://www.googletagmanager.com/gtag/js"
strategy="afterInteractive"
nonce={nonce}
/>
</>
)
}
```
```jsx filename="pages/_app.jsx" switcher
import Script from 'next/script'
export default function App({ Component, pageProps }) {
const nonce = pageProps.nonce
return (
<>
<Component {...pageProps} />
<Script
src="https://www.googletagmanager.com/gtag/js"
strategy="afterInteractive"
nonce={nonce}
/>
</>
)
}
```
</PagesOnly>
Update your CSP to allow third-party domains:
```ts filename="proxy.ts" switcher
const cspHeader = `
default-src 'self';
script-src 'self' 'nonce-${nonce}' 'strict-dynamic' https://www.googletagmanager.com;
connect-src 'self' https://www.google-analytics.com;
img-src 'self' data: https://www.google-analytics.com;
`
```
```js filename="proxy.js" switcher
const cspHeader = `
default-src 'self';
script-src 'self' 'nonce-${nonce}' 'strict-dynamic' https://www.googletagmanager.com;
connect-src 'self' https://www.google-analytics.com;
img-src 'self' data: https://www.google-analytics.com;
`
```
### Common CSP Violations
1. **Inline styles**: Use CSS-in-JS libraries that support nonces or move styles to external files
2. **Dynamic imports**: Ensure dynamic imports are allowed in your script-src policy
3. **WebAssembly**: Add `'wasm-unsafe-eval'` if using WebAssembly
4. **Service workers**: Add appropriate policies for service worker scripts
## Version History
| Version | Changes |
| ---------- | ------------------------------------------------------------- |
| `v14.0.0` | Experimental SRI support added for hash-based CSP |
| `v13.4.20` | Recommended for proper nonce handling and CSP header parsing. |
+322
View File
@@ -0,0 +1,322 @@
---
title: How to use CSS-in-JS libraries
nav_title: CSS-in-JS
description: Use CSS-in-JS libraries with Next.js
---
{/* 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>
> **Warning:** Using CSS-in-JS with newer React features like Server Components and Streaming requires library authors to support the latest version of React, including [concurrent rendering](https://react.dev/blog/2022/03/29/react-v18#what-is-concurrent-react).
The following libraries are supported in Client Components in the `app` directory (alphabetical):
- [`ant-design`](https://ant.design/docs/react/use-with-next#using-app-router)
- [`chakra-ui`](https://chakra-ui.com/getting-started/nextjs-app-guide)
- [`@fluentui/react-components`](https://react.fluentui.dev/?path=/docs/concepts-developer-server-side-rendering-next-js-appdir-setup--page)
- [`kuma-ui`](https://kuma-ui.com)
- [`@mui/material`](https://mui.com/material-ui/guides/next-js-app-router/)
- [`@mui/joy`](https://mui.com/joy-ui/integrations/next-js-app-router/)
- [`pandacss`](https://panda-css.com)
- [`styled-jsx`](#styled-jsx)
- [`styled-components`](#styled-components)
- [`stylex`](https://stylexjs.com)
- [`tamagui`](https://tamagui.dev/docs/guides/next-js#server-components)
- [`tss-react`](https://tss-react.dev/)
- [`vanilla-extract`](https://vanilla-extract.style)
The following are currently working on support:
- [`emotion`](https://github.com/emotion-js/emotion/issues/2928)
> **Good to know**: We're testing out different CSS-in-JS libraries and we'll be adding more examples for libraries that support React 18 features and/or the `app` directory.
## Configuring CSS-in-JS in `app`
Configuring CSS-in-JS is a three-step opt-in process that involves:
1. A **style registry** to collect all CSS rules in a render.
2. The new `useServerInsertedHTML` hook to inject rules before any content that might use them.
3. A Client Component that wraps your app with the style registry during initial server-side rendering.
### `styled-jsx`
Using `styled-jsx` in Client Components requires using `v5.1.0`. First, create a new registry:
```tsx filename="app/registry.tsx" switcher
'use client'
import React, { useState } from 'react'
import { useServerInsertedHTML } from 'next/navigation'
import { StyleRegistry, createStyleRegistry } from 'styled-jsx'
export default function StyledJsxRegistry({
children,
}: {
children: React.ReactNode
}) {
// Only create stylesheet once with lazy initial state
// x-ref: https://reactjs.org/docs/hooks-reference.html#lazy-initial-state
const [jsxStyleRegistry] = useState(() => createStyleRegistry())
useServerInsertedHTML(() => {
const styles = jsxStyleRegistry.styles()
jsxStyleRegistry.flush()
return <>{styles}</>
})
return <StyleRegistry registry={jsxStyleRegistry}>{children}</StyleRegistry>
}
```
```jsx filename="app/registry.js" switcher
'use client'
import React, { useState } from 'react'
import { useServerInsertedHTML } from 'next/navigation'
import { StyleRegistry, createStyleRegistry } from 'styled-jsx'
export default function StyledJsxRegistry({ children }) {
// Only create stylesheet once with lazy initial state
// x-ref: https://reactjs.org/docs/hooks-reference.html#lazy-initial-state
const [jsxStyleRegistry] = useState(() => createStyleRegistry())
useServerInsertedHTML(() => {
const styles = jsxStyleRegistry.styles()
jsxStyleRegistry.flush()
return <>{styles}</>
})
return <StyleRegistry registry={jsxStyleRegistry}>{children}</StyleRegistry>
}
```
Then, wrap your [root layout](/docs/app/api-reference/file-conventions/layout#root-layout) with the registry:
```tsx filename="app/layout.tsx" switcher
import StyledJsxRegistry from './registry'
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html>
<body>
<StyledJsxRegistry>{children}</StyledJsxRegistry>
</body>
</html>
)
}
```
```jsx filename="app/layout.js" switcher
import StyledJsxRegistry from './registry'
export default function RootLayout({ children }) {
return (
<html>
<body>
<StyledJsxRegistry>{children}</StyledJsxRegistry>
</body>
</html>
)
}
```
[View an example here](https://github.com/vercel/next.js/tree/canary/examples/with-styled-jsx).
### Styled Components
Below is an example of how to configure `styled-components@6` or newer:
First, enable styled-components in `next.config.js`.
```js filename="next.config.js"
module.exports = {
compiler: {
styledComponents: true,
},
}
```
Then, use the `styled-components` API to create a global registry component to collect all CSS style rules generated during a render, and a function to return those rules. Then use the `useServerInsertedHTML` hook to inject the styles collected in the registry into the `<head>` HTML tag in the root layout.
```tsx filename="lib/registry.tsx" switcher
'use client'
import React, { useState } from 'react'
import { useServerInsertedHTML } from 'next/navigation'
import { ServerStyleSheet, StyleSheetManager } from 'styled-components'
export default function StyledComponentsRegistry({
children,
}: {
children: React.ReactNode
}) {
// Only create stylesheet once with lazy initial state
// x-ref: https://reactjs.org/docs/hooks-reference.html#lazy-initial-state
const [styledComponentsStyleSheet] = useState(() => new ServerStyleSheet())
useServerInsertedHTML(() => {
const styles = styledComponentsStyleSheet.getStyleElement()
styledComponentsStyleSheet.instance.clearTag()
return <>{styles}</>
})
if (typeof window !== 'undefined') return <>{children}</>
return (
<StyleSheetManager sheet={styledComponentsStyleSheet.instance}>
{children}
</StyleSheetManager>
)
}
```
```jsx filename="lib/registry.js" switcher
'use client'
import React, { useState } from 'react'
import { useServerInsertedHTML } from 'next/navigation'
import { ServerStyleSheet, StyleSheetManager } from 'styled-components'
export default function StyledComponentsRegistry({ children }) {
// Only create stylesheet once with lazy initial state
// x-ref: https://reactjs.org/docs/hooks-reference.html#lazy-initial-state
const [styledComponentsStyleSheet] = useState(() => new ServerStyleSheet())
useServerInsertedHTML(() => {
const styles = styledComponentsStyleSheet.getStyleElement()
styledComponentsStyleSheet.instance.clearTag()
return <>{styles}</>
})
if (typeof window !== 'undefined') return <>{children}</>
return (
<StyleSheetManager sheet={styledComponentsStyleSheet.instance}>
{children}
</StyleSheetManager>
)
}
```
Wrap the `children` of the root layout with the style registry component:
```tsx filename="app/layout.tsx" switcher
import StyledComponentsRegistry from './lib/registry'
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html>
<body>
<StyledComponentsRegistry>{children}</StyledComponentsRegistry>
</body>
</html>
)
}
```
```jsx filename="app/layout.js" switcher
import StyledComponentsRegistry from './lib/registry'
export default function RootLayout({ children }) {
return (
<html>
<body>
<StyledComponentsRegistry>{children}</StyledComponentsRegistry>
</body>
</html>
)
}
```
[View an example here](https://github.com/vercel/next.js/tree/canary/examples/with-styled-components).
> **Good to know**:
>
> - During server rendering, styles will be extracted to a global registry and flushed to the `<head>` of your HTML. This ensures the style rules are placed before any content that might use them. In the future, we may use an upcoming React feature to determine where to inject the styles.
> - During streaming, styles from each chunk will be collected and appended to existing styles. After client-side hydration is complete, `styled-components` will take over as usual and inject any further dynamic styles.
> - We specifically use a Client Component at the top level of the tree for the style registry because it's more efficient to extract CSS rules this way. It avoids re-generating styles on subsequent server renders, and prevents them from being sent in the Server Component payload.
> - For advanced use cases where you need to configure individual properties of styled-components compilation, you can read our [Next.js styled-components API reference](/docs/architecture/nextjs-compiler#styled-components) to learn more.
</AppOnly>
<PagesOnly>
<details>
<summary>Examples</summary>
- [Styled JSX](https://github.com/vercel/next.js/tree/canary/examples/with-styled-jsx)
- [Styled Components](https://github.com/vercel/next.js/tree/canary/examples/with-styled-components)
- [Emotion](https://github.com/vercel/next.js/tree/canary/examples/with-emotion)
- [Linaria](https://github.com/vercel/next.js/tree/canary/examples/with-linaria)
- [Styletron](https://github.com/vercel/next.js/tree/canary/examples/with-styletron)
- [Cxs](https://github.com/vercel/next.js/tree/canary/examples/with-cxs)
- [Fela](https://github.com/vercel/next.js/tree/canary/examples/with-fela)
- [Stitches](https://github.com/vercel/next.js/tree/canary/examples/with-stitches)
</details>
It's possible to use any existing CSS-in-JS solution. The simplest one is inline styles:
```jsx
function HiThere() {
return <p style={{ color: 'red' }}>hi there</p>
}
export default HiThere
```
We bundle [styled-jsx](https://github.com/vercel/styled-jsx) to provide support for isolated scoped CSS.
The aim is to support "shadow CSS" similar to Web Components, which unfortunately [do not support server-rendering and are JS-only](https://github.com/w3c/webcomponents/issues/71).
See the above examples for other popular CSS-in-JS solutions (like Styled Components).
A component using `styled-jsx` looks like this:
```jsx
function HelloWorld() {
return (
<div>
Hello world
<p>scoped!</p>
<style jsx>{`
p {
color: blue;
}
div {
background: red;
}
@media (max-width: 600px) {
div {
background: blue;
}
}
`}</style>
<style global jsx>{`
body {
background: black;
}
`}</style>
</div>
)
}
export default HelloWorld
```
Please see the [styled-jsx documentation](https://github.com/vercel/styled-jsx) for more examples.
### Disabling JavaScript
Yes, if you disable JavaScript the CSS will still be loaded in the production build (`next start`). During development, we require JavaScript to be enabled to provide the best developer experience with [Fast Refresh](https://nextjs.org/blog/next-9-4#fast-refresh).
</PagesOnly>
+118
View File
@@ -0,0 +1,118 @@
---
title: How to set up a custom server in Next.js
nav_title: Custom Server
description: Start a Next.js app programmatically using a custom 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 includes its own server with `next start` by default. If you have an existing backend, you can still use it with Next.js (this is not a custom server). A custom Next.js server allows you to programmatically start a server for custom patterns. The majority of the time, you will not need this approach. However, it's available if you need to eject.
> **Good to know**:
>
> - Before deciding to use a custom server, keep in mind that it should only be used when the integrated router of Next.js can't meet your app requirements. A custom server will remove important performance optimizations, like **[Automatic Static Optimization](/docs/pages/building-your-application/rendering/automatic-static-optimization).**
> - When using standalone output mode, it does not trace custom server files. This mode outputs a separate minimal `server.js` file, instead. These cannot be used together.
Take a look at the [following example](https://github.com/vercel/next.js/tree/canary/examples/custom-server) of a custom server:
```ts filename="server.ts" switcher
import { createServer } from 'http'
import next from 'next'
const port = parseInt(process.env.PORT || '3000', 10)
const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()
app.prepare().then(() => {
createServer((req, res) => {
handle(req, res)
}).listen(port)
console.log(
`> Server listening at http://localhost:${port} as ${
dev ? 'development' : process.env.NODE_ENV
}`
)
})
```
```js filename="server.js" switcher
import { createServer } from 'http'
import next from 'next'
const port = parseInt(process.env.PORT || '3000', 10)
const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()
app.prepare().then(() => {
createServer((req, res) => {
handle(req, res)
}).listen(port)
console.log(
`> Server listening at http://localhost:${port} as ${
dev ? 'development' : process.env.NODE_ENV
}`
)
})
```
> `server.js` does not run through the Next.js Compiler or bundling process. Make sure the syntax and source code this file requires are compatible with the current Node.js version you are using. [View an example](https://github.com/vercel/next.js/tree/canary/examples/custom-server).
To run the custom server, you'll need to update the `scripts` in `package.json` like so:
```json filename="package.json"
{
"scripts": {
"dev": "node server.js",
"build": "next build",
"start": "NODE_ENV=production node server.js"
}
}
```
Alternatively, you can set up `nodemon` ([example](https://github.com/vercel/next.js/tree/canary/examples/custom-server)). The custom server uses the following import to connect the server with the Next.js application:
```js
import next from 'next'
const app = next({})
```
The above `next` import is a function that receives an object with the following options:
| Option | Type | Description |
| ------------ | ------------------ | ----------------------------------------------------------------------------------- |
| `conf` | `Object` | The same object you would use in `next.config.js`. Defaults to `{}` |
| `dev` | `Boolean` | (_Optional_) Whether or not to launch Next.js in dev mode. Defaults to `false` |
| `dir` | `String` | (_Optional_) Location of the Next.js project. Defaults to `'.'` |
| `quiet` | `Boolean` | (_Optional_) Hide error messages containing server information. Defaults to `false` |
| `hostname` | `String` | (_Optional_) The hostname the server is running behind |
| `port` | `Number` | (_Optional_) The port the server is running behind |
| `httpServer` | `node:http#Server` | (_Optional_) The HTTP Server that Next.js is running behind |
| `turbopack` | `Boolean` | (_Optional_) Enable Turbopack (enabled by default) |
| `webpack` | `Boolean` | (_Optional_) Enable webpack |
The returned `app` can then be used to let Next.js handle requests as required.
<PagesOnly>
## Disabling file-system routing
By default, `Next` will serve each file in the `pages` folder under a pathname matching the filename. If your project uses a custom server, this behavior may result in the same content being served from multiple paths, which can present problems with SEO and UX.
To disable this behavior and prevent routing based on files in `pages`, open `next.config.js` and disable the `useFileSystemPublicRoutes` config:
```js filename="next.config.js"
module.exports = {
useFileSystemPublicRoutes: false,
}
```
> Note that `useFileSystemPublicRoutes` disables filename routes from SSR; client-side routing may still access those paths. When using this option, you should guard against navigation to routes you do not want programmatically.
> You may also wish to configure the client-side router to disallow client-side redirects to filename routes; for that refer to [`router.beforePopState`](/docs/pages/api-reference/functions/use-router#routerbeforepopstate).
</PagesOnly>
+600
View File
@@ -0,0 +1,600 @@
---
title: How to think about data security in Next.js
nav_title: Data Security
description: Learn the built-in data security features in Next.js and learn best practices for protecting your application's data.
related:
title: Next Steps
description: Learn more about the topics mentioned in this guide.
links:
- app/guides/authentication
- app/guides/content-security-policy
- app/guides/forms
---
[React Server Components](https://react.dev/reference/rsc/server-components) improve performance and simplify data fetching, but also shift where and how data is accessed, changing some of the traditional security assumptions for handling data in frontend apps.
This guide will help you understand how to think about data security in Next.js and how to implement best practices.
## Data fetching approaches
There are three main approaches we recommend for fetching data in Next.js, depending on the size and age of your project:
- [HTTP APIs](#external-http-apis): for existing large applications and organizations.
- [Data Access Layer](#data-access-layer): for new projects.
- [Component-Level Data Access](#component-level-data-access): for prototypes and learning.
We recommend choosing one data fetching approach and avoiding mixing them. This makes it clear for both developers working in your code base and security auditors what to expect.
### External HTTP APIs
You should follow a **Zero Trust** model when adopting Server Components in an existing project. You can continue calling your existing API endpoints such as REST or GraphQL from Server Components using [`fetch`](/docs/app/api-reference/functions/fetch), just as you would in Client Components.
```tsx filename="app/page.tsx"
import { cookies } from 'next/headers'
export default async function Page() {
const cookieStore = cookies()
const token = cookieStore.get('AUTH_TOKEN')?.value
const res = await fetch('https://api.example.com/profile', {
headers: {
Cookie: `AUTH_TOKEN=${token}`,
// Other headers
},
})
// ....
}
```
This approach works well when:
- You already have security practices in place.
- Separate backend teams use other languages or manage APIs independently.
### Data Access Layer
For new projects, we recommend creating a dedicated **Data Access Layer (DAL)**. This is a internal library that controls how and when data is fetched, and what gets passed to your render context.
A Data Access Layer should:
- Only run on the server.
- Perform authorization checks.
- Return safe, minimal **Data Transfer Objects (DTOs)**.
This approach centralizes all data access logic, making it easier to enforce consistent data access and reduces the risk of authorization bugs. You also get the benefit of sharing an in-memory cache across different parts of a request.
```ts filename="data/auth.ts"
import { cache } from 'react'
import { cookies } from 'next/headers'
// Cached helper methods makes it easy to get the same value in many places
// without manually passing it around. This discourages passing it from Server
// Component to Server Component which minimizes risk of passing it to a Client
// Component.
export const getCurrentUser = cache(async () => {
const token = cookies().get('AUTH_TOKEN')
const decodedToken = await decryptAndValidate(token)
// Don't include secret tokens or private information as public fields.
// Use classes to avoid accidentally passing the whole object to the client.
return new User(decodedToken.id)
})
```
```tsx filename="data/user-dto.tsx"
import 'server-only'
import { getCurrentUser } from './auth'
function canSeeUsername(viewer: User) {
// Public info for now, but can change
return true
}
function canSeePhoneNumber(viewer: User, team: string) {
// Privacy rules
return viewer.isAdmin || team === viewer.team
}
export async function getProfileDTO(slug: string) {
// Don't pass values, read back cached values, also solves context and easier to make it lazy
// use a database API that supports safe templating of queries
const [rows] = await sql`SELECT * FROM user WHERE slug = ${slug}`
const userData = rows[0]
const currentUser = await getCurrentUser()
// only return the data relevant for this query and not everything
// <https://www.w3.org/2001/tag/doc/APIMinimization>
return {
username: canSeeUsername(currentUser) ? userData.username : null,
phonenumber: canSeePhoneNumber(currentUser, userData.team)
? userData.phonenumber
: null,
}
}
```
```tsx filename="app/page.tsx"
import { getProfile } from '../../data/user'
export async function Page({ params: { slug } }) {
// This page can now safely pass around this profile knowing
// that it shouldn't contain anything sensitive.
const profile = await getProfile(slug);
...
}
```
> **Good to know:** Secret keys should be stored in environment variables, but only the Data Access Layer should access `process.env`. This keeps secrets from being exposed to other parts of the application.
### Component-level data access
For quick prototypes and iteration, database queries can be placed directly in Server Components.
This approach, however, makes it easier to accidentally expose private data to the client, for example:
```tsx filename="app/page.tsx"
import Profile from './components/profile.tsx'
export async function Page({ params: { slug } }) {
const [rows] = await sql`SELECT * FROM user WHERE slug = ${slug}`
const userData = rows[0]
// EXPOSED: This exposes all the fields in userData to the client because
// we are passing the data from the Server Component to the Client.
return <Profile user={userData} />
}
```
```tsx filename="app/ui/profile.tsx"
'use client'
// BAD: This is a bad props interface because it accepts way more data than the
// Client Component needs and it encourages server components to pass all that
// data down. A better solution would be to accept a limited object with just
// the fields necessary for rendering the profile.
export default async function Profile({ user }: { user: User }) {
return (
<div>
<h1>{user.name}</h1>
...
</div>
)
}
```
You should sanitize the data before passing it to the Client Component:
```ts filename="data/user.ts"
import { sql } from './db'
export async function getUser(slug: string) {
const [rows] = await sql`SELECT * FROM user WHERE slug = ${slug}`
const user = rows[0]
// Return only the public fields
return {
name: user.name,
}
}
```
```tsx filename="app/page.tsx"
import { getUser } from '../data/user'
import Profile from './ui/profile'
export default async function Page({
params: { slug },
}: {
params: { slug: string }
}) {
const publicProfile = await getUser(slug)
return <Profile user={publicProfile} />
}
```
## Reading data
### Passing data from server to client
On the initial load, both Server and Client Components run on the server to generate HTML. However, they execute in isolated module systems. This ensures that Server Components can access private data and APIs, while Client Components cannot.
**Server Components:**
- Run only on the server.
- Can safely access environment variables, secrets, databases, and internal APIs.
**Client Components:**
- Run on the server during prerendering, but must follow the same security assumptions as code running in the browser.
- Must not access privileged data or server-only modules.
This ensures the app is secure by default, but it's possible to accidentally expose private data through how data is fetched or passed to components.
### Tainting
To prevent accidental exposure of private data to the client, you can use React Taint APIs:
- [`experimental_taintObjectReference`](https://react.dev/reference/react/experimental_taintObjectReference) for data objects.
- [`experimental_taintUniqueValue`](https://react.dev/reference/react/experimental_taintUniqueValue) for specific values.
You can enable usage in your Next.js app with the [`experimental.taint`](/docs/app/api-reference/config/next-config-js/taint) option in `next.config.js`:
```js filename="next.config.js"
module.exports = {
experimental: {
taint: true,
},
}
```
This prevents the tainted objects or values from being passed to the client. However, it's an additional layer of protection, you should still filter and sanitize the data in your [DAL](#data-access-layer) before passing it to React's render context.
> **Good to know:**
>
> - By default, environment variables are only available on the Server. Next.js exposes any environment variable prefixed with `NEXT_PUBLIC_` to the client. [Learn more](/docs/app/guides/environment-variables).
> - Functions and classes are already blocked from being passed to Client Components by default.
### Preventing client-side execution of server-only code
To prevent server-only code from being executed on the client, you can mark a module with the [`server-only`](https://www.npmjs.com/package/server-only) package:
```bash package="npm"
npm install server-only
```
```bash package="yarn"
yarn add server-only
```
```bash package="pnpm"
pnpm add server-only
```
```bash package="bun"
bun add server-only
```
```ts filename="lib/data.ts"
import 'server-only'
//...
```
This ensures that proprietary code or internal business logic stays on the server by causing a build error if the module is imported in the client environment.
## Mutating Data
Next.js handles mutations with [Server Actions](https://react.dev/reference/rsc/server-functions).
### Built-in Server Actions Security features
By default, when a Server Action is created and exported, it is reachable via a direct POST request, not just through your application's UI. This means, even if a Server Action or utility function is not imported elsewhere in your code, it can still be called externally.
To improve security, Next.js has the following built-in features:
- **Secure action IDs:** Next.js creates encrypted, non-deterministic IDs to allow the client to reference and call the Server Action. These IDs are periodically recalculated between builds for enhanced security.
- **Dead code elimination:** Unused Server Actions (referenced by their IDs) are removed from client bundle to avoid public access.
> **Good to know**:
>
> The IDs are created during compilation and are cached for a maximum of 14 days. They will be regenerated when a new build is initiated or when the build cache is invalidated.
> This security improvement reduces the risk in cases where an authentication layer is missing. However, you should still treat Server Actions as reachable via direct POST requests and verify authentication and authorization inside each one.
```jsx
// app/actions.js
'use server'
// If this action **is** used in our application, Next.js
// will create a secure ID to allow the client to reference
// and call the Server Action.
export async function updateUserAction(formData) {}
// If this action **is not** used in our application, Next.js
// will automatically remove this code during `next build`
// and will not create a public endpoint.
export async function deleteUserAction(formData) {}
```
### Validating client input
You should always validate input from client, as they can be easily modified. For example, form data, URL parameters, headers, and searchParams:
```tsx filename="app/page.tsx"
// BAD: Trusting searchParams directly
export default async function Page({ searchParams }) {
const isAdmin = searchParams.get('isAdmin')
if (isAdmin === 'true') {
// Vulnerable: relies on untrusted client data
return <AdminPanel />
}
}
// GOOD: Re-verify every time
import { cookies } from 'next/headers'
import { verifyAdmin } from './auth'
export default async function Page() {
const token = cookies().get('AUTH_TOKEN')
const isAdmin = await verifyAdmin(token)
if (isAdmin) {
return <AdminPanel />
}
}
```
### Authentication and authorization
A page-level authentication check does not extend to the Server Actions defined within it. Always re-verify inside the action:
```tsx filename="app/admin/page.tsx" highlight={13,14,15,16}
import { auth } from '@/lib/auth'
import { redirect } from 'next/navigation'
export default async function AdminPage() {
const session = await auth()
if (!session?.user?.isAdmin) {
redirect('/login')
}
return (
<form
action={async () => {
'use server'
const session = await auth()
if (!session?.user?.isAdmin) {
throw new Error('Unauthorized')
}
await db.record.deleteMany()
}}
>
<button>Delete Records</button>
</form>
)
}
```
The highlighted `auth()` check inside the action is critical. The page-level redirect on line 6 controls which UI is rendered, but the Server Action is a separate entry point and must verify the caller on its own.
Beyond authentication (is the user logged in?), remember to check **authorization** (does this user have permission to act on this specific resource?). This prevents [Insecure Direct Object Reference (IDOR)](https://cheatsheetseries.owasp.org/cheatsheets/Insecure_Direct_Object_Reference_Prevention_Cheat_Sheet.html) vulnerabilities:
```tsx filename="app/actions.ts"
'use server'
import { auth } from '@/lib/auth'
import { db } from '@/lib/db'
export async function deletePost(postId: string) {
const session = await auth()
if (!session?.user) {
throw new Error('Unauthorized')
}
const post = await db.post.findUnique({ where: { id: postId } })
// Check that the user owns this resource
if (post.authorId !== session.user.id) {
throw new Error('Forbidden')
}
await db.post.delete({ where: { id: postId } })
}
```
Learn more about [Authentication](/docs/app/guides/authentication) in Next.js.
### Using a Data Access Layer for mutations
Just as we recommend a [Data Access Layer](#data-access-layer) for reading data, you can apply the same pattern to mutations. This keeps authentication, authorization, and database logic in a dedicated `server-only` module, while `"use server"` actions stay thin.
```ts filename="data/posts.ts"
import 'server-only'
import { auth } from '@/lib/auth'
import { db } from '@/lib/db'
export async function deletePost(postId: string) {
const session = await auth()
if (!session?.user) {
throw new Error('Unauthorized')
}
const post = await db.post.findUnique({ where: { id: postId } })
if (post.authorId !== session.user.id) {
throw new Error('Forbidden')
}
await db.post.delete({ where: { id: postId } })
}
```
The `"use server"` action then delegates to the DAL:
```ts filename="app/actions.ts"
'use server'
import { deletePost } from '@/data/posts'
import { revalidatePath } from 'next/cache'
export async function deletePostAction(postId: string) {
await deletePost(postId) // Auth + authz happen inside the DAL
revalidatePath('/posts')
}
```
> **Good to know:** You can use `import 'server-only'` in both the Data Access Layer and the `"use server"` file itself. Both work when the action is imported into a Client Component (for example, to pass it to `useActionState`), because `"use server"` modules are resolved in a server-only webpack layer.
### Controlling return values
Server Action return values are serialized and sent to the client. Only return what the UI needs, not raw database records.
```tsx filename="app/actions.ts"
'use server'
import { auth } from '@/lib/auth'
import { db } from '@/lib/db'
// BAD: Returns the full database record, which may include
// internal fields the client should not see.
export async function updateUser(data: FormData) {
const session = await auth()
if (!session?.user) {
throw new Error('Unauthorized')
}
return db.user.update({
where: { id: session.user.id },
data: { name: data.get('name') as string },
})
}
// GOOD: Returns only what the client needs.
export async function updateUserSafe(data: FormData) {
const session = await auth()
if (!session?.user) {
throw new Error('Unauthorized')
}
await db.user.update({
where: { id: session.user.id },
data: { name: data.get('name') as string },
})
return { success: true }
}
```
### Rate limiting
For expensive operations (sending emails, writing to a database), consider adding rate limiting to prevent abuse. See the [Rate limiting](/docs/app/guides/backend-for-frontend#rate-limiting) example in the Backend for Frontend guide.
### Closures and encryption
Defining a Server Action inside a component creates a [closure](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures) where the action has access to the outer function's scope. For example, the `publish` action has access to the `publishVersion` variable:
```tsx filename="app/page.tsx" switcher
export default async function Page() {
const publishVersion = await getLatestVersion();
async function publish() {
"use server";
if (publishVersion !== await getLatestVersion()) {
throw new Error('The version has changed since pressing publish');
}
...
}
return (
<form>
<button formAction={publish}>Publish</button>
</form>
);
}
```
```jsx filename="app/page.js" switcher
export default async function Page() {
const publishVersion = await getLatestVersion();
async function publish() {
"use server";
if (publishVersion !== await getLatestVersion()) {
throw new Error('The version has changed since pressing publish');
}
...
}
return (
<form>
<button formAction={publish}>Publish</button>
</form>
);
}
```
Closures are useful when you need to capture a _snapshot_ of data (e.g. `publishVersion`) at the time of rendering so that it can be used later when the action is invoked.
However, for this to happen, the captured variables are sent to the client and back to the server when the action is invoked. To prevent sensitive data from being exposed to the client, Next.js automatically encrypts the closed-over variables. A new private key is generated for each action every time a Next.js application is built. This means actions can only be invoked for a specific build.
> **Good to know:** We don't recommend relying on encryption alone to prevent sensitive values from being exposed on the client.
### Overwriting encryption keys (advanced)
When **self-hosting** your Next.js application across multiple servers, each server instance may end up with a different encryption key, leading to potential inconsistencies.
To mitigate this, you can overwrite the encryption key using the `process.env.NEXT_SERVER_ACTIONS_ENCRYPTION_KEY` environment variable. Specifying this variable ensures that your encryption keys are persistent across builds, and all server instances use the same key.
The key must be a base64-encoded value whose decoded length matches a valid AES key size (16, 24, or 32 bytes). Next.js generates 32-byte keys by default. You can generate a compatible key using your platforms cryptographic tools, for example:
```bash
openssl rand -base64 32
```
This is an advanced use case where consistent encryption behavior across multiple deployments is critical for your application. Follow standard security practices such as key rotation and signing. See the [Self-Hosting guide](/docs/app/guides/self-hosting#server-functions-encryption-key) for deployment-specific considerations.
### Allowed origins (advanced)
Since Server Actions can be invoked in a `<form>` element, this opens them up to [CSRF attacks](https://developer.mozilla.org/en-US/docs/Glossary/CSRF).
Behind the scenes, Server Actions use the `POST` method, and only this HTTP method is allowed to invoke them. This prevents most CSRF vulnerabilities in modern browsers, particularly with [SameSite cookies](https://web.dev/articles/samesite-cookies-explained) being the default.
As an additional protection, Server Actions in Next.js also compare the [Origin header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin) to the [Host header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Host) (or `X-Forwarded-Host`). If these don't match, the request will be aborted. In other words, Server Actions can only be invoked on the same host as the page that hosts it.
For large applications that use reverse proxies or multi-layered backend architectures (where the server API differs from the production domain), it's recommended to use the configuration option [`serverActions.allowedOrigins`](/docs/app/api-reference/config/next-config-js/serverActions) option to specify a list of safe origins. The option accepts an array of strings.
```js filename="next.config.js"
/** @type {import('next').NextConfig} */
module.exports = {
experimental: {
serverActions: {
allowedOrigins: ['my-proxy.com', '*.my-proxy.com'],
},
},
}
```
Learn more about [Security and Server Actions](https://nextjs.org/blog/security-nextjs-server-components-actions).
### Avoiding side-effects during rendering
Mutations (e.g. logging out users, updating databases, invalidating caches) should never be a side-effect, either in Server or Client Components. Next.js explicitly prevents setting cookies or triggering cache revalidation within render methods to avoid unintended side effects.
```tsx filename="app/page.tsx"
// BAD: Triggering a mutation during rendering
export default async function Page({ searchParams }) {
if (searchParams.get('logout')) {
cookies().delete('AUTH_TOKEN')
}
return <UserProfile />
}
```
Instead, you should use Server Actions to handle mutations.
```tsx filename="app/page.tsx"
// GOOD: Using Server Actions to handle mutations
import { logout } from './actions'
export default function Page() {
return (
<>
<UserProfile />
<form action={logout}>
<button type="submit">Logout</button>
</form>
</>
)
}
```
> **Good to know:** Next.js uses `POST` requests to handle mutations. This prevents accidental side-effects from GET requests, reducing Cross-Site Request Forgery (CSRF) risks.
## Auditing
If you're doing an audit of a Next.js project, here are a few things we recommend looking extra at:
- **Data Access Layer:** Is there an established practice for an isolated Data Access Layer? Verify that database packages and environment variables are not imported outside the Data Access Layer.
- **`"use client"` files:** Are the Component props expecting private data? Are the type signatures overly broad?
- **`"use server"` files:** Are the Action arguments validated in the action or inside the Data Access Layer? Is the user re-authorized inside the action? Does the action check ownership of the resource (authorization, not just authentication)? Are return values filtered to only what the client needs? Is database access delegated to a `server-only` Data Access Layer?
- **`/[param]/.`** Folders with brackets are user input. Are params validated?
- **`proxy.ts` and `route.ts`:** Have a lot of power. Spend extra time auditing these using traditional techniques. Perform Penetration Testing or Vulnerability Scanning regularly or in alignment with your team's software development lifecycle.
+179
View File
@@ -0,0 +1,179 @@
---
title: How to use debugging tools with Next.js
nav_title: Debugging
description: Learn how to debug your Next.js application with VS Code, Chrome DevTools, or Firefox DevTools.
---
{/* 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 documentation explains how you can debug your Next.js frontend and backend code with full source maps support using the [VS Code debugger](https://code.visualstudio.com/docs/editor/debugging), [Chrome DevTools](https://developers.google.com/web/tools/chrome-devtools), or [Firefox DevTools](https://firefox-source-docs.mozilla.org/devtools-user/).
Any debugger that can attach to Node.js can also be used to debug a Next.js application. You can find more details in the Node.js [Debugging Guide](https://nodejs.org/en/docs/guides/debugging-getting-started/).
## Debugging with VS Code
Create a file named `.vscode/launch.json` at the root of your project with the following content:
```json filename="launch.json"
{
"version": "0.2.0",
"configurations": [
{
"name": "Next.js: debug server-side",
"type": "node-terminal",
"request": "launch",
"command": "npm run dev -- --inspect"
},
{
"name": "Next.js: debug client-side",
"type": "chrome",
"request": "launch",
"url": "http://localhost:3000"
},
{
"name": "Next.js: debug client-side (Firefox)",
"type": "firefox",
"request": "launch",
"url": "http://localhost:3000",
"reAttach": true,
"pathMappings": [
{
"url": "webpack://_N_E",
"path": "${workspaceFolder}"
}
]
},
{
"name": "Next.js: debug full stack",
"type": "node",
"request": "launch",
"program": "${workspaceFolder}/node_modules/next/dist/bin/next",
"runtimeArgs": ["--inspect"],
"skipFiles": ["<node_internals>/**"],
"serverReadyAction": {
"action": "debugWithEdge",
"killOnServerStop": true,
"pattern": "- Local:.+(https?://.+)",
"uriFormat": "%s",
"webRoot": "${workspaceFolder}"
}
}
]
}
```
> **Note**: To use Firefox debugging in VS Code, you'll need to install the [Firefox Debugger extension](https://marketplace.visualstudio.com/items?itemName=firefox-devtools.vscode-firefox-debug).
`npm run dev` can be replaced with `yarn dev` if you're using Yarn or `pnpm dev` if you're using pnpm.
In the "Next.js: debug full stack" configuration, `serverReadyAction.action` specifies which browser to open when the server is ready. `debugWithEdge` means to launch the Edge browser. If you are using Chrome, change this value to `debugWithChrome`.
If you're [changing the port number](/docs/pages/api-reference/cli/next#next-dev-options) your application starts on, replace the `3000` in `http://localhost:3000` with the port you're using instead.
If you're running Next.js from a directory other than root (for example, if you're using Turborepo) then you need to add `cwd` to the server-side and full stack debugging tasks. For example, `"cwd": "${workspaceFolder}/apps/web"`.
Now go to the Debug panel (`Ctrl+Shift+D` on Windows/Linux, `⇧+⌘+D` on macOS), select a launch configuration, then press `F5` or select **Debug: Start Debugging** from the Command Palette to start your debugging session.
## Using the Debugger in Jetbrains WebStorm
Click the drop down menu listing the runtime configuration, and click `Edit Configurations...`. Create a `JavaScript Debug` debug configuration with `http://localhost:3000` as the URL. Customize to your liking (e.g. Browser for debugging, store as project file), and click `OK`. Run this debug configuration, and the selected browser should automatically open. At this point, you should have 2 applications in debug mode: the NextJS node application, and the client/browser application.
## Debugging with Browser DevTools
### Client-side code
Start your development server as usual by running `next dev`, `npm run dev`, or `yarn dev`. Once the server starts, open `http://localhost:3000` (or your alternate URL) in your preferred browser.
For Chrome:
- Open Chrome's Developer Tools (`Ctrl+Shift+J` on Windows/Linux, `⌥+⌘+I` on macOS)
- Go to the **Sources** tab
For Firefox:
- Open Firefox's Developer Tools (`Ctrl+Shift+I` on Windows/Linux, `⌥+⌘+I` on macOS)
- Go to the **Debugger** tab
In either browser, any time your client-side code reaches a [`debugger`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/debugger) statement, code execution will pause and that file will appear in the debug area. You can also search for files to set breakpoints manually:
- In Chrome: Press `Ctrl+P` on Windows/Linux or `⌘+P` on macOS
- In Firefox: Press `Ctrl+P` on Windows/Linux or `⌘+P` on macOS, or use the file tree in the left panel
Note that when searching, your source files will have paths starting with `webpack://_N_E/./`.
### React Developer Tools
For React-specific debugging, install the [React Developer Tools](https://react.dev/learn/react-developer-tools) browser extension. This essential tool helps you:
- Inspect React components
- Edit props and state
- Identify performance problems
### Server-side code
To debug server-side Next.js code with browser DevTools, you need to pass the `--inspect` flag:
```bash package="pnpm"
pnpm dev --inspect
```
```bash package="npm"
npm run dev -- --inspect
```
```bash package="yarn"
yarn dev --inspect
```
```bash package="bun"
bun run dev --inspect
```
The value of `--inspect` is passed to the underlying Node.js process. Check out the [`--inspect` docs for advanced use cases](https://nodejs.org/api/cli.html#--inspecthostport).
> **Good to know**: Use `--inspect=0.0.0.0` to allow remote debugging access outside localhost, such as when running the app in a Docker container.
Launching the Next.js server with the `--inspect` flag will look something like this:
```bash filename="Terminal"
Debugger listening on ws://127.0.0.1:9229/0cf90313-350d-4466-a748-cd60f4e47c95
For help, see: https://nodejs.org/en/docs/inspector
ready - started server on 0.0.0.0:3000, url: http://localhost:3000
```
For Chrome:
1. Open a new tab and visit `chrome://inspect`
1. Look for your Next.js application in the **Remote Target** section
1. Click **inspect** to open a separate DevTools window
1. Go to the **Sources** tab
For Firefox:
1. Open a new tab and visit `about:debugging`
1. Click **This Firefox** in the left sidebar
1. Under **Remote Targets**, find your Next.js application
1. Click **Inspect** to open the debugger
1. Go to the **Debugger** tab
Debugging server-side code works similarly to client-side debugging. When searching for files (`Ctrl+P`/`⌘+P`), your source files will have paths starting with `webpack://{application-name}/./` (where `{application-name}` will be replaced with the name of your application according to your `package.json` file).
To use `--inspect-brk` or `--inspect-wait`, you have to specify `NODE_OPTIONS` instead. e.g. `NODE_OPTIONS=--inspect-brk next dev`.
### Inspect Server Errors with Browser DevTools
When you encounter an error, inspecting the source code can help trace the root cause of errors.
Next.js will display a Node.js icon underneath the Next.js version indicator on the error overlay. By clicking that icon, the DevTools URL is copied to your clipboard. You can open a new browser tab with that URL to inspect the Next.js server process.
### Debugging on Windows
Ensure Windows Defender is disabled on your machine. This external service will check _every file read_, which has been reported to greatly increase Fast Refresh time with `next dev`. This is a known issue, not related to Next.js, but it does affect Next.js development.
## More information
To learn more about how to use a JavaScript debugger, take a look at the following documentation:
- [Node.js debugging in VS Code: Breakpoints](https://code.visualstudio.com/docs/nodejs/nodejs-debugging#_breakpoints)
- [Chrome DevTools: Debug JavaScript](https://developers.google.com/web/tools/chrome-devtools/javascript)
- [Firefox DevTools: Debugger](https://firefox-source-docs.mozilla.org/devtools-user/debugger/)
+215
View File
@@ -0,0 +1,215 @@
---
title: How to preview content with Draft Mode in Next.js
nav_title: Draft Mode
description: Next.js has draft mode to toggle between static and dynamic pages. You can learn how it works with App Router here.
related:
title: Next Steps
description: See the API reference for more information on how to use Draft Mode.
links:
- app/api-reference/functions/draft-mode
---
**Draft Mode** allows you to preview draft content from your headless CMS in your Next.js application. This is useful for static pages that are generated at build time as it allows you to switch to [dynamic rendering](/docs/app/glossary#dynamic-rendering) and see the draft changes without having to rebuild your entire site.
This page walks through how to enable and use Draft Mode.
## Step 1: Create a Route Handler
Create a [Route Handler](/docs/app/api-reference/file-conventions/route). It can have any name, for example, `app/api/draft/route.ts`.
```ts filename="app/api/draft/route.ts" switcher
export async function GET(request: Request) {
return new Response('')
}
```
```js filename="app/api/draft/route.js" switcher
export async function GET() {
return new Response('')
}
```
Then, import the [`draftMode`](/docs/app/api-reference/functions/draft-mode) function and call the `enable()` method.
```ts filename="app/api/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/api/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')
}
```
This will set a **cookie** to enable draft mode. Subsequent requests containing this cookie will trigger draft mode and change the behavior of statically generated pages.
You can test this manually by visiting `/api/draft` and looking at your browsers developer tools. Notice the `Set-Cookie` response header with a cookie named `__prerender_bypass`.
## Step 2: Access the Route Handler from your Headless CMS
> These steps assume that the headless CMS youre using supports setting **custom draft URLs**. If it doesnt, you can still use this method to secure your draft URLs, but youll need to construct and access the draft URL manually. The specific steps will vary depending on which headless CMS youre using.
To securely access the Route Handler from your headless CMS:
1. Create a **secret token string** using a token generator of your choice. This secret will only be known by your Next.js app and your headless CMS.
2. If your headless CMS supports setting custom draft URLs, specify a draft URL (this assumes that your Route Handler is located at `app/api/draft/route.ts`). For example:
```bash filename="Terminal"
https://<your-site>/api/draft?secret=<token>&slug=<path>
```
> - `<your-site>` should be your deployment domain.
> - `<token>` should be replaced with the secret token you generated.
> - `<path>` should be the path for the page that you want to view. If you want to view `/posts/one`, then you should use `&slug=/posts/one`.
>
> Your headless CMS might allow you to include a variable in the draft URL so that `<path>` can be set dynamically based on the CMSs data like so: `&slug=/posts/{entry.fields.slug}`
3. In your Route Handler, check that the secret matches and that the `slug` parameter exists (if not, the request should fail), call `draftMode.enable()` to set the cookie. Then, redirect the browser to the path specified by `slug`:
```ts filename="app/api/draft/route.ts" switcher
import { draftMode } from 'next/headers'
import { redirect } from 'next/navigation'
export async function GET(request: Request) {
// Parse query string parameters
const { searchParams } = new URL(request.url)
const secret = searchParams.get('secret')
const slug = searchParams.get('slug')
// Check the secret and next parameters
// This secret should only be known to this Route Handler and the CMS
if (secret !== 'MY_SECRET_TOKEN' || !slug) {
return new Response('Invalid token', { status: 401 })
}
// Fetch the headless CMS to check if the provided `slug` exists
// getPostBySlug would implement the required fetching logic to the headless CMS
const post = await getPostBySlug(slug)
// If the slug doesn't exist prevent draft mode from being enabled
if (!post) {
return new Response('Invalid slug', { status: 401 })
}
// Enable Draft Mode by setting the cookie
const draft = await draftMode()
draft.enable()
// Redirect to the path from the fetched post
// We don't redirect to searchParams.slug as that might lead to open redirect vulnerabilities
redirect(post.slug)
}
```
```js filename="app/api/draft/route.js" switcher
import { draftMode } from 'next/headers'
import { redirect } from 'next/navigation'
export async function GET(request) {
// Parse query string parameters
const { searchParams } = new URL(request.url)
const secret = searchParams.get('secret')
const slug = searchParams.get('slug')
// Check the secret and next parameters
// This secret should only be known to this Route Handler and the CMS
if (secret !== 'MY_SECRET_TOKEN' || !slug) {
return new Response('Invalid token', { status: 401 })
}
// Fetch the headless CMS to check if the provided `slug` exists
// getPostBySlug would implement the required fetching logic to the headless CMS
const post = await getPostBySlug(slug)
// If the slug doesn't exist prevent draft mode from being enabled
if (!post) {
return new Response('Invalid slug', { status: 401 })
}
// Enable Draft Mode by setting the cookie
const draft = await draftMode()
draft.enable()
// Redirect to the path from the fetched post
// We don't redirect to searchParams.slug as that might lead to open redirect vulnerabilities
redirect(post.slug)
}
```
If it succeeds, then the browser will be redirected to the path you want to view with the draft mode cookie.
## Step 3: Preview the Draft Content
The next step is to update your page to check the value of `draftMode().isEnabled`.
If you request a page which has the cookie set, then data will be fetched at **request time** (instead of at build time).
Furthermore, the value of `isEnabled` will be `true`.
```tsx filename="app/page.tsx" switcher
// page that fetches data
import { draftMode } from 'next/headers'
async function getData() {
const { isEnabled } = await draftMode()
const url = isEnabled
? 'https://draft.example.com'
: 'https://production.example.com'
const res = await fetch(url)
return res.json()
}
export default async function Page() {
const { title, desc } = await getData()
return (
<main>
<h1>{title}</h1>
<p>{desc}</p>
</main>
)
}
```
```jsx filename="app/page.js" switcher
// page that fetches data
import { draftMode } from 'next/headers'
async function getData() {
const { isEnabled } = await draftMode()
const url = isEnabled
? 'https://draft.example.com'
: 'https://production.example.com'
const res = await fetch(url)
return res.json()
}
export default async function Page() {
const { title, desc } = await getData()
return (
<main>
<h1>{title}</h1>
<p>{desc}</p>
</main>
)
}
```
If you access the draft Route Handler (with `secret` and `slug`) from your headless CMS or manually using the URL, you should now be able to see the draft content. And, if you update your draft without publishing, you should be able to view the draft.
+289
View File
@@ -0,0 +1,289 @@
---
title: How to use environment variables in Next.js
nav_title: Environment Variables
description: Learn to add and access environment variables in your Next.js application.
---
{/* 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 comes with built-in support for environment variables, which allows you to do the following:
- [Use `.env` to load environment variables](#loading-environment-variables)
- [Bundle environment variables for the browser by prefixing with `NEXT_PUBLIC_`](#bundling-environment-variables-for-the-browser)
> **Warning:** The default `create-next-app` template ensures all `.env` files are added to your `.gitignore`. You almost never want to commit these files to your repository.
## Loading Environment Variables
Next.js has built-in support for loading environment variables from `.env*` files into `process.env`.
```txt filename=".env"
DB_HOST=localhost
DB_USER=myuser
DB_PASS=mypassword
```
<PagesOnly>
This loads `process.env.DB_HOST`, `process.env.DB_USER`, and `process.env.DB_PASS` into the Node.js environment automatically allowing you to use them in [Next.js data fetching methods](/docs/pages/building-your-application/data-fetching) and [API routes](/docs/pages/building-your-application/routing/api-routes).
For example, using [`getStaticProps`](/docs/pages/building-your-application/data-fetching/get-static-props):
```js filename="pages/index.js"
export async function getStaticProps() {
const db = await myDB.connect({
host: process.env.DB_HOST,
username: process.env.DB_USER,
password: process.env.DB_PASS,
})
// ...
}
```
</PagesOnly>
<AppOnly>
> **Note**: Next.js also supports multiline variables inside of your `.env*` files:
>
> ```bash
> # .env
>
> # you can write with line breaks
> PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----
> ...
> Kh9NV...
> ...
> -----END DSA PRIVATE KEY-----"
>
> # or with `\n` inside double quotes
> PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\nKh9NV...\n-----END DSA PRIVATE KEY-----\n"
> ```
> **Note**: If you are using a `/src` folder, please note that Next.js will load the .env files **only** from the parent folder and **not** from the `/src` folder.
> This loads `process.env.DB_HOST`, `process.env.DB_USER`, and `process.env.DB_PASS` into the Node.js environment automatically allowing you to use them in [Route Handlers](/docs/app/api-reference/file-conventions/route).
For example:
```js filename="app/api/route.js"
export async function GET() {
const db = await myDB.connect({
host: process.env.DB_HOST,
username: process.env.DB_USER,
password: process.env.DB_PASS,
})
// ...
}
```
</AppOnly>
### Loading Environment Variables with `@next/env`
If you need to load environment variables outside of the Next.js runtime, such as in a root config file for an ORM or test runner, you can use the `@next/env` package.
This package is used internally by Next.js to load environment variables from `.env*` files.
To use it, install the package and use the `loadEnvConfig` function to load the environment variables:
```bash package="pnpm"
pnpm add @next/env
```
```bash package="npm"
npm install @next/env
```
```bash package="yarn"
yarn add @next/env
```
```bash package="bun"
bun add @next/env
```
```tsx filename="envConfig.ts" switcher
import { loadEnvConfig } from '@next/env'
const projectDir = process.cwd()
loadEnvConfig(projectDir)
```
```jsx filename="envConfig.js" switcher
import { loadEnvConfig } from '@next/env'
const projectDir = process.cwd()
loadEnvConfig(projectDir)
```
Then, you can import the configuration where needed. For example:
```tsx filename="orm.config.ts" switcher
import './envConfig.ts'
export default defineConfig({
dbCredentials: {
connectionString: process.env.DATABASE_URL!,
},
})
```
```jsx filename="orm.config.js" switcher
import './envConfig.js'
export default defineConfig({
dbCredentials: {
connectionString: process.env.DATABASE_URL,
},
})
```
### Referencing Other Variables
Next.js will automatically expand variables that use `$` to reference other variables e.g. `$VARIABLE` inside of your `.env*` files. This allows you to reference other secrets. For example:
```txt filename=".env"
TWITTER_USER=nextjs
TWITTER_URL=https://x.com/$TWITTER_USER
```
In the above example, `process.env.TWITTER_URL` would be set to `https://x.com/nextjs`.
> **Good to know**: If you need to use variable with a `$` in the actual value, it needs to be escaped e.g. `\$`.
## Bundling Environment Variables for the Browser
Non-`NEXT_PUBLIC_` environment variables are only available in the Node.js environment, meaning they aren't accessible to the browser (the client runs in a different _environment_).
In order to make the value of an environment variable accessible in the browser, Next.js can "inline" a value, at build time, into the js bundle that is delivered to the client, replacing all references to `process.env.[variable]` with a hard-coded value. To tell it to do this, you just have to prefix the variable with `NEXT_PUBLIC_`. For example:
```txt filename=".env"
NEXT_PUBLIC_ANALYTICS_ID=abcdefghijk
```
This will tell Next.js to replace all references to `process.env.NEXT_PUBLIC_ANALYTICS_ID` in the Node.js environment with the value from the environment in which you run `next build`, allowing you to use it anywhere in your code. It will be inlined into any JavaScript sent to the browser.
> **Note**: After being built, your app will no longer respond to changes to these environment variables. For instance, if you use a Heroku pipeline to promote slugs built in one environment to another environment, or if you build and deploy a single Docker image to multiple environments, all `NEXT_PUBLIC_` variables will be frozen with the value evaluated at build time, so these values need to be set appropriately when the project is built. If you need access to runtime environment values, you'll have to setup your own API to provide them to the client (either on demand or during initialization).
```js filename="pages/index.js"
import setupAnalyticsService from '../lib/my-analytics-service'
// 'NEXT_PUBLIC_ANALYTICS_ID' can be used here as it's prefixed by 'NEXT_PUBLIC_'.
// It will be transformed at build time to `setupAnalyticsService('abcdefghijk')`.
setupAnalyticsService(process.env.NEXT_PUBLIC_ANALYTICS_ID)
function HomePage() {
return <h1>Hello World</h1>
}
export default HomePage
```
Note that dynamic lookups will _not_ be inlined, such as:
```js
// This will NOT be inlined, because it uses a variable
const varName = 'NEXT_PUBLIC_ANALYTICS_ID'
setupAnalyticsService(process.env[varName])
// This will NOT be inlined, because it uses a variable
const env = process.env
setupAnalyticsService(env.NEXT_PUBLIC_ANALYTICS_ID)
```
### Runtime Environment Variables
Next.js can support both build time and runtime environment variables.
**By default, environment variables are only available on the server**. To expose an environment variable to the browser, it must be prefixed with `NEXT_PUBLIC_`. However, these public environment variables will be inlined into the JavaScript bundle during `next build`.
<PagesOnly>
To read runtime environment variables, we recommend using `getServerSideProps` or [incrementally adopting the App Router](/docs/app/guides/migrating/app-router-migration).
</PagesOnly>
<AppOnly>
You can safely read environment variables on the server during dynamic rendering:
```tsx filename="app/page.ts" switcher
import { connection } from 'next/server'
export default async function Component() {
await connection()
// cookies, headers, and other Request-time APIs
// will also opt into dynamic rendering, meaning
// this env variable is evaluated at runtime
const value = process.env.MY_VALUE
// ...
}
```
```jsx filename="app/page.js" switcher
import { connection } from 'next/server'
export default async function Component() {
await connection()
// cookies, headers, and other Request-time APIs
// will also opt into dynamic rendering, meaning
// this env variable is evaluated at runtime
const value = process.env.MY_VALUE
// ...
}
```
</AppOnly>
This allows you to use a singular Docker image that can be promoted through multiple environments with different values.
**Good to know:**
- You can run code on server startup using the [`register` function](/docs/app/guides/instrumentation).
## Test Environment Variables
Apart from `development` and `production` environments, there is a 3rd option available: `test`. In the same way you can set defaults for development or production environments, you can do the same with a `.env.test` file for the `testing` environment (though this one is not as common as the previous two). Next.js will not load environment variables from `.env.development` or `.env.production` in the `testing` environment.
This one is useful when running tests with tools like `jest` or `cypress` where you need to set specific environment vars only for testing purposes. Test default values will be loaded if `NODE_ENV` is set to `test`, though you usually don't need to do this manually as testing tools will address it for you.
There is a small difference between `test` environment, and both `development` and `production` that you need to bear in mind: `.env.local` won't be loaded, as you expect tests to produce the same results for everyone. This way every test execution will use the same env defaults across different executions by ignoring your `.env.local` (which is intended to override the default set).
> **Good to know**: similar to Default Environment Variables, `.env.test` file should be included in your repository, but `.env.test.local` shouldn't, as `.env*.local` are intended to be ignored through `.gitignore`.
While running unit tests you can make sure to load your environment variables the same way Next.js does by leveraging the `loadEnvConfig` function from the `@next/env` package.
```js
// The below can be used in a Jest global setup file or similar for your testing set-up
import { loadEnvConfig } from '@next/env'
export default async () => {
const projectDir = process.cwd()
loadEnvConfig(projectDir)
}
```
## Environment Variable Load Order
Environment variables are looked up in the following places, in order, stopping once the variable is found.
1. `process.env`
1. `.env.$(NODE_ENV).local`
1. `.env.local` (Not checked when `NODE_ENV` is `test`.)
1. `.env.$(NODE_ENV)`
1. `.env`
For example, if `NODE_ENV` is `development` and you define a variable in both `.env.development.local` and `.env`, the value in `.env.development.local` will be used.
> **Good to know**: The allowed values for `NODE_ENV` are `production`, `development` and `test`.
## Good to know
- If you are using a [`/src` directory](/docs/app/api-reference/file-conventions/src-folder), `.env.*` files should remain in the root of your project.
- If the environment variable `NODE_ENV` is unassigned, Next.js automatically assigns `development` when running the `next dev` command, or `production` for all other commands.
## Version History
| Version | Changes |
| -------- | --------------------------------------------- |
| `v9.4.0` | Support `.env` and `NEXT_PUBLIC_` introduced. |
+508
View File
@@ -0,0 +1,508 @@
---
title: How to create forms with Server Actions
nav_title: Forms
description: Learn how to create forms in Next.js with React Server Actions.
---
React Server Actions are [Server Functions](https://react.dev/reference/rsc/server-functions) that execute on the server. They can be called in Server and Client Components to handle form submissions. This guide will walk you through how to create forms in Next.js with Server Actions.
> [!WARNING]
> Always verify [authentication and authorization](/docs/app/guides/authentication) inside each Server Action, even if the form is only rendered on an authenticated page. See the [Data Security guide](/docs/app/guides/data-security) for more details.
## How it works
React extends the HTML [`<form>`](https://developer.mozilla.org/docs/Web/HTML/Element/form) element to allow Server Actions to be invoked with the [`action`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/form#action) attribute.
When used in a form, the function automatically receives the [`FormData`](https://developer.mozilla.org/docs/Web/API/FormData/FormData) object. You can then extract the data using the native [`FormData` methods](https://developer.mozilla.org/en-US/docs/Web/API/FormData#instance_methods):
```tsx filename="app/invoices/page.tsx" switcher
import { auth } from '@/lib/auth'
export default function Page() {
async function createInvoice(formData: FormData) {
'use server'
const session = await auth()
if (!session?.user) {
throw new Error('Unauthorized')
}
const rawFormData = {
customerId: formData.get('customerId'),
amount: formData.get('amount'),
status: formData.get('status'),
}
// mutate data
// revalidate the cache
}
return <form action={createInvoice}>...</form>
}
```
```jsx filename="app/invoices/page.js" switcher
import { auth } from '@/lib/auth'
export default function Page() {
async function createInvoice(formData) {
'use server'
const session = await auth()
if (!session?.user) {
throw new Error('Unauthorized')
}
const rawFormData = {
customerId: formData.get('customerId'),
amount: formData.get('amount'),
status: formData.get('status'),
}
// mutate data
// revalidate the cache
}
return <form action={createInvoice}>...</form>
}
```
> **Good to know:** When working with forms that have multiple fields, use JavaScript's [`Object.fromEntries()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/fromEntries). For example: `const rawFormData = Object.fromEntries(formData)`. Note that this object will contain extra properties prefixed with `$ACTION_`.
## Passing additional arguments
Outside of form fields, you can pass additional arguments to a Server Function using the JavaScript [`bind`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind) method. For example, to pass the `userId` argument to the `updateUser` Server Function:
```tsx filename="app/client-component.tsx" highlight={6} switcher
'use client'
import { updateUser } from './actions'
export function UserProfile({ userId }: { userId: string }) {
const updateUserWithId = updateUser.bind(null, userId)
return (
<form action={updateUserWithId}>
<input type="text" name="name" />
<button type="submit">Update User Name</button>
</form>
)
}
```
```jsx filename="app/client-component.js" highlight={6} switcher
'use client'
import { updateUser } from './actions'
export function UserProfile({ userId }) {
const updateUserWithId = updateUser.bind(null, userId)
return (
<form action={updateUserWithId}>
<input type="text" name="name" />
<button type="submit">Update User Name</button>
</form>
)
}
```
The Server Function will receive the `userId` as an additional argument:
```ts filename="app/actions.ts" switcher
'use server'
export async function updateUser(userId: string, formData: FormData) {}
```
```js filename="app/actions.js" switcher
'use server'
export async function updateUser(userId, formData) {}
```
> **Good to know**:
>
> - An alternative is to pass arguments as hidden input fields in the form (e.g. `<input type="hidden" name="userId" value={userId} />`). However, the value will be part of the rendered HTML and will not be encoded.
> - `bind` works in both Server and Client Components and supports progressive enhancement.
## Form validation
Forms can be validated on the client or server.
- For **client-side validation**, you can use the HTML attributes like `required` and `type="email"` for basic validation.
- For **server-side validation**, you can use a library like [zod](https://zod.dev/) to validate the form fields. For example:
```tsx filename="app/actions.ts" switcher
'use server'
import { z } from 'zod'
const schema = z.object({
email: z.string({
invalid_type_error: 'Invalid Email',
}),
})
export default async function createUser(formData: FormData) {
const validatedFields = schema.safeParse({
email: formData.get('email'),
})
// Return early if the form data is invalid
if (!validatedFields.success) {
return {
errors: validatedFields.error.flatten().fieldErrors,
}
}
// Mutate data
}
```
```jsx filename="app/actions.js" switcher
'use server'
import { z } from 'zod'
const schema = z.object({
email: z.string({
invalid_type_error: 'Invalid Email',
}),
})
export default async function createUser(formData) {
const validatedFields = schema.safeParse({
email: formData.get('email'),
})
// Return early if the form data is invalid
if (!validatedFields.success) {
return {
errors: validatedFields.error.flatten().fieldErrors,
}
}
// Mutate data
}
```
## Validation errors
To display validation errors or messages, turn the component that defines the `<form>` into a Client Component and use React [`useActionState`](https://react.dev/reference/react/useActionState).
When using `useActionState`, the Server function signature will change to receive a new `prevState` or `initialState` parameter as its first argument.
```tsx filename="app/actions.ts" highlight={5} switcher
'use server'
import { z } from 'zod'
export async function createUser(initialState: any, formData: FormData) {
const validatedFields = schema.safeParse({
email: formData.get('email'),
})
// ...
}
```
```jsx filename="app/actions.ts" highlight={7} switcher
'use server'
import { z } from 'zod'
// ...
export async function createUser(initialState, formData) {
const validatedFields = schema.safeParse({
email: formData.get('email'),
})
// ...
}
```
You can then conditionally render the error message based on the `state` object.
```tsx filename="app/ui/signup.tsx" highlight={11,18-20} switcher
'use client'
import { useActionState } from 'react'
import { createUser } from '@/app/actions'
const initialState = {
message: '',
}
export function Signup() {
const [state, formAction, pending] = useActionState(createUser, initialState)
return (
<form action={formAction}>
<label htmlFor="email">Email</label>
<input type="text" id="email" name="email" required />
{/* ... */}
<p aria-live="polite">{state?.message}</p>
<button disabled={pending}>Sign up</button>
</form>
)
}
```
```jsx filename="app/ui/signup.js" highlight={11,18-20} switcher
'use client'
import { useActionState } from 'react'
import { createUser } from '@/app/actions'
const initialState = {
message: '',
}
export function Signup() {
const [state, formAction, pending] = useActionState(createUser, initialState)
return (
<form action={formAction}>
<label htmlFor="email">Email</label>
<input type="text" id="email" name="email" required />
{/* ... */}
<p aria-live="polite">{state?.message}</p>
<button disabled={pending}>Sign up</button>
</form>
)
}
```
## Pending states
The [`useActionState`](https://react.dev/reference/react/useActionState) hook exposes a `pending` boolean that can be used to show a loading indicator or disable the submit button while the action is being executed.
```tsx filename="app/ui/signup.tsx" highlight={7,12} switcher
'use client'
import { useActionState } from 'react'
import { createUser } from '@/app/actions'
export function Signup() {
const [state, formAction, pending] = useActionState(createUser, initialState)
return (
<form action={formAction}>
{/* Other form elements */}
<button disabled={pending}>Sign up</button>
</form>
)
}
```
```jsx filename="app/ui/signup.js" highlight={7,12} switcher
'use client'
import { useActionState } from 'react'
import { createUser } from '@/app/actions'
export function Signup() {
const [state, formAction, pending] = useActionState(createUser, initialState)
return (
<form action={formAction}>
{/* Other form elements */}
<button disabled={pending}>Sign up</button>
</form>
)
}
```
Alternatively, you can use the [`useFormStatus`](https://react.dev/reference/react-dom/hooks/useFormStatus) hook to show a loading indicator while the action is being executed. When using this hook, you'll need to create a separate component to render the loading indicator. For example, to disable the button when the action is pending:
```tsx filename="app/ui/button.tsx" highlight={6} switcher
'use client'
import { useFormStatus } from 'react-dom'
export function SubmitButton() {
const { pending } = useFormStatus()
return (
<button disabled={pending} type="submit">
Sign Up
</button>
)
}
```
```jsx filename="app/ui/button.js" highlight={6} switcher
'use client'
import { useFormStatus } from 'react-dom'
export function SubmitButton() {
const { pending } = useFormStatus()
return (
<button disabled={pending} type="submit">
Sign Up
</button>
)
}
```
You can then nest the `SubmitButton` component inside the form:
```tsx filename="app/ui/signup.tsx" switcher
import { SubmitButton } from './button'
import { createUser } from '@/app/actions'
export function Signup() {
return (
<form action={createUser}>
{/* Other form elements */}
<SubmitButton />
</form>
)
}
```
```jsx filename="app/ui/signup.js" switcher
import { SubmitButton } from './button'
import { createUser } from '@/app/actions'
export function Signup() {
return (
<form action={createUser}>
{/* Other form elements */}
<SubmitButton />
</form>
)
}
```
> **Good to know:** In React 19, `useFormStatus` includes additional keys on the returned object, like data, method, and action. If you are not using React 19, only the `pending` key is available.
## Optimistic updates
You can use the React [`useOptimistic`](https://react.dev/reference/react/useOptimistic) hook to optimistically update the UI before the Server Function finishes executing, rather than waiting for the response:
```tsx filename="app/page.tsx" switcher
'use client'
import { useOptimistic } from 'react'
import { send } from './actions'
type Message = {
message: string
}
export function Thread({ messages }: { messages: Message[] }) {
const [optimisticMessages, addOptimisticMessage] = useOptimistic<
Message[],
string
>(messages, (state, newMessage) => [...state, { message: newMessage }])
const formAction = async (formData: FormData) => {
const message = formData.get('message') as string
addOptimisticMessage(message)
await send(message)
}
return (
<div>
{optimisticMessages.map((m, i) => (
<div key={i}>{m.message}</div>
))}
<form action={formAction}>
<input type="text" name="message" />
<button type="submit">Send</button>
</form>
</div>
)
}
```
```jsx filename="app/page.js" switcher
'use client'
import { useOptimistic } from 'react'
import { send } from './actions'
export function Thread({ messages }) {
const [optimisticMessages, addOptimisticMessage] = useOptimistic(
messages,
(state, newMessage) => [...state, { message: newMessage }]
)
const formAction = async (formData) => {
const message = formData.get('message')
addOptimisticMessage(message)
await send(message)
}
return (
<div>
{optimisticMessages.map((m) => (
<div>{m.message}</div>
))}
<form action={formAction}>
<input type="text" name="message" />
<button type="submit">Send</button>
</form>
</div>
)
}
```
## Nested form elements
You can call Server Actions in elements nested inside `<form>` such as `<button>`, `<input type="submit">`, and `<input type="image">`. These elements accept the `formAction` prop or event handlers.
This is useful in cases where you want to call multiple Server Actions within a form. For example, you can create a specific `<button>` element for saving a post draft in addition to publishing it. See the [React `<form>` docs](https://react.dev/reference/react-dom/components/form#handling-multiple-submission-types) for more information.
## Programmatic form submission
You can trigger a form submission programmatically using the [`requestSubmit()`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/requestSubmit) method. For example, when the user submits a form using the `` + `Enter` keyboard shortcut, you can listen for the `onKeyDown` event:
```tsx filename="app/entry.tsx" switcher
'use client'
export function Entry() {
const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
if (
(e.ctrlKey || e.metaKey) &&
(e.key === 'Enter' || e.key === 'NumpadEnter')
) {
e.preventDefault()
e.currentTarget.form?.requestSubmit()
}
}
return (
<div>
<textarea name="entry" rows={20} required onKeyDown={handleKeyDown} />
</div>
)
}
```
```jsx filename="app/entry.js" switcher
'use client'
export function Entry() {
const handleKeyDown = (e) => {
if (
(e.ctrlKey || e.metaKey) &&
(e.key === 'Enter' || e.key === 'NumpadEnter')
) {
e.preventDefault()
e.currentTarget.form?.requestSubmit()
}
}
return (
<div>
<textarea name="entry" rows={20} required onKeyDown={handleKeyDown} />
</div>
)
}
```
This will trigger the submission of the nearest `<form>` ancestor, which will invoke the Server Function.
@@ -0,0 +1,608 @@
---
title: How to implement Incremental Static Regeneration (ISR)
nav_title: ISR
description: Learn how to create or update static pages at runtime with Incremental Static Regeneration.
---
<details>
<summary>Examples</summary>
- [Next.js Commerce](https://vercel.com/templates/next.js/nextjs-commerce)
- [On-Demand ISR](https://on-demand-isr.vercel.app)
- [Next.js Forms](https://github.com/vercel/next.js/tree/canary/examples/next-forms)
</details>
Incremental Static Regeneration (ISR) enables you to:
- Update static content without rebuilding the entire site
- Reduce server load by serving prerendered, static pages for most requests
- Ensure proper `cache-control` headers are automatically added to pages
- Handle large amounts of content pages without long `next build` times
Here's a minimal example:
<AppOnly>
```tsx filename="app/blog/[id]/page.tsx" switcher
interface Post {
id: string
title: string
content: string
}
// Next.js will invalidate the cache when a
// request comes in, at most once every 60 seconds.
export const revalidate = 60
export async function generateStaticParams() {
const posts: Post[] = await fetch('https://api.vercel.app/blog').then((res) =>
res.json()
)
return posts.map((post) => ({
id: String(post.id),
}))
}
export default async function Page({
params,
}: {
params: Promise<{ id: string }>
}) {
const { id } = await params
const post: Post = await fetch(`https://api.vercel.app/blog/${id}`).then(
(res) => res.json()
)
return (
<main>
<h1>{post.title}</h1>
<p>{post.content}</p>
</main>
)
}
```
```jsx filename="app/blog/[id]/page.jsx" switcher
// Next.js will invalidate the cache when a
// request comes in, at most once every 60 seconds.
export const revalidate = 60
export async function generateStaticParams() {
const posts = await fetch('https://api.vercel.app/blog').then((res) =>
res.json()
)
return posts.map((post) => ({
id: String(post.id),
}))
}
export default async function Page({ params }) {
const { id } = await params
const post = await fetch(`https://api.vercel.app/blog/${id}`).then((res) =>
res.json()
)
return (
<main>
<h1>{post.title}</h1>
<p>{post.content}</p>
</main>
)
}
```
Here's how this example works:
1. During `next build`, all known blog posts are generated
2. All requests made to these pages (e.g. `/blog/1`) are cached and instantaneous
3. After 60 seconds has passed, the next request will still return the cached (now stale) page
4. The cache is invalidated and a new version of the page begins generating in the background
5. Once generated successfully, the next request will return the updated page and cache it for subsequent requests
6. If `/blog/26` is requested, and it exists, the page will be generated on-demand. This behavior can be changed by using a different [dynamicParams](/docs/app/api-reference/file-conventions/route-segment-config/dynamicParams) value. However, if the post does not exist, then 404 is returned.
</AppOnly>
<PagesOnly>
```tsx filename="pages/blog/[id].tsx" switcher
import type { GetStaticPaths, GetStaticProps } from 'next'
interface Post {
id: string
title: string
content: string
}
interface Props {
post: Post
}
export const getStaticPaths: GetStaticPaths = async () => {
const posts = await fetch('https://api.vercel.app/blog').then((res) =>
res.json()
)
const paths = posts.map((post: Post) => ({
params: { id: String(post.id) },
}))
return { paths, fallback: 'blocking' }
}
export const getStaticProps: GetStaticProps<Props> = async ({
params,
}: {
params: { id: string }
}) => {
const post = await fetch(`https://api.vercel.app/blog/${params.id}`).then(
(res) => res.json()
)
return {
props: { post },
// Next.js will invalidate the cache when a
// request comes in, at most once every 60 seconds.
revalidate: 60,
}
}
export default function Page({ post }: Props) {
return (
<main>
<h1>{post.title}</h1>
<p>{post.content}</p>
</main>
)
}
```
```jsx filename="pages/blog/[id].jsx" switcher
export async function getStaticPaths() {
const posts = await fetch('https://api.vercel.app/blog').then((res) =>
res.json()
)
const paths = posts.map((post) => ({
params: { id: post.id },
}))
return { paths, fallback: 'blocking' }
}
export async function getStaticProps({ params }) {
const post = await fetch(`https://api.vercel.app/blog/${params.id}`).then(
(res) => res.json()
)
return {
props: { post },
// Next.js will invalidate the cache when a
// request comes in, at most once every 60 seconds.
revalidate: 60,
}
}
export default function Page({ post }) {
return (
<main>
<h1>{post.title}</h1>
<p>{post.content}</p>
</main>
)
}
```
Here's how this example works:
1. During `next build`, all known blog posts are generated
2. All requests made to these pages (e.g. `/blog/1`) are cached and instantaneous
3. After 60 seconds has passed, the next request will still return the cached (now stale) page
4. The cache is invalidated and a new version of the page begins generating in the background
5. Once generated successfully, the next request will return the updated page and cache it for subsequent requests
6. If `/blog/26` is requested, and it exists, the page will be generated on-demand. This behavior can be changed by using a different [fallback](/docs/pages/api-reference/functions/get-static-paths#fallback-false) value. However, if the post does not exist, then 404 is returned.
</PagesOnly>
## Reference
<AppOnly>
### Route segment config
- [`revalidate`](/docs/app/guides/caching-without-cache-components#route-segment-config-revalidate)
- [`dynamicParams`](/docs/app/api-reference/file-conventions/route-segment-config/dynamicParams)
### Functions
- [`revalidatePath`](/docs/app/api-reference/functions/revalidatePath)
- [`revalidateTag`](/docs/app/api-reference/functions/revalidateTag)
</AppOnly>
<PagesOnly>
### Functions
- [`getStaticProps`](/docs/pages/building-your-application/data-fetching/get-static-props)
- [`res.revalidate`](/docs/pages/building-your-application/routing/api-routes#response-helpers)
</PagesOnly>
## Examples
<AppOnly>
### Time-based revalidation
This fetches and displays a list of blog posts on /blog. After an hour has passed, the next visitor will still receive the cached (stale) version of the page immediately for a fast response. Simultaneously, Next.js triggers regeneration of a fresh version in the background. Once the new version is successfully generated, it replaces the cached version, and subsequent visitors will receive the updated content.
```tsx filename="app/blog/page.tsx" switcher
interface Post {
id: string
title: string
content: string
}
export const revalidate = 3600 // invalidate every hour
export default async function Page() {
const data = await fetch('https://api.vercel.app/blog')
const posts: Post[] = await data.json()
return (
<main>
<h1>Blog Posts</h1>
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</main>
)
}
```
```jsx filename="app/blog/page.js" switcher
export const revalidate = 3600 // invalidate every hour
export default async function Page() {
const data = await fetch('https://api.vercel.app/blog')
const posts = await data.json()
return (
<main>
<h1>Blog Posts</h1>
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</main>
)
}
```
We recommend setting a high revalidation time. For instance, 1 hour instead of 1 second. If you need more precision, consider using on-demand revalidation. If you need real-time data, consider switching to [dynamic rendering](/docs/app/glossary#dynamic-rendering).
### On-demand revalidation with `revalidatePath`
For a more precise method of revalidation, invalidate cached pages on-demand with the `revalidatePath` function.
For example, this Server Action would get called after adding a new post. Regardless of how you retrieve your data in your Server Component, either using `fetch` or connecting to a database, this will invalidate the cache for the entire route. The next request to that route will trigger regeneration and serve fresh data, which will then be cached for subsequent requests.
> **Note:** `revalidatePath` invalidates the cache entries but regeneration happens on the next request. If you want to eagerly regenerate the cache entry immediately instead of waiting for the next request, you can use the Pages router [`res.revalidate`](/docs/pages/guides/incremental-static-regeneration#on-demand-validation-with-resrevalidate) method. We're working on adding new methods to provide eager regeneration capabilities for the App Router.
```ts filename="app/actions.ts" switcher
'use server'
import { revalidatePath } from 'next/cache'
export async function createPost() {
// Invalidate the cache for the /posts route
revalidatePath('/posts')
}
```
```js filename="app/actions.js" switcher
'use server'
import { revalidatePath } from 'next/cache'
export async function createPost() {
// Invalidate the cache for the /posts route
revalidatePath('/posts')
}
```
[View a demo](https://on-demand-isr.vercel.app) and [explore the source code](https://github.com/vercel/on-demand-isr).
### On-demand revalidation with `revalidateTag`
For most use cases, prefer revalidating entire paths. If you need more granular control, you can use the `revalidateTag` function. For example, you can tag individual `fetch` calls:
```tsx filename="app/blog/page.tsx" switcher
export default async function Page() {
const data = await fetch('https://api.vercel.app/blog', {
next: { tags: ['posts'] },
})
const posts = await data.json()
// ...
}
```
```jsx filename="app/blog/page.js" switcher
export default async function Page() {
const data = await fetch('https://api.vercel.app/blog', {
next: { tags: ['posts'] },
})
const posts = await data.json()
// ...
}
```
If you are using an ORM or connecting to a database, you can use `unstable_cache`:
```tsx filename="app/blog/page.tsx" switcher
import { unstable_cache } from 'next/cache'
import { db, posts } from '@/lib/db'
const getCachedPosts = unstable_cache(
async () => {
return await db.select().from(posts)
},
['posts'],
{ revalidate: 3600, tags: ['posts'] }
)
export default async function Page() {
const posts = getCachedPosts()
// ...
}
```
```jsx filename="app/blog/page.js" switcher
import { unstable_cache } from 'next/cache'
import { db, posts } from '@/lib/db'
const getCachedPosts = unstable_cache(
async () => {
return await db.select().from(posts)
},
['posts'],
{ revalidate: 3600, tags: ['posts'] }
)
export default async function Page() {
const posts = getCachedPosts()
// ...
}
```
You can then use `revalidateTag` in a [Server Actions](/docs/app/getting-started/mutating-data) or [Route Handler](/docs/app/api-reference/file-conventions/route):
```ts filename="app/actions.ts" switcher
'use server'
import { revalidateTag } from 'next/cache'
export async function createPost() {
// Invalidate all data tagged with 'posts'
revalidateTag('posts')
}
```
```js filename="app/actions.js" switcher
'use server'
import { revalidateTag } from 'next/cache'
export async function createPost() {
// Invalidate all data tagged with 'posts'
revalidateTag('posts')
}
```
</AppOnly>
<PagesOnly>
### On-demand validation with `res.revalidate()`
For a more precise method of revalidation, use `res.revalidate` to generate a new page on-demand from an API Router.
For example, this API Route can be called at `/api/revalidate?secret=<token>` to revalidate a given blog post. Create a secret token only known by your Next.js app. This secret will be used to prevent unauthorized access to the revalidation API Route.
```ts filename="pages/api/revalidate.ts" switcher
import type { NextApiRequest, NextApiResponse } from 'next'
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
// Check for secret to confirm this is a valid request
if (req.query.secret !== process.env.MY_SECRET_TOKEN) {
return res.status(401).json({ message: 'Invalid token' })
}
try {
// This should be the actual path not a rewritten path
// e.g. for "/posts/[id]" this should be "/posts/1"
await res.revalidate('/posts/1')
return res.json({ revalidated: true })
} catch (err) {
// If there was an error, Next.js will continue
// to show the last successfully generated page
return res.status(500).send('Error revalidating')
}
}
```
```js filename="pages/api/revalidate.js" switcher
export default async function handler(req, res) {
// Check for secret to confirm this is a valid request
if (req.query.secret !== process.env.MY_SECRET_TOKEN) {
return res.status(401).json({ message: 'Invalid token' })
}
try {
// This should be the actual path not a rewritten path
// e.g. for "/posts/[id]" this should be "/posts/1"
await res.revalidate('/posts/1')
return res.json({ revalidated: true })
} catch (err) {
// If there was an error, Next.js will continue
// to show the last successfully generated page
return res.status(500).send('Error revalidating')
}
}
```
If you are using on-demand revalidation, you do not need to specify a `revalidate` time inside of `getStaticProps`. Next.js will use the default value of `false` (no revalidation) and only revalidate the page on-demand when `res.revalidate()` is called.
</PagesOnly>
### Handling uncaught exceptions
<AppOnly>
If an error is thrown while attempting to revalidate data, the last successfully generated data will continue to be served from the cache. On the next subsequent request, Next.js will retry revalidating the data. [Learn more about error handling](/docs/app/getting-started/error-handling).
</AppOnly>
<PagesOnly>
If there is an error inside `getStaticProps` when handling background regeneration, or you manually throw an error, the last successfully generated page will continue to show. On the next subsequent request, Next.js will retry calling `getStaticProps`.
```tsx filename="pages/blog/[id].tsx" switcher
import type { GetStaticProps } from 'next'
interface Post {
id: string
title: string
content: string
}
interface Props {
post: Post
}
export const getStaticProps: GetStaticProps<Props> = async ({
params,
}: {
params: { id: string }
}) => {
// If this request throws an uncaught error, Next.js will
// not invalidate the currently shown page and
// retry getStaticProps on the next request.
const res = await fetch(`https://api.vercel.app/blog/${params.id}`)
const post: Post = await res.json()
if (!res.ok) {
// If there is a server error, you might want to
// throw an error instead of returning so that the cache is not updated
// until the next successful request.
throw new Error(`Failed to fetch posts, received status ${res.status}`)
}
return {
props: { post },
// Next.js will invalidate the cache when a
// request comes in, at most once every 60 seconds.
revalidate: 60,
}
}
```
```jsx filename="pages/blog/[id].jsx" switcher
export async function getStaticProps({ params }) {
// If this request throws an uncaught error, Next.js will
// not invalidate the currently shown page and
// retry getStaticProps on the next request.
const res = await fetch(`https://api.vercel.app/blog/${params.id}`)
const post = await res.json()
if (!res.ok) {
// If there is a server error, you might want to
// throw an error instead of returning so that the cache is not updated
// until the next successful request.
throw new Error(`Failed to fetch posts, received status ${res.status}`)
}
return {
props: { post },
// Next.js will invalidate the cache when a
// request comes in, at most once every 60 seconds.
revalidate: 60,
}
}
```
</PagesOnly>
### Customizing the cache location
You can configure the Next.js cache location if you want to persist cached pages and data to durable storage, or share the cache across multiple containers or instances of your Next.js application. [Learn more](/docs/app/guides/self-hosting#caching-and-isr).
## Troubleshooting
### Debugging cached data in local development
If you are using the `fetch` API, you can add additional logging to understand which requests are cached or uncached. [Learn more about the `logging` option](/docs/app/api-reference/config/next-config-js/logging).
```jsx filename="next.config.js"
module.exports = {
logging: {
fetches: {
fullUrl: true,
},
},
}
```
### Verifying correct production behavior
To verify your pages are cached and revalidated correctly in production, you can test locally by running `next build` and then `next start` to run the production Next.js server.
This will allow you to test ISR behavior as it would work in a production environment. For further debugging, add the following environment variable to your `.env` file:
```bash filename=".env"
NEXT_PRIVATE_DEBUG_CACHE=1
```
This will make the Next.js server console log ISR cache hits and misses. You can inspect the output to see which pages are generated during `next build`, as well as how pages are updated as paths are accessed on-demand.
## Caveats
<AppOnly>
- ISR is only supported when using the Node.js runtime (default).
- ISR is not supported when creating a [Static Export](/docs/app/guides/static-exports).
- If you have multiple `fetch` requests in a prerendered route, and each has a different `revalidate` frequency, the lowest time will be used for ISR. However, those revalidate frequencies will still be respected by the [cache](/docs/app/getting-started/caching).
- If any of the `fetch` requests used on a route have a `revalidate` time of `0`, or an explicit `no-store`, the route will be dynamically rendered.
- Proxy won't be executed for on-demand ISR requests, meaning any path rewrites or logic in Proxy will not be applied. Ensure you are revalidating the exact path. For example, `/post/1` instead of a rewritten `/post-1`.
</AppOnly>
<PagesOnly>
- ISR is only supported when using the Node.js runtime (default).
- ISR is not supported when creating a [Static Export](/docs/app/guides/static-exports).
- Proxy won't be executed for on-demand ISR requests, meaning any path rewrites or logic in Proxy will not be applied. Ensure you are revalidating the exact path. For example, `/post/1` instead of a rewritten `/post-1`.
</PagesOnly>
## 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 ISR](/docs/app/guides/self-hosting#caching-and-isr) when self-hosting Next.js.
## Version history
| Version | Changes |
| --------- | ----------------------------------------------------------------------------------- |
| `v14.1.0` | Custom `cacheHandler` is stable. |
| `v13.0.0` | App Router is introduced. |
| `v12.2.0` | Pages Router: On-Demand ISR is stable |
| `v12.0.0` | Pages Router: [Bot-aware ISR fallback](/blog/next-12#bot-aware-isr-fallback) added. |
| `v9.5.0` | Pages Router: [Stable ISR introduced](/blog/next-9-5). |
+4
View File
@@ -0,0 +1,4 @@
---
title: Guides
description: Learn how to implement common patterns and real-world use cases using Next.js
---
+287
View File
@@ -0,0 +1,287 @@
---
title: Ensuring instant navigations
description: Learn how to structure your app to prefetch and prerender more content, providing instant page loads and client navigations.
nav_title: Instant navigation
version: draft
related:
title: Learn more
description: Explore the full instant API, caching, and revalidation.
links:
- app/api-reference/file-conventions/route-segment-config/instant
- app/getting-started/caching
- app/getting-started/revalidating
- app/guides/prefetching
---
With [Cache Components](/docs/app/api-reference/config/next-config-js/cacheComponents) enabled, wrapping uncached data in `<Suspense>` boundaries produces instant navigations — but only if the boundaries are in the right place. A misplaced boundary can silently block client-side navigations, especially where the entry point varies by shared layout. **Always export `unstable_instant` from routes that should navigate instantly** — it validates the caching structure at dev time and build time, catching issues before they reach users.
This guide starts with a product page that navigates instantly, then shows how to catch and fix a page where Suspense boundaries are not in the right place.
## A page that navigates instantly
A product page at `/store/[slug]` that fetches two things: product details (name, price) and live inventory. Product details rarely change, so they are cached with `use cache`. Inventory must be fresh and streams behind its own `<Suspense>` fallback:
```tsx filename="app/store/[slug]/page.tsx" highlight={1,12-19,25}
export const unstable_instant = { prefetch: 'static' }
import { Suspense } from 'react'
export default async function ProductPage({
params,
}: {
params: Promise<{ slug: string }>
}) {
return (
<div>
<Suspense fallback={<p>Loading product...</p>}>
{params.then(({ slug }) => (
<ProductInfo slug={slug} />
))}
</Suspense>
<Suspense fallback={<p>Checking availability...</p>}>
<Inventory params={params} />
</Suspense>
</div>
)
}
async function ProductInfo({ slug }: { slug: string }) {
'use cache'
const product = await fetchProduct(slug)
return (
<>
<h1>{product.name}</h1>
<p>${product.price}</p>
</>
)
}
async function Inventory({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params
const inventory = await fetchInventory(slug)
return <p>{inventory.count} in stock</p>
}
```
There is no `generateStaticParams`, so `[slug]` is a dynamic segment and `slug` is only known at request time. Awaiting `params` suspends, which is why each component that reads it has its own `<Suspense>` boundary. The `params` Promise is resolved inline with `.then()` so the cached `ProductInfo` receives a plain `slug` string.
The [`unstable_instant`](/docs/app/api-reference/file-conventions/route-segment-config/instant) export on line 1 tells Next.js to validate that this page produces an instant [static shell](/docs/app/glossary#static-shell) at every possible entry point. Validation runs during development and at build time. If a component would block navigation, the error overlay tells you exactly which one and suggests a fix.
### Inspect it with the Next.js DevTools
Enable the Instant Navigation DevTools toggle in your Next.js config:
```ts filename="next.config.ts" highlight={5-7}
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
cacheComponents: true,
experimental: {
instantNavigationDevToolsToggle: true,
},
}
export default nextConfig
```
Open the Next.js DevTools and select **Instant Navs**. You will see two options:
- **Page load**: click **Reload** to refresh the page and freeze it at the initial static UI generated for this route, before any dynamic data streams in.
- **Client navigation**: once enabled, clicking any link in your app shows the prefetched UI for that page instead of the full result.
Try a **page load**. "Loading product..." and "Checking availability..." appear as separate fallbacks. On the first visit the cache is cold, so both fallbacks are visible. Navigate to the page again and the product name appears immediately from cache.
Now try a **client navigation** (click a link from `/store/shoes` to `/store/hats`). The product name and price appear immediately (cached). "Checking availability..." shows where inventory will stream in.
> **Good to know:** Page loads and client navigations can produce different shells. Client-side hooks like `useSearchParams` suspend on page loads (search params are not known at build time) but resolve synchronously on client navigations (the router already has the params).
<details>
<summary>Why page loads and client navigations produce different shells</summary>
On a page load, the entire page renders from the document root. Every component runs on the server, and anything that suspends is caught by the nearest Suspense boundary in the full tree.
On a client navigation (link click), Next.js only re-renders below the layout that the source and destination routes share. Components above that shared layout are not re-rendered. This means a Suspense boundary in the root layout covers everything on a page load, but for a client navigation between `/store/shoes` and `/store/hats`, the shared `/store` layout is the entry point. The root Suspense sits above it and has no effect.
This is also why client-side hooks behave differently. `useSearchParams()` suspends during server rendering because search params are not available at build time. But on a client navigation, the router already has the params from the URL, so the hook resolves synchronously. The same component can appear in the instant shell on a client navigation but behind a fallback on a page load.
</details>
### Prevent regressions with e2e tests
Validation catches structural problems during development and at build time. To prevent regressions as the codebase evolves, the `@next/playwright` package includes an `instant()` helper that asserts on exactly what appears in the instant shell:
```typescript filename="e2e/navigation.test.ts"
import { test, expect } from '@playwright/test'
import { instant } from '@next/playwright'
test('product title appears instantly', async ({ page }) => {
await page.goto('/store/shoes')
await instant(page, async () => {
await page.click('a[href="/store/hats"]')
await expect(page.locator('h1')).toContainText('Baseball Cap')
})
// After instant() exits, dynamic content streams in
await expect(page.locator('text=in stock')).toBeVisible()
})
```
`instant()` holds back dynamic content while the callback runs against the static shell. After it resolves, dynamic content streams in and you can assert on the full page.
There is no need to write an `instant()` test for every navigation. Build-time validation already provides the structural guarantee. Use `instant()` for the user flows that matter most.
## Fixing a page that blocks
Now consider a different route, `/shop/[slug]`, that has the same data requirements but without local Suspense boundaries or caching:
```tsx filename="app/shop/[slug]/page.tsx"
export default async function ProductPage({
params,
}: {
params: Promise<{ slug: string }>
}) {
const { slug } = await params
const product = await fetchProduct(slug)
const inventory = await fetchInventory(slug)
return (
<div>
<h1>{product.name}</h1>
<p>${product.price}</p>
<p>{inventory.count} in stock</p>
</div>
)
}
```
The root layout wraps `{children}` in `<Suspense>`:
```tsx filename="app/layout.tsx" highlight={9-11}
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>
<Suspense fallback={<p>Loading...</p>}>{children}</Suspense>
</body>
</html>
)
}
```
On an initial page load, the root Suspense catches the async work and streams the page in behind the fallback. Everything appears to work. But on a client navigation from `/shop/shoes` to `/shop/hats`, the shared `/shop` layout is the entry point. The root `<Suspense>` boundary is above that layout, so it is invisible to this navigation. The page fetches uncached data with no local boundary, so the old page stays visible until the server finishes renderingm making the navigation feel unresponsive.
### Step 1: Add instant validation
Add the `unstable_instant` export to surface the problem:
```tsx filename="app/shop/[slug]/page.tsx" highlight={1}
export const unstable_instant = { prefetch: 'static' }
export default async function ProductPage({
params,
}: {
params: Promise<{ slug: string }>
}) {
const { slug } = await params
const product = await fetchProduct(slug)
const inventory = await fetchInventory(slug)
return (
<div>
<h1>{product.name}</h1>
<p>${product.price}</p>
<p>{inventory.count} in stock</p>
</div>
)
}
```
Next.js now simulates navigations at every shared layout boundary in the route. Awaiting `params` and both data fetches are flagged as violations because they suspend or access uncached data outside a Suspense boundary. Each error identifies the specific component and suggests a fix.
### Step 2: Fix the errors
Look at the data. There is no `generateStaticParams`, so `slug` is only known at request time. Awaiting `params` suspends, so every component that reads it needs its own `<Suspense>` boundary.
Decide what to do with each fetch:
- **Product details** (name, price) rarely change. Cache them as a function of `slug` with `use cache`.
- **Inventory** must be fresh from upstream. Leave it uncached and let it stream behind a `<Suspense>` fallback.
The result is the same structure from the first section:
```tsx filename="app/shop/[slug]/page.tsx" highlight={1,12-19,25}
export const unstable_instant = { prefetch: 'static' }
import { Suspense } from 'react'
export default async function ProductPage({
params,
}: {
params: Promise<{ slug: string }>
}) {
return (
<div>
<Suspense fallback={<p>Loading product...</p>}>
{params.then(({ slug }) => (
<ProductInfo slug={slug} />
))}
</Suspense>
<Suspense fallback={<p>Checking availability...</p>}>
<Inventory params={params} />
</Suspense>
</div>
)
}
async function ProductInfo({ slug }: { slug: string }) {
'use cache'
const product = await fetchProduct(slug)
return (
<>
<h1>{product.name}</h1>
<p>${product.price}</p>
</>
)
}
async function Inventory({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params
const inventory = await fetchInventory(slug)
return <p>{inventory.count} in stock</p>
}
```
Validation passes. Open the DevTools and try a client navigation. The product name and price appear immediately, and "Checking availability..." shows where inventory will stream in.
<details>
<summary>How validation checks every entry point</summary>
When you add `unstable_instant` to a route, Next.js does not only check the initial page load. It simulates navigations at every possible shared layout boundary in the route hierarchy.
For a route like `/shop/[slug]`, validation checks:
- Entry from outside (page load): the full tree renders, root layout Suspense catches everything
- Entry from a sibling under `/shop` (client navigation from `/shop/shoes` to `/shop/hats`): only the page segment re-renders, the `/shop` layout is the entry point
Each entry point is validated independently. A Suspense boundary that covers one path might be invisible to another. This is why a page can pass the initial load check but fail for sibling navigations, and why catching these issues by hand is difficult as the number of routes grows.
</details>
## Opting out with `instant = false`
Not every layout can be instant. A dashboard layout that reads cookies and fetches user-specific data might be too dynamic for the first entry. You can set `instant = false` on that layout to exempt it from validation:
```tsx filename="app/dashboard/layout.tsx"
export const unstable_instant = false
```
This tells validation: do not require that entry into `/dashboard` is instant, but still allows you to validate sibling navigations within it by using `instant` on those inner segments. Navigating from `/dashboard/a` to `/dashboard/b` can still be checked by adding `instant` to the page segments under `/dashboard`.
## Next steps
- [`instant` API reference](/docs/app/api-reference/file-conventions/route-segment-config/instant) for all configuration options, including runtime prefetching and incremental adoption with `instant = false`
- [Caching](/docs/app/getting-started/caching) for background on `use cache`, Suspense, and Partial Prerendering
- [Revalidating](/docs/app/getting-started/revalidating) for how to expire cached data with `cacheLife` and `updateTag`
+96
View File
@@ -0,0 +1,96 @@
---
title: How to set up instrumentation
nav_title: Instrumentation
description: Learn how to use instrumentation to run code at server startup in your Next.js app
related:
title: Learn more about Instrumentation
links:
- app/api-reference/file-conventions/instrumentation
---
{/* 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. */}
Instrumentation is the process of using code to integrate monitoring and logging tools into your application. This allows you to track the performance and behavior of your application, and to debug issues in production.
## Convention
To set up instrumentation, create `instrumentation.ts|js` file in the **root directory** of your project (or inside the [`src`](/docs/app/api-reference/file-conventions/src-folder) folder if using one).
Then, export a `register` function in the file. This function will be called **once** when a new Next.js server instance is initiated, and must complete before the server is ready to handle requests.
For example, to use Next.js with [OpenTelemetry](https://opentelemetry.io/) and [@vercel/otel](https://vercel.com/docs/observability/otel-overview):
```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')
}
```
See the [Next.js with OpenTelemetry example](https://github.com/vercel/next.js/tree/canary/examples/with-opentelemetry) for a complete implementation.
> **Good to know**:
>
> - The `instrumentation` file should be in the root of your project and not inside the `app` or `pages` directory. If you're using the `src` folder, then place the file inside `src` alongside `pages` and `app`.
> - If you use the [`pageExtensions` config option](/docs/app/api-reference/config/next-config-js/pageExtensions) to add a suffix, you will also need to update the `instrumentation` filename to match.
## Examples
### Importing files with side effects
Sometimes, it may be useful to import a file in your code because of the side effects it will cause. For example, you might import a file that defines a set of global variables, but never explicitly use the imported file in your code. You would still have access to the global variables the package has declared.
We recommend importing files using JavaScript `import` syntax within your `register` function. The following example demonstrates a basic usage of `import` in a `register` function:
```ts filename="instrumentation.ts" switcher
export async function register() {
await import('package-with-side-effect')
}
```
```js filename="instrumentation.js" switcher
export async function register() {
await import('package-with-side-effect')
}
```
> **Good to know:**
>
> We recommend importing the file from within the `register` function, rather than at the top of the file. By doing this, you can colocate all of your side effects in one place in your code, and avoid any unintended consequences from importing globally at the top of the file.
### Importing runtime-specific code
Next.js calls `register` in all environments, so it's important to conditionally import any code that doesn't support specific runtimes (e.g. [Edge or Node.js](/docs/app/api-reference/edge)). You can use the `NEXT_RUNTIME` environment variable to get the current environment:
```ts filename="instrumentation.ts" switcher
export async function register() {
if (process.env.NEXT_RUNTIME === 'nodejs') {
await import('./instrumentation-node')
}
if (process.env.NEXT_RUNTIME === 'edge') {
await import('./instrumentation-edge')
}
}
```
```js filename="instrumentation.js" switcher
export async function register() {
if (process.env.NEXT_RUNTIME === 'nodejs') {
await import('./instrumentation-node')
}
if (process.env.NEXT_RUNTIME === 'edge') {
await import('./instrumentation-edge')
}
}
```
+226
View File
@@ -0,0 +1,226 @@
---
title: Internationalization
description: Add support for multiple languages with internationalized routing and localized content.
---
Next.js enables you to configure the routing and rendering of content to support multiple languages. Making your site adaptive to different locales includes translated content (localization) and internationalized routes.
## Terminology
- **Locale:** An identifier for a set of language and formatting preferences. This usually includes the preferred language of the user and possibly their geographic region.
- `en-US`: English as spoken in the United States
- `nl-NL`: Dutch as spoken in the Netherlands
- `nl`: Dutch, no specific region
## Routing Overview
Its recommended to use the users language preferences in the browser to select which locale to use. Changing your preferred language will modify the incoming `Accept-Language` header to your application.
For example, using the following libraries, you can look at an incoming `Request` to determine which locale to select, based on the `Headers`, locales you plan to support, and the default locale.
```js filename="proxy.js"
import { match } from '@formatjs/intl-localematcher'
import Negotiator from 'negotiator'
let headers = { 'accept-language': 'en-US,en;q=0.5' }
let languages = new Negotiator({ headers }).languages()
let locales = ['en-US', 'nl-NL', 'nl']
let defaultLocale = 'en-US'
match(languages, locales, defaultLocale) // -> 'en-US'
```
Routing can be internationalized by either the sub-path (`/fr/products`) or domain (`my-site.fr/products`). With this information, you can now redirect the user based on the locale inside [Proxy](/docs/app/api-reference/file-conventions/proxy).
```js filename="proxy.js"
import { NextResponse } from "next/server";
let locales = ['en-US', 'nl-NL', 'nl']
// Get the preferred locale, similar to the above or using a library
function getLocale(request) { ... }
export function proxy(request) {
// Check if there is any supported locale in the pathname
const { pathname } = request.nextUrl
const pathnameHasLocale = locales.some(
(locale) => pathname.startsWith(`/${locale}/`) || pathname === `/${locale}`
)
if (pathnameHasLocale) return
// Redirect if there is no locale
const locale = getLocale(request)
request.nextUrl.pathname = `/${locale}${pathname}`
// e.g. incoming request is /products
// The new URL is now /en-US/products
return NextResponse.redirect(request.nextUrl)
}
export const config = {
matcher: [
// Skip all internal paths (_next)
'/((?!_next).*)',
// Optional: only run on root (/) URL
// '/'
],
}
```
Finally, ensure all special files inside `app/` are nested under `app/[lang]`. This enables the Next.js router to dynamically handle different locales in the route, and forward the `lang` parameter to every layout and page. For example:
```tsx filename="app/[lang]/page.tsx" switcher
// You now have access to the current locale
// e.g. /en-US/products -> `lang` is "en-US"
export default async function Page({ params }: PageProps<'/[lang]'>) {
const { lang } = await params
return ...
}
```
```jsx filename="app/[lang]/page.js" switcher
// You now have access to the current locale
// e.g. /en-US/products -> `lang` is "en-US"
export default async function Page({ params }) {
const { lang } = await params
return ...
}
```
> **Good to know:** `PageProps` and `LayoutProps` are globally available TypeScript helpers that provide strong typing for route parameters. See [PageProps](/docs/app/api-reference/file-conventions/page#page-props-helper) and [LayoutProps](/docs/app/api-reference/file-conventions/layout#layout-props-helper) for more details.
The root layout can also be nested in the new folder (e.g. `app/[lang]/layout.js`).
## Localization
Changing displayed content based on the users preferred locale, or localization, is not something specific to Next.js. The patterns described below would work the same with any web application.
Lets assume we want to support both English and Dutch content inside our application. We might maintain two different “dictionaries”, which are objects that give us a mapping from some key to a localized string. For example:
```json filename="dictionaries/en.json"
{
"products": {
"cart": "Add to Cart"
}
}
```
```json filename="dictionaries/nl.json"
{
"products": {
"cart": "Toevoegen aan Winkelwagen"
}
}
```
We can then create a `getDictionary` function to load the translations for the requested locale:
```ts filename="app/[lang]/dictionaries.ts" switcher
import 'server-only'
const dictionaries = {
en: () => import('./dictionaries/en.json').then((module) => module.default),
nl: () => import('./dictionaries/nl.json').then((module) => module.default),
}
export type Locale = keyof typeof dictionaries
export const hasLocale = (locale: string): locale is Locale =>
locale in dictionaries
export const getDictionary = async (locale: Locale) => dictionaries[locale]()
```
```js filename="app/[lang]/dictionaries.js" switcher
import 'server-only'
const dictionaries = {
en: () => import('./dictionaries/en.json').then((module) => module.default),
nl: () => import('./dictionaries/nl.json').then((module) => module.default),
}
export const hasLocale = (locale) => locale in dictionaries
export const getDictionary = async (locale) => dictionaries[locale]()
```
Given the currently selected language, we can fetch the dictionary inside of a layout or page.
Since `lang` is typed as `string`, using `hasLocale` narrows the type to your supported locales. It also ensures a 404 is returned if a translation is missing, rather than a runtime error.
```tsx filename="app/[lang]/page.tsx" switcher
import { notFound } from 'next/navigation'
import { getDictionary, hasLocale } from './dictionaries'
export default async function Page({ params }: PageProps<'/[lang]'>) {
const { lang } = await params
if (!hasLocale(lang)) notFound()
const dict = await getDictionary(lang)
return <button>{dict.products.cart}</button> // Add to Cart
}
```
```jsx filename="app/[lang]/page.js" switcher
import { notFound } from 'next/navigation'
import { getDictionary, hasLocale } from './dictionaries'
export default async function Page({ params }) {
const { lang } = await params
if (!hasLocale(lang)) notFound()
const dict = await getDictionary(lang)
return <button>{dict.products.cart}</button> // Add to Cart
}
```
Because all layouts and pages in the `app/` directory default to [Server Components](/docs/app/getting-started/server-and-client-components), we do not need to worry about the size of the translation files affecting our client-side JavaScript bundle size. This code will **only run on the server**, and only the resulting HTML will be sent to the browser.
## Static Rendering
To generate static routes for a given set of locales, we can use `generateStaticParams` with any page or layout. This can be global, for example, in the root layout:
```tsx filename="app/[lang]/layout.tsx" switcher
export async function generateStaticParams() {
return [{ lang: 'en-US' }, { lang: 'de' }]
}
export default async function RootLayout({
children,
params,
}: LayoutProps<'/[lang]'>) {
return (
<html lang={(await params).lang}>
<body>{children}</body>
</html>
)
}
```
```jsx filename="app/[lang]/layout.js" switcher
export async function generateStaticParams() {
return [{ lang: 'en-US' }, { lang: 'de' }]
}
export default async function RootLayout({ children, params }) {
return (
<html lang={(await params).lang}>
<body>{children}</body>
</html>
)
}
```
## Resources
- [Minimal i18n routing and translations](https://github.com/vercel/next.js/tree/canary/examples/i18n-routing)
- [`next-intl`](https://next-intl.dev)
- [`next-international`](https://github.com/QuiiBz/next-international)
- [`next-i18n-router`](https://github.com/i18nexus/next-i18n-router)
- [`paraglide-next`](https://inlang.com/m/osslbuzt/paraglide-next-i18n)
- [`lingui`](https://lingui.dev)
- [`tolgee`](https://tolgee.io/apps-integrations/next)
- [`next-intlayer`](https://intlayer.org/doc/environment/nextjs)
- [`gt-next`](https://generaltranslation.com/en/docs/next)
+87
View File
@@ -0,0 +1,87 @@
---
title: How to implement JSON-LD in your Next.js application
nav_title: JSON-LD
description: Learn how to add JSON-LD to your Next.js application to describe your content to search engines and AI.
---
[JSON-LD](https://json-ld.org/) is a format for structured data that can be used by search engines and AI to help them understand the structure of the page beyond pure content. For example, you can use it to describe a person, an event, an organization, a movie, a book, a recipe, and many other types of entities.
Our current recommendation for JSON-LD is to render structured data as a `<script>` tag in your `layout.js` or `page.js` components.
The following snippet uses `JSON.stringify`, which does not sanitize malicious strings used in XSS injection. To prevent this type of vulnerability, you can scrub `HTML` tags from the `JSON-LD` payload, for example, by replacing the character, `<`, with its unicode equivalent, `\u003c`.
Review your organization's recommended approach to sanitize potentially dangerous strings, or use community maintained alternatives for `JSON.stringify` such as, [serialize-javascript](https://www.npmjs.com/package/serialize-javascript).
```tsx filename="app/products/[id]/page.tsx" switcher
export default async function Page({ params }) {
const { id } = await params
const product = await getProduct(id)
const jsonLd = {
'@context': 'https://schema.org',
'@type': 'Product',
name: product.name,
image: product.image,
description: product.description,
}
return (
<section>
{/* Add JSON-LD to your page */}
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify(jsonLd).replace(/</g, '\\u003c'),
}}
/>
{/* ... */}
</section>
)
}
```
```jsx filename="app/products/[id]/page.js" switcher
export default async function Page({ params }) {
const { id } = await params
const product = await getProduct(id)
const jsonLd = {
'@context': 'https://schema.org',
'@type': 'Product',
name: product.name,
image: product.image,
description: product.description,
}
return (
<section>
{/* Add JSON-LD to your page */}
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify(jsonLd).replace(/</g, '\\u003c'),
}}
/>
{/* ... */}
</section>
)
}
```
You can validate and test your structured data with the [Rich Results Test](https://search.google.com/test/rich-results) for Google or the generic [Schema Markup Validator](https://validator.schema.org/).
You can type your JSON-LD with TypeScript using community packages like [`schema-dts`](https://www.npmjs.com/package/schema-dts):
```tsx
import { Product, WithContext } from 'schema-dts'
const jsonLd: WithContext<Product> = {
'@context': 'https://schema.org',
'@type': 'Product',
name: 'Next.js Sticker',
image: 'https://nextjs.org/imgs/sticker.png',
description: 'Dynamic at the speed of static.',
}
```
> **Good to know**: The `next/script` component is optimized for loading and executing JavaScript. Since JSON-LD is structured data, not executable code, a native `<script>` tag is the right choice here.
+306
View File
@@ -0,0 +1,306 @@
---
title: How to lazy load Client Components and libraries
nav_title: Lazy Loading
description: Lazy load imported libraries and React Components to improve your application's loading performance.
---
{/* 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. */}
[Lazy loading](https://developer.mozilla.org/docs/Web/Performance/Lazy_loading) in Next.js helps improve the initial loading performance of an application by decreasing the amount of JavaScript needed to render a route.
<AppOnly>
It allows you to defer loading of **Client Components** and imported libraries, and only include them in the client bundle when they're needed. For example, you might want to defer loading a modal until a user clicks to open it.
There are two ways you can implement lazy loading in Next.js:
1. Using [Dynamic Imports](#nextdynamic) with `next/dynamic`
2. Using [`React.lazy()`](https://react.dev/reference/react/lazy) with [Suspense](https://react.dev/reference/react/Suspense)
By default, Server Components are automatically [code split](https://developer.mozilla.org/docs/Glossary/Code_splitting), and you can use [streaming](/docs/app/guides/streaming) to progressively send pieces of UI from the server to the client. Lazy loading applies to Client Components.
## `next/dynamic`
`next/dynamic` is a composite of [`React.lazy()`](https://react.dev/reference/react/lazy) and [Suspense](https://react.dev/reference/react/Suspense). It behaves the same way in the `app` and `pages` directories to allow for incremental migration.
## Examples
### Importing Client Components
```jsx filename="app/page.js"
'use client'
import { useState } from 'react'
import dynamic from 'next/dynamic'
// Client Components:
const ComponentA = dynamic(() => import('../components/A'))
const ComponentB = dynamic(() => import('../components/B'))
const ComponentC = dynamic(() => import('../components/C'), { ssr: false })
export default function ClientComponentExample() {
const [showMore, setShowMore] = useState(false)
return (
<div>
{/* Load immediately, but in a separate client bundle */}
<ComponentA />
{/* Load on demand, only when/if the condition is met */}
{showMore && <ComponentB />}
<button onClick={() => setShowMore(!showMore)}>Toggle</button>
{/* Load only on the client side */}
<ComponentC />
</div>
)
}
```
> **Note:** When a Server Component dynamically imports a Client Component, automatic [code splitting](https://developer.mozilla.org/docs/Glossary/Code_splitting) is currently **not** supported.
### Skipping SSR
When using `React.lazy()` and Suspense, Client Components will be [prerendered](https://github.com/reactwg/server-components/discussions/4) (SSR) by default.
> **Note:** `ssr: false` option will only work for Client Components, move it into Client Components ensure the client code-splitting working properly.
If you want to disable prerendering for a Client Component, you can use the `ssr` option set to `false`:
```jsx
const ComponentC = dynamic(() => import('../components/C'), { ssr: false })
```
### Importing Server Components
If you dynamically import a Server Component, only the Client Components that are children of the Server Component will be lazy-loaded - not the Server Component itself.
It will also help preload the static assets such as CSS when you're using it in Server Components.
```jsx filename="app/page.js"
import dynamic from 'next/dynamic'
// Server Component:
const ServerComponent = dynamic(() => import('../components/ServerComponent'))
export default function ServerComponentExample() {
return (
<div>
<ServerComponent />
</div>
)
}
```
> **Note:** `ssr: false` option is not supported in Server Components. You will see an error if you try to use it in Server Components.
> `ssr: false` is not allowed with `next/dynamic` in Server Components. Please move it into a Client Component.
### Loading External Libraries
External libraries can be loaded on demand using the [`import()`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Operators/import) function. This example uses the external library `fuse.js` for fuzzy search. The module is only loaded on the client after the user types in the search input.
```jsx filename="app/page.js"
'use client'
import { useState } from 'react'
const names = ['Tim', 'Joe', 'Bel', 'Lee']
export default function Page() {
const [results, setResults] = useState()
return (
<div>
<input
type="text"
placeholder="Search"
onChange={async (e) => {
const { value } = e.currentTarget
// Dynamically load fuse.js
const Fuse = (await import('fuse.js')).default
const fuse = new Fuse(names)
setResults(fuse.search(value))
}}
/>
<pre>Results: {JSON.stringify(results, null, 2)}</pre>
</div>
)
}
```
### Adding a custom loading component
```jsx filename="app/page.js"
'use client'
import dynamic from 'next/dynamic'
const WithCustomLoading = dynamic(
() => import('../components/WithCustomLoading'),
{
loading: () => <p>Loading...</p>,
}
)
export default function Page() {
return (
<div>
{/* The loading component will be rendered while <WithCustomLoading/> is loading */}
<WithCustomLoading />
</div>
)
}
```
### Importing Named Exports
To dynamically import a named export, you can return it from the Promise returned by [`import()`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Operators/import) function:
```jsx filename="components/hello.js"
'use client'
export function Hello() {
return <p>Hello!</p>
}
```
```jsx filename="app/page.js"
import dynamic from 'next/dynamic'
const ClientComponent = dynamic(() =>
import('../components/hello').then((mod) => mod.Hello)
)
```
## Magic Comments
Next.js supports magic comments to control how dynamic imports are handled by the bundler. These comments work with dynamic `import()`, `require()`, `require.resolve()`, and `new Worker()` expressions.
> **Good to know:** Magic comments do not work with static `import` statements (`import x from 'y'`). They only work with dynamic expressions.
### `webpackIgnore` / `turbopackIgnore`
Use these comments to skip bundling a dynamic import. The import expression will be left as-is in the output, useful for runtime-only modules:
```js
// Skip bundling - import happens at runtime
const runtime = await import(/* webpackIgnore: true */ 'runtime-module')
// Turbopack-specific variant
const plugin = await import(/* turbopackIgnore: true */ pluginPath)
// Also works with require
const mod = require(/* webpackIgnore: true */ 'runtime-module')
```
### `turbopackOptional` (Turbopack only)
Use this comment to suppress build errors when a module might not exist. The import will still throw at runtime if the module is missing:
```js
// No build error if './optional-feature' doesn't exist
// Runtime will throw MODULE_NOT_FOUND if executed
const feature = await import(/* turbopackOptional: true */ './optional-feature')
// Also works with require
const mod = require(/* turbopackOptional: true */ './optional-module')
```
This is useful for:
- Conditional features that may not be installed
- Plugin systems where modules are optional
- Gradual migrations where some files may not exist yet
> **Good to know:** `webpackOptional` is not supported. Use `turbopackOptional` instead when using Turbopack.
</AppOnly>
<PagesOnly>
## `next/dynamic`
`next/dynamic` is a composite of [`React.lazy()`](https://react.dev/reference/react/lazy) and [Suspense](https://react.dev/reference/react/Suspense). It behaves the same way in the `app` and `pages` directories to allow for incremental migration.
In the example below, by using `next/dynamic`, the header component will not be included in the page's initial JavaScript bundle. The page will render the Suspense `fallback` first, followed by the `Header` component when the `Suspense` boundary is resolved.
```jsx
import dynamic from 'next/dynamic'
const DynamicHeader = dynamic(() => import('../components/header'), {
loading: () => <p>Loading...</p>,
})
export default function Home() {
return <DynamicHeader />
}
```
> **Good to know**: In `import('path/to/component')`, the path must be explicitly written. It can't be a template string nor a variable. Furthermore the `import()` has to be inside the `dynamic()` call for Next.js to be able to match webpack bundles / module ids to the specific `dynamic()` call and preload them before rendering. `dynamic()` can't be used inside of React rendering as it needs to be marked in the top level of the module for preloading to work, similar to `React.lazy`.
## Examples
### With named exports
To dynamically import a named export, you can return it from the [Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise) returned by [`import()`](https://github.com/tc39/proposal-dynamic-import#example):
```jsx filename="components/hello.js"
export function Hello() {
return <p>Hello!</p>
}
// pages/index.js
import dynamic from 'next/dynamic'
const DynamicComponent = dynamic(() =>
import('../components/hello').then((mod) => mod.Hello)
)
```
### With no SSR
To dynamically load a component on the client side, you can use the `ssr` option to disable server-rendering. This is useful if an external dependency or component relies on browser APIs like `window`.
```jsx
'use client'
import dynamic from 'next/dynamic'
const DynamicHeader = dynamic(() => import('../components/header'), {
ssr: false,
})
```
### With external libraries
This example uses the external library `fuse.js` for fuzzy search. The module is only loaded in the browser after the user types in the search input.
```jsx
import { useState } from 'react'
const names = ['Tim', 'Joe', 'Bel', 'Lee']
export default function Page() {
const [results, setResults] = useState()
return (
<div>
<input
type="text"
placeholder="Search"
onChange={async (e) => {
const { value } = e.currentTarget
// Dynamically load fuse.js
const Fuse = (await import('fuse.js')).default
const fuse = new Fuse(names)
setResults(fuse.search(value))
}}
/>
<pre>Results: {JSON.stringify(results, null, 2)}</pre>
</div>
)
}
```
</PagesOnly>
+276
View File
@@ -0,0 +1,276 @@
---
title: How to optimize your local development environment
nav_title: Development Environment
description: Learn how to optimize your local development environment with Next.js.
---
Next.js is designed to provide a great developer experience. As your application grows, you might notice slower compilation times during local development. This guide will help you identify and fix common compile-time performance issues.
## Local dev vs. production
The development process with `next dev` is different than `next build` and `next start`.
`next dev` compiles routes in your application as you open or navigate to them. This enables you to start the dev server without waiting for every route in your application to compile, which is both faster and uses less memory. Running a production build applies other optimizations, like minifying files and creating content hashes, which are not needed for local development.
## Improving local dev performance
### 1. Check your computer's antivirus
Antivirus software can slow down file access. While this is more common on Windows machines, this can be an issue for any system with an antivirus tool installed.
On Windows, you can add your project to the [Microsoft Defender Antivirus exclusion list](https://support.microsoft.com/en-us/windows/virus-and-threat-protection-in-the-windows-security-app-1362f4cd-d71a-b52a-0b66-c2820032b65e#bkmk_threat-protection-settings).
1. Open the **"Windows Security"** application and then select **"Virus & threat protection"** &rarr; **"Manage settings"** &rarr; **"Add or remove exclusions"**.
2. Add a **"Folder"** exclusion. Select your project folder.
On macOS, you can disable [Gatekeeper](https://support.apple.com/guide/security/gatekeeper-and-runtime-protection-sec5599b66df/web) inside of your terminal.
1. Run `sudo spctl developer-mode enable-terminal` in your terminal.
2. Open the **"System Settings"** app and then select **"Privacy & Security"** &rarr; **"Developer Tools"**.
3. Ensure your terminal is listed and enabled. If you're using a third-party terminal like iTerm or Ghostty, add that to the list.
4. Restart your terminal.
<Image
alt="Screenshot of macOS System Settings showing the Privacy & Security pane"
srcLight="/docs/light/macos-gatekeeper-privacy-and-security.png"
srcDark="/docs/dark/macos-gatekeeper-privacy-and-security.png"
width="723"
height="250"
/>
<Image
alt="Screenshot of macOS System Settings showing the Developer Tools options"
srcLight="/docs/light/macos-gatekeeper-developer-tools.png"
srcDark="/docs/dark/macos-gatekeeper-developer-tools.png"
width="723"
height="250"
/>
If you or your employer have configured any other Antivirus solutions on your system, you should inspect the relevant settings for those products.
### 2. Update Next.js and use Turbopack
Make sure you're using the latest version of Next.js. Each new version often includes performance improvements.
Turbopack is now the default bundler for Next.js development and provides significant performance improvements over webpack.
```bash package="pnpm"
pnpm add next@latest
pnpm dev # Turbopack is used by default
```
```bash package="npm"
npm install next@latest
npm run dev # Turbopack is used by default
```
```bash package="yarn"
yarn add next@latest
yarn dev # Turbopack is used by default
```
```bash package="bun"
bun add next@latest
bun dev # Turbopack is used by default
```
If you need to use Webpack instead of Turbopack, you can opt-in:
```bash package="pnpm"
pnpm dev --webpack
```
```bash package="npm"
npm run dev -- --webpack
```
```bash package="yarn"
yarn dev --webpack
```
```bash package="bun"
bun run dev --webpack
```
[Learn more about Turbopack](/blog/turbopack-for-development-stable). See our [upgrade guides](/docs/app/guides/upgrading) and codemods for more information.
### 3. Check your imports
The way you import code can greatly affect compilation and bundling time. Learn more about [optimizing package bundling](/docs/app/guides/package-bundling) and explore tools like [Dependency Cruiser](https://github.com/sverweij/dependency-cruiser) or [Madge](https://github.com/pahen/madge).
#### Icon libraries
Libraries like `@material-ui/icons`, `@phosphor-icons/react`, or `react-icons` can import thousands of icons, even if you only use a few. Try to import only the icons you need:
```jsx
// Instead of this:
import { TriangleIcon } from '@phosphor-icons/react'
// Do this:
import { TriangleIcon } from '@phosphor-icons/react/dist/csr/Triangle'
```
You can often find what import pattern to use in the documentation for the icon library you're using. This example follows [`@phosphor-icons/react`](https://www.npmjs.com/package/@phosphor-icons/react#import-performance-optimization) recommendation.
Libraries like `react-icons` includes many different icon sets. Choose one set and stick with that set.
For example, if your application uses `react-icons` and imports all of these:
- `pi` (Phosphor Icons)
- `md` (Material Design Icons)
- `tb` (tabler-icons)
- `cg` (cssgg)
Combined they will be tens of thousands of modules that the compiler has to handle, even if you only use a single import from each.
#### Barrel files
"Barrel files" are files that export many items from other files. They can slow down builds because the compiler has to parse them to find if there are side-effects in the module scope by using the import.
Try to import directly from specific files when possible. [Learn more about barrel files](https://vercel.com/blog/how-we-optimized-package-imports-in-next-js) and the built-in optimizations in Next.js.
#### Optimize package imports
Next.js can automatically optimize imports for certain packages. If you are using packages that utilize barrel files, add them to your `next.config.js`:
```jsx
module.exports = {
experimental: {
optimizePackageImports: ['package-name'],
},
}
```
Turbopack automatically analyzes imports and optimizes them. It does not require this configuration.
### 4. Check your Tailwind CSS setup
If you're using Tailwind CSS, make sure it's set up correctly.
A common mistake is configuring your `content` array in a way which includes `node_modules` or other large directories of files that should not be scanned.
Tailwind CSS version 3.4.8 or newer will warn you about settings that might slow down your build.
1. In your `tailwind.config.js`, be specific about which files to scan:
```jsx
module.exports = {
content: [
'./src/**/*.{js,ts,jsx,tsx}', // Good
// This might be too broad
// It will match `packages/**/node_modules` too
// '../../packages/**/*.{js,ts,jsx,tsx}',
],
}
```
2. Avoid scanning unnecessary files:
```jsx
module.exports = {
content: [
// Better - only scans the 'src' folder
'../../packages/ui/src/**/*.{js,ts,jsx,tsx}',
],
}
```
### 5. Check custom webpack settings
If you've added custom webpack settings, they might be slowing down compilation.
Consider if you really need them for local development. You can optionally only include certain tools for production builds, or explore using the default Turbopack bundler and configuring [loaders](/docs/app/api-reference/config/next-config-js/turbopack#configuring-webpack-loaders) instead.
### 6. Optimize memory usage
If your app is very large, it might need more memory.
[Learn more about optimizing memory usage](/docs/app/guides/memory-usage).
### 7. Server Components and data fetching
Changes to Server Components cause the entire page to re-render locally in order to show the new changes, which includes fetching new data for the component.
The experimental `serverComponentsHmrCache` option allows you to cache `fetch` responses in Server Components across Hot Module Replacement (HMR) refreshes in local development. This results in faster responses and reduced costs for billed API calls.
[Learn more about the experimental option](/docs/app/api-reference/config/next-config-js/serverComponentsHmrCache).
### 8. Consider local development over Docker
If you're using Docker for development on Mac or Windows, you may experience significantly slower performance compared to running Next.js locally.
Docker's filesystem access on Mac and Windows can cause Hot Module Replacement (HMR) to take seconds or even minutes, while the same application runs with fast HMR when developed locally.
This performance difference is due to how Docker handles filesystem operations outside of Linux environments. For the best development experience:
- Use local development (`npm run dev` or `pnpm dev`) instead of Docker during development
- Reserve Docker for production deployments and testing production builds
- If you must use Docker for development, consider using Docker on a Linux machine or VM
[Learn more about Docker deployment](/docs/app/getting-started/deploying#docker) for production use.
## Tools for finding problems
### Detailed fetch logging
Use the `logging.fetches` option in your `next.config.js` file, to see more detailed information about what's happening during development:
```js
module.exports = {
logging: {
fetches: {
fullUrl: true,
},
},
}
```
[Learn more about fetch logging](/docs/app/api-reference/config/next-config-js/logging).
### Turbopack tracing
Turbopack tracing is a tool that helps you understand the performance of your application during local development.
It provides detailed information about the time taken for each module to compile and how they are related.
1. Make sure you have the latest version of Next.js installed.
1. Generate a Turbopack trace file:
```bash package="pnpm"
NEXT_TURBOPACK_TRACING=1 pnpm dev
```
```bash package="npm"
NEXT_TURBOPACK_TRACING=1 npm run dev
```
```bash package="yarn"
NEXT_TURBOPACK_TRACING=1 yarn dev
```
```bash package="bun"
NEXT_TURBOPACK_TRACING=1 bun dev
```
1. Navigate around your application or make edits to files to reproduce the problem.
1. Stop the Next.js development server.
1. A file called `trace-turbopack` will be available in the `.next/dev` folder.
1. You can interpret the file using `npx next internal trace [path-to-file]`:
```bash
npx next internal trace .next/dev/trace-turbopack
```
On versions where `trace` is not available, the command was named `turbo-trace-server`:
```bash
npx next internal turbo-trace-server .next/dev/trace-turbopack
```
1. Once the trace server is running you can view the trace at https://trace.nextjs.org/.
1. By default the trace viewer will aggregate timings, in order to see each individual time you can switch from "Aggregated in order" to "Spans in order" at the top right of the viewer.
> **Good to know**: The trace file is placed under the development server directory, which defaults to `.next/dev`.
### Still having problems?
Share the trace file generated in the [Turbopack Tracing](#turbopack-tracing) section and share it on [GitHub Discussions](https://github.com/vercel/next.js/discussions) or [Discord](https://nextjs.org/discord).
+188
View File
@@ -0,0 +1,188 @@
---
title: Enabling Next.js MCP Server for Coding Agents
nav_title: Next.js MCP Server
description: Learn how to use Next.js MCP support to allow coding agents access to your application state
---
The [Model Context Protocol (MCP)](https://modelcontextprotocol.io) is an open standard that allows AI agents and coding assistants to interact with your applications through a standardized interface.
Next.js 16+ includes MCP support that enables coding agents to access your application's internals in real-time. To use this functionality, install the [`next-devtools-mcp`](https://www.npmjs.com/package/next-devtools-mcp) package.
## Getting started
**Requirements:** Next.js 16 or above
Add `next-devtools-mcp` to the `.mcp.json` file at the root of your project:
```json filename=".mcp.json"
{
"mcpServers": {
"next-devtools": {
"command": "npx",
"args": ["-y", "next-devtools-mcp@latest"]
}
}
}
```
That's it! When you start your development server, `next-devtools-mcp` will automatically discover and connect to your running Next.js instance.
For more configuration options, see the [next-devtools-mcp repository](https://github.com/vercel/next-devtools-mcp).
## Capabilities
`next-devtools-mcp` provides coding agents with a growing set of capabilities:
### Application Runtime Access
- **Error Detection**: Retrieve current build errors, runtime errors, and type errors from your dev server
- **Live State Queries**: Access real-time application state and runtime information
- **Page Metadata**: Query page routes, components, and rendering details
- **Server Actions**: Inspect Server Actions and component hierarchies
- **Development Logs**: Access development server logs and console output
### Development Tools
- **Next.js Knowledge Base**: Query comprehensive Next.js documentation and best practices
- **Migration and Upgrade Tools**: Automated helpers for upgrading to Next.js 16 with codemods
- **Caching Guide**: Setup and configuration assistance for Cache Components
- **Browser Testing**: [Playwright MCP](https://github.com/microsoft/playwright-mcp) integration for verifying pages in the browser
> **Note:** The Next.js team is actively expanding these capabilities. New tools and features are added regularly to improve the agent development experience.
## Development workflow
1. Start your Next.js development server:
```bash package="pnpm"
pnpm dev
```
```bash package="npm"
npm run dev
```
```bash package="yarn"
yarn dev
```
```bash package="bun"
bun dev
```
2. Your Coding Agent will automatically connect to the running Next.js instance via `next-devtools-mcp`
3. Open your application in the browser to view pages
4. Query your agent for insights and diagnostics (see examples below)
### Available tools
Through `next-devtools-mcp`, agents can use the following tools:
- **`get_errors`**: Retrieve current build errors, runtime errors, and type errors from your dev server
- **`get_logs`**: Get the path to the development log file containing browser console logs and server output
- **`get_page_metadata`**: Get metadata about specific pages including routes, components, and rendering information
- **`get_project_metadata`**: Retrieve project structure, configuration, and dev server URL
- **`get_routes`**: Get all routes that will become entry points by scanning the filesystem. Returns routes grouped by router type (appRouter, pagesRouter). Dynamic segments appear as `[param]` or `[...slug]` patterns
- **`get_server_action_by_id`**: Look up Server Actions by their ID to find the source file and function name
## Using with agents
MCP-compatible Coding Agents can automatically discover and connect to your Next.js development server, providing them with rich context about your project.
### Benefits for agent-assisted development
Agents can:
- **Make context-aware suggestions**: Recommend the right place to add new features based on your existing structure
- **Query live application state**: Check current configuration, routes, and middleware during development
- **Understand your app router pages layout**: Know exactly which page and layout are rendered
- **Provide accurate implementations**: Generate code that follows your project's patterns and conventions
## Examples
### Error detection and diagnosis
Ask your agent about errors in real-time:
```bash
User: "What errors are currently in my application?"
```
The agent will:
- Query your running Next.js application via `next-devtools-mcp`
- Retrieve current build errors, runtime errors, and type errors
- Analyze the errors and provide actionable fixes
<details>
<summary>Agent response (click to expand)</summary>
```bash
> fix errors on the page
⏺ I'll help you fix the errors on the page. Let me first check what errors exist by looking at the
current state of the project.
⏺ next-devtools - nextjs_runtime (MCP)(action: "discover_servers")
⏺ next-devtools - nextjs_runtime (MCP)(action: "call_tool", toolName: "get_errors")
⏺ I found a hydration error on the /about page. The error shows that the server is rendering
"server" but the client is rendering "client", causing a mismatch. Let me create a todo list and fix
this issue.
⏺ Perfect! Now let me check for errors on the page. 01:14 PM claude-sonnet-4-5-20250929
⏺ next-devtools - nextjs_runtime (MCP)(action: "call_tool", toolName: "get_errors")
⎿ {
"success": true,
"port": 3000,
"toolName": "get_errors",
"result": {
"content": [
{
"type": "text",
"text": "# Found errors in 1 browser session(s)\n\n## Session: /about\n\n**1 error(s)
found**\n\n### Runtime Errors\n\n#### Error 1 (Type: recoverable)\n\n**Error**: Hydration failed
```
</details>
### Upgrading and best practices
Get help with Next.js concepts and migrations:
```bash
User: "Help me upgrade my Next.js app to version 16"
```
The agent will analyze your current version, guide you through automated migrations with codemods, and provide step-by-step instructions for handling breaking changes.
Ask conceptual questions:
```bash
User: "When should I use 'use client' in App Router?"
```
The agent will query the Next.js knowledge base and provide documentation-backed explanations with examples from your codebase.
## How it works
Next.js 16+ includes a built-in MCP endpoint at `/_next/mcp` that runs within your development server. The `next-devtools-mcp` package automatically discovers and communicates with these endpoints, allowing it to:
- Connect to multiple Next.js instances running on different ports
- Forward tool calls to the appropriate Next.js dev server
- Provide a unified interface for coding agents
This architecture decouples the agent interface from the internal implementation, enabling `next-devtools-mcp` to work seamlessly across different Next.js projects.
## Troubleshooting
### MCP server not connecting
- Ensure you're using Next.js v16 or above
- Verify `next-devtools-mcp` is configured in your `.mcp.json`
- Start your development server: `npm run dev`
- Restart your development server if it was already running
- Check that your coding agent has loaded the MCP server configuration
+825
View File
@@ -0,0 +1,825 @@
---
title: How to use markdown and MDX in Next.js
nav_title: MDX
description: Learn how to configure MDX and use it in your Next.js apps.
---
{/* 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. */}
[Markdown](https://daringfireball.net/projects/markdown/syntax) is a lightweight markup language used to format text. It allows you to write using plain text syntax and convert it to structurally valid HTML. It's commonly used for writing content on websites and blogs.
You write...
```md
I **love** using [Next.js](https://nextjs.org/)
```
Output:
```html
<p>I <strong>love</strong> using <a href="https://nextjs.org/">Next.js</a></p>
```
[MDX](https://mdxjs.com/) is a superset of markdown that lets you write [JSX](https://react.dev/learn/writing-markup-with-jsx) directly in your markdown files. It is a powerful way to add dynamic interactivity and embed React components within your content.
Next.js can support both local MDX content inside your application, as well as remote MDX files fetched dynamically on the server. The Next.js plugin handles transforming markdown and React components into HTML, including support for usage in Server Components (the default in App Router).
> **Good to know**: View the [Portfolio Starter Kit](https://vercel.com/templates/next.js/portfolio-starter-kit) template for a complete working example.
## Install dependencies
The `@next/mdx` package, and related packages, are used to configure Next.js so it can process markdown and MDX. **It sources data from local files**, allowing you to create pages with a `.md` or `.mdx` extension, directly in your `/pages` or `/app` directory.
Install these packages to render MDX with Next.js:
```bash package="pnpm"
pnpm add @next/mdx @mdx-js/loader @mdx-js/react @types/mdx
```
```bash package="npm"
npm install @next/mdx @mdx-js/loader @mdx-js/react @types/mdx
```
```bash package="yarn"
yarn add @next/mdx @mdx-js/loader @mdx-js/react @types/mdx
```
```bash package="bun"
bun add @next/mdx @mdx-js/loader @mdx-js/react @types/mdx
```
## Configure `next.config.mjs`
Update the `next.config.mjs` file at your project's root to configure it to use MDX:
```js filename="next.config.mjs"
import createMDX from '@next/mdx'
/** @type {import('next').NextConfig} */
const nextConfig = {
// Configure `pageExtensions` to include markdown and MDX files
pageExtensions: ['js', 'jsx', 'md', 'mdx', 'ts', 'tsx'],
// Optionally, add any other Next.js config below
}
const withMDX = createMDX({
// Add markdown plugins here, as desired
})
// Merge MDX config with Next.js config
export default withMDX(nextConfig)
```
This allows `.mdx` files to act as pages, routes, or imports in your application.
### Handling `.md` files
By default, `next/mdx` only compiles files with the `.mdx` extension. To handle `.md` files with webpack, update the `extension` option:
```js filename="next.config.mjs"
const withMDX = createMDX({
extension: /\.(md|mdx)$/,
})
```
## Add an `mdx-components.tsx` file
Create an `mdx-components.tsx` (or `.js`) file in the root of your project to define global 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
}
```
> **Good to know**:
>
> - `mdx-components.tsx` is **required** to use `@next/mdx` with App Router and will not work without it.
> - Learn more about the [`mdx-components.tsx` file convention](/docs/app/api-reference/file-conventions/mdx-components).
> - Learn how to [use custom styles and components](#using-custom-styles-and-components).
## Rendering MDX
You can render MDX using Next.js's file based routing or by importing MDX files into other pages.
### Using file based routing
When using file based routing, you can use MDX pages like any other page.
<AppOnly>
In App Router apps, that includes being able to use [metadata](/docs/app/getting-started/metadata-and-og-images).
Create a new MDX page within the `/app` directory:
```txt
my-project
├── app
│ └── mdx-page
│ └── page.(mdx/md)
|── mdx-components.(tsx/js)
└── package.json
```
</AppOnly>
<PagesOnly>
Create a new MDX page within the `/pages` directory:
```txt
my-project
|── mdx-components.(tsx/js)
├── pages
│ └── mdx-page.(mdx/md)
└── package.json
```
</PagesOnly>
You can use MDX in these files, and even import React components, directly inside your MDX page:
```mdx
import { MyComponent } from 'my-component'
# Welcome to my MDX page!
This is some **bold** and _italics_ text.
This is a list in markdown:
- One
- Two
- Three
Checkout my React component:
<MyComponent />
```
Navigating to the `/mdx-page` route should display your rendered MDX page.
### Using imports
<AppOnly>
Create a new page within the `/app` directory and an MDX file wherever you'd like:
```txt
.
├── app/
│ └── mdx-page/
│ └── page.(tsx/js)
├── markdown/
│ └── welcome.(mdx/md)
├── mdx-components.(tsx/js)
└── package.json
```
</AppOnly>
<PagesOnly>
Create a new page within the `/pages` directory and an MDX file wherever you'd like:
```txt
.
├── markdown/
│ └── welcome.(mdx/md)
├── pages/
│ └── mdx-page.(tsx/js)
├── mdx-components.(tsx/js)
└── package.json
```
</PagesOnly>
You can use MDX in these files, and even import React components, directly inside your MDX page:
```mdx filename="markdown/welcome.mdx" switcher
import { MyComponent } from 'my-component'
# Welcome to my MDX page!
This is some **bold** and _italics_ text.
This is a list in markdown:
- One
- Two
- Three
Checkout my React component:
<MyComponent />
```
Import the MDX file inside the page to display the content:
<AppOnly>
```tsx filename="app/mdx-page/page.tsx" switcher
import Welcome from '@/markdown/welcome.mdx'
export default function Page() {
return <Welcome />
}
```
```jsx filename="app/mdx-page/page.js" switcher
import Welcome from '@/markdown/welcome.mdx'
export default function Page() {
return <Welcome />
}
```
</AppOnly>
<PagesOnly>
```tsx filename="pages/mdx-page.tsx" switcher
import Welcome from '@/markdown/welcome.mdx'
export default function Page() {
return <Welcome />
}
```
```jsx filename="pages/mdx-page.js" switcher
import Welcome from '@/markdown/welcome.mdx'
export default function Page() {
return <Welcome />
}
```
</PagesOnly>
Navigating to the `/mdx-page` route should display your rendered MDX page.
<AppOnly>
### Using dynamic imports
You can import dynamic MDX components instead of using filesystem routing for MDX files.
For example, you can have a dynamic route segment which loads MDX components from a separate directory:
<Image
alt="Route segments for dynamic MDX components"
srcLight="/docs/light/mdx-files.png"
srcDark="/docs/dark/mdx-files.png"
width="1600"
height="849"
/>
[`generateStaticParams`](/docs/app/api-reference/functions/generate-static-params) can be used to prerender the provided routes. By marking `dynamicParams` as `false`, accessing a route not defined in `generateStaticParams` will 404.
```tsx filename="app/blog/[slug]/page.tsx" switcher
export default async function Page({
params,
}: {
params: Promise<{ slug: string }>
}) {
const { slug } = await params
const { default: Post } = await import(`@/content/${slug}.mdx`)
return <Post />
}
export function generateStaticParams() {
return [{ slug: 'welcome' }, { slug: 'about' }]
}
export const dynamicParams = false
```
```jsx filename="app/blog/[slug]/page.js" switcher
export default async function Page({ params }) {
const { slug } = await params
const { default: Post } = await import(`@/content/${slug}.mdx`)
return <Post />
}
export function generateStaticParams() {
return [{ slug: 'welcome' }, { slug: 'about' }]
}
export const dynamicParams = false
```
> **Good to know**: Ensure you specify the `.mdx` file extension in your import. While it is not required to use [module path aliases](/docs/app/getting-started/installation#set-up-absolute-imports-and-module-path-aliases) (e.g., `@/content`), it does simplify your import path.
</AppOnly>
## Using custom styles and components
Markdown, when rendered, maps to native HTML elements. For example, writing the following markdown:
```md
## This is a heading
This is a list in markdown:
- One
- Two
- Three
```
Generates the following HTML:
```html
<h2>This is a heading</h2>
<p>This is a list in markdown:</p>
<ul>
<li>One</li>
<li>Two</li>
<li>Three</li>
</ul>
```
To style your markdown, you can provide custom components that map to the generated HTML elements. Styles and components can be implemented globally, locally, and with shared layouts.
### Global styles and components
Adding styles and components in `mdx-components.tsx` will affect _all_ MDX files in your application.
```tsx filename="mdx-components.tsx" switcher
import type { MDXComponents } from 'mdx/types'
import Image, { ImageProps } from 'next/image'
// This file allows you to provide custom React components
// to be used in MDX files. You can import and use any
// React component you want, including inline styles,
// components from other libraries, and more.
const components = {
// Allows customizing built-in components, e.g. to add styling.
h1: ({ children }) => (
<h1 style={{ color: 'red', fontSize: '48px' }}>{children}</h1>
),
img: (props) => (
<Image
sizes="100vw"
style={{ width: '100%', height: 'auto' }}
{...(props as ImageProps)}
/>
),
} satisfies MDXComponents
export function useMDXComponents(): MDXComponents {
return components
}
```
```js filename="mdx-components.js" switcher
import Image from 'next/image'
// This file allows you to provide custom React components
// to be used in MDX files. You can import and use any
// React component you want, including inline styles,
// components from other libraries, and more.
const components = {
// Allows customizing built-in components, e.g. to add styling.
h1: ({ children }) => (
<h1 style={{ color: 'red', fontSize: '48px' }}>{children}</h1>
),
img: (props) => (
<Image sizes="100vw" style={{ width: '100%', height: 'auto' }} {...props} />
),
}
export function useMDXComponents() {
return components
}
```
### Local styles and components
You can apply local styles and components to specific pages by passing them into imported MDX components. These will merge with and override [global styles and components](#global-styles-and-components).
<AppOnly>
```tsx filename="app/mdx-page/page.tsx" switcher
import Welcome from '@/markdown/welcome.mdx'
function CustomH1({ children }) {
return <h1 style={{ color: 'blue', fontSize: '100px' }}>{children}</h1>
}
const overrideComponents = {
h1: CustomH1,
}
export default function Page() {
return <Welcome components={overrideComponents} />
}
```
```jsx filename="app/mdx-page/page.js" switcher
import Welcome from '@/markdown/welcome.mdx'
function CustomH1({ children }) {
return <h1 style={{ color: 'blue', fontSize: '100px' }}>{children}</h1>
}
const overrideComponents = {
h1: CustomH1,
}
export default function Page() {
return <Welcome components={overrideComponents} />
}
```
</AppOnly>
<PagesOnly>
```tsx filename="pages/mdx-page.tsx" switcher
import Welcome from '@/markdown/welcome.mdx'
function CustomH1({ children }) {
return <h1 style={{ color: 'blue', fontSize: '100px' }}>{children}</h1>
}
const overrideComponents = {
h1: CustomH1,
}
export default function Page() {
return <Welcome components={overrideComponents} />
}
```
```jsx filename="pages/mdx-page.js" switcher
import Welcome from '@/markdown/welcome.mdx'
function CustomH1({ children }) {
return <h1 style={{ color: 'blue', fontSize: '100px' }}>{children}</h1>
}
const overrideComponents = {
h1: CustomH1,
}
export default function Page() {
return <Welcome components={overrideComponents} />
}
```
</PagesOnly>
### Shared layouts
<AppOnly>
To share a layout across MDX pages, you can use the [built-in layouts support](/docs/app/api-reference/file-conventions/layout) with the App Router.
```tsx filename="app/mdx-page/layout.tsx" switcher
export default function MdxLayout({ children }: { children: React.ReactNode }) {
// Create any shared layout or styles here
return <div style={{ color: 'blue' }}>{children}</div>
}
```
```jsx filename="app/mdx-page/layout.js" switcher
export default function MdxLayout({ children }) {
// Create any shared layout or styles here
return <div style={{ color: 'blue' }}>{children}</div>
}
```
</AppOnly>
<PagesOnly>
To share a layout around MDX pages, create a layout component:
```tsx filename="components/mdx-layout.tsx" switcher
export default function MdxLayout({ children }: { children: React.ReactNode }) {
// Create any shared layout or styles here
return <div style={{ color: 'blue' }}>{children}</div>
}
```
```jsx filename="components/mdx-layout.js" switcher
export default function MdxLayout({ children }) {
// Create any shared layout or styles here
return <div style={{ color: 'blue' }}>{children}</div>
}
```
Then, import the layout component into the MDX page, wrap the MDX content in the layout, and export it:
```mdx
import MdxLayout from '../components/mdx-layout'
# Welcome to my MDX page!
export default function MDXPage({ children }) {
return <MdxLayout>{children}</MdxLayout>
}
```
</PagesOnly>
### Using Tailwind typography plugin
If you are using [Tailwind](https://tailwindcss.com) to style your application, using the [`@tailwindcss/typography` plugin](https://tailwindcss.com/docs/plugins#typography) will allow you to reuse your Tailwind configuration and styles in your markdown files.
The plugin adds a set of `prose` classes that can be used to add typographic styles to content blocks that come from sources, like markdown.
[Install Tailwind typography](https://github.com/tailwindlabs/tailwindcss-typography?tab=readme-ov-file#installation) and use with [shared layouts](#shared-layouts) to add the `prose` you want.
<AppOnly>
```tsx filename="app/mdx-page/layout.tsx" switcher
export default function MdxLayout({ children }: { children: React.ReactNode }) {
// Create any shared layout or styles here
return (
<div className="prose prose-headings:mt-8 prose-headings:font-semibold prose-headings:text-black prose-h1:text-5xl prose-h2:text-4xl prose-h3:text-3xl prose-h4:text-2xl prose-h5:text-xl prose-h6:text-lg dark:prose-headings:text-white">
{children}
</div>
)
}
```
```jsx filename="app/mdx-page/layout.js" switcher
export default function MdxLayout({ children }) {
// Create any shared layout or styles here
return (
<div className="prose prose-headings:mt-8 prose-headings:font-semibold prose-headings:text-black prose-h1:text-5xl prose-h2:text-4xl prose-h3:text-3xl prose-h4:text-2xl prose-h5:text-xl prose-h6:text-lg dark:prose-headings:text-white">
{children}
</div>
)
}
```
</AppOnly>
<PagesOnly>
To share a layout around MDX pages, create a layout component:
```tsx filename="components/mdx-layout.tsx" switcher
export default function MdxLayout({ children }: { children: React.ReactNode }) {
// Create any shared layout or styles here
return (
<div className="prose prose-headings:mt-8 prose-headings:font-semibold prose-headings:text-black prose-h1:text-5xl prose-h2:text-4xl prose-h3:text-3xl prose-h4:text-2xl prose-h5:text-xl prose-h6:text-lg dark:prose-headings:text-white">
{children}
</div>
)
}
```
```jsx filename="components/mdx-layout.js" switcher
export default function MdxLayout({ children }) {
// Create any shared layout or styles here
return (
<div className="prose prose-headings:mt-8 prose-headings:font-semibold prose-headings:text-black prose-h1:text-5xl prose-h2:text-4xl prose-h3:text-3xl prose-h4:text-2xl prose-h5:text-xl prose-h6:text-lg dark:prose-headings:text-white">
{children}
</div>
)
}
```
Then, import the layout component into the MDX page, wrap the MDX content in the layout, and export it:
```mdx
import MdxLayout from '../components/mdx-layout'
# Welcome to my MDX page!
export default function MDXPage({ children }) {
return <MdxLayout>{children}</MdxLayout>
}
```
</PagesOnly >
## Frontmatter
Frontmatter is a YAML like key/value pairing that can be used to store data about a page. `@next/mdx` does **not** support frontmatter by default, though there are many solutions for adding frontmatter to your MDX content, such as:
- [remark-frontmatter](https://github.com/remarkjs/remark-frontmatter)
- [remark-mdx-frontmatter](https://github.com/remcohaszing/remark-mdx-frontmatter)
- [gray-matter](https://github.com/jonschlinkert/gray-matter)
`@next/mdx` **does** allow you to use exports like any other JavaScript component:
```mdx filename="content/blog-post.mdx" switcher
export const metadata = {
author: 'John Doe',
}
# Blog post
```
Metadata can now be referenced outside of the MDX file:
<AppOnly>
```tsx filename="app/blog/page.tsx" switcher
import BlogPost, { metadata } from '@/content/blog-post.mdx'
export default function Page() {
console.log('metadata: ', metadata)
//=> { author: 'John Doe' }
return <BlogPost />
}
```
```jsx filename="app/blog/page.js" switcher
import BlogPost, { metadata } from '@/content/blog-post.mdx'
export default function Page() {
console.log('metadata: ', metadata)
//=> { author: 'John Doe' }
return <BlogPost />
}
```
</AppOnly>
<PagesOnly>
```tsx filename="pages/blog.tsx" switcher
import BlogPost, { metadata } from '@/content/blog-post.mdx'
export default function Page() {
console.log('metadata: ', metadata)
//=> { author: 'John Doe' }
return <BlogPost />
}
```
```jsx filename="pages/blog.js" switcher
import BlogPost, { metadata } from '@/content/blog-post.mdx'
export default function Page() {
console.log('metadata: ', metadata)
//=> { author: 'John Doe' }
return <BlogPost />
}
```
</PagesOnly>
A common use case for this is when you want to iterate over a collection of MDX and extract data. For example, creating a blog index page from all blog posts. You can use packages like [Node's `fs` module](https://nodejs.org/api/fs.html) or [globby](https://www.npmjs.com/package/globby) to read a directory of posts and extract the metadata.
> **Good to know**:
>
> - Using `fs`, `globby`, etc. can only be used server-side.
> - View the [Portfolio Starter Kit](https://vercel.com/templates/next.js/portfolio-starter-kit) template for a complete working example.
## remark and rehype Plugins
You can optionally provide remark and rehype plugins to transform the MDX content.
For example, you can use [`remark-gfm`](https://github.com/remarkjs/remark-gfm) to support GitHub Flavored Markdown.
Since the remark and rehype ecosystem is ESM only, you'll need to use `next.config.mjs` or `next.config.ts` as the configuration file.
```js filename="next.config.mjs"
import remarkGfm from 'remark-gfm'
import createMDX from '@next/mdx'
/** @type {import('next').NextConfig} */
const nextConfig = {
// Allow .mdx extensions for files
pageExtensions: ['js', 'jsx', 'md', 'mdx', 'ts', 'tsx'],
// Optionally, add any other Next.js config below
}
const withMDX = createMDX({
// Add markdown plugins here, as desired
options: {
remarkPlugins: [remarkGfm],
rehypePlugins: [],
},
})
// Combine MDX and Next.js config
export default withMDX(nextConfig)
```
### Using Plugins with Turbopack
To use plugins with [Turbopack](/docs/app/api-reference/turbopack), upgrade to the latest `@next/mdx` and specify plugin names using a string:
```js filename="next.config.mjs"
import createMDX from '@next/mdx'
/** @type {import('next').NextConfig} */
const nextConfig = {
pageExtensions: ['js', 'jsx', 'md', 'mdx', 'ts', 'tsx'],
}
const withMDX = createMDX({
options: {
remarkPlugins: [
// Without options
'remark-gfm',
// With options
['remark-toc', { heading: 'The Table' }],
],
rehypePlugins: [
// Without options
'rehype-slug',
// With options
['rehype-katex', { strict: true, throwOnError: true }],
],
},
})
export default withMDX(nextConfig)
```
> **Good to know**:
>
> remark and rehype plugins without serializable options cannot be used yet with [Turbopack](/docs/app/api-reference/turbopack), because JavaScript functions can't be passed to Rust.
## Deep Dive: How do you transform markdown into HTML?
React does not natively understand markdown. The markdown plaintext needs to first be transformed into HTML. This can be accomplished with `remark` and `rehype`.
`remark` is an ecosystem of tools around markdown. `rehype` is the same, but for HTML. For example, the following code snippet transforms markdown into HTML:
```js
import { unified } from 'unified'
import remarkParse from 'remark-parse'
import remarkRehype from 'remark-rehype'
import rehypeSanitize from 'rehype-sanitize'
import rehypeStringify from 'rehype-stringify'
main()
async function main() {
const file = await unified()
.use(remarkParse) // Convert into markdown AST
.use(remarkRehype) // Transform to HTML AST
.use(rehypeSanitize) // Sanitize HTML input
.use(rehypeStringify) // Convert AST into serialized HTML
.process('Hello, Next.js!')
console.log(String(file)) // <p>Hello, Next.js!</p>
}
```
The `remark` and `rehype` ecosystem contains plugins for [syntax highlighting](https://github.com/atomiks/rehype-pretty-code), [linking headings](https://github.com/rehypejs/rehype-autolink-headings), [generating a table of contents](https://github.com/remarkjs/remark-toc), and more.
When using `@next/mdx` as shown above, you **do not** need to use `remark` or `rehype` directly, as it is handled for you. We're describing it here for a deeper understanding of what the `@next/mdx` package is doing underneath.
## Using the Rust-based MDX compiler (experimental)
Next.js supports a new MDX compiler written in Rust. This compiler is still experimental and is not recommended for production use. To use the new compiler, you need to configure `next.config.js` when you pass it to `withMDX`:
```js filename="next.config.js"
module.exports = withMDX({
experimental: {
mdxRs: true,
},
})
```
`mdxRs` also accepts an object to configure how to transform mdx files.
```js filename="next.config.js"
module.exports = withMDX({
experimental: {
mdxRs: {
jsxRuntime?: string // Custom jsx runtime
jsxImportSource?: string // Custom jsx import source,
mdxType?: 'gfm' | 'commonmark' // Configure what kind of mdx syntax will be used to parse & transform
},
},
})
```
## Helpful Links
- [MDX](https://mdxjs.com)
- [`@next/mdx`](https://www.npmjs.com/package/@next/mdx)
- [remark](https://github.com/remarkjs/remark)
- [rehype](https://github.com/rehypejs/rehype)
- [Markdoc](https://markdoc.dev/docs/nextjs)
+169
View File
@@ -0,0 +1,169 @@
---
title: How to optimize memory usage
nav_title: Memory Usage
description: Optimize memory used by your application in development and production.
---
As applications grow and become more feature rich, they can demand more resources when developing locally or creating production builds.
Let's explore some strategies and techniques to optimize memory and address common memory issues in Next.js.
## Reduce number of dependencies
Applications with a large amount of dependencies will use more memory.
The [Bundle Analyzer](/docs/app/guides/package-bundling) can help you investigate large dependencies in your application that may be able to be removed to improve performance and memory usage.
## Try `experimental.webpackMemoryOptimizations`
Starting in `v15.0.0`, you can add `experimental.webpackMemoryOptimizations: true` to your `next.config.js` file to change behavior in Webpack that reduces max memory usage but may increase compilation times by a slight amount.
> **Good to know**: This feature is currently experimental to test on more projects first, but it is considered to be low-risk.
## Run `next build` with `--experimental-debug-memory-usage`
Starting in `14.2.0`, you can run `next build --experimental-debug-memory-usage` to run the build in a mode where Next.js will print out information about memory usage continuously throughout the build, such as heap usage and garbage collection statistics. Heap snapshots will also be taken automatically when memory usage gets close to the configured limit.
> **Good to know**: This feature is not compatible with the Webpack build worker option which is auto-enabled unless you have custom webpack config.
## Record a heap profile
To look for memory issues, you can record a heap profile from Node.js and load it in Chrome DevTools to identify potential sources of memory leaks.
In your terminal, pass the `--heap-prof` flag to Node.js when starting your Next.js build:
```sh
node --heap-prof node_modules/next/dist/bin/next build
```
At the end of the build, a `.heapprofile` file will be created by Node.js.
In Chrome DevTools, you can open the Memory tab and click on the "Load Profile" button to visualize the file.
## Analyze a snapshot of the heap
You can use an inspector tool to analyze the memory usage of the application.
When running the `next build` or `next dev` command, add `NODE_OPTIONS=--inspect` to the beginning of the command. This will expose the inspector agent on the default port.
If you wish to break before any user code starts, you can pass `--inspect-brk` instead. While the process is running, you can use a tool such as Chrome DevTools to connect to the debugging port to record and analyze a snapshot of the heap to see what memory is being retained.
Starting in `14.2.0`, you can also run `next build` with the `--experimental-debug-memory-usage` flag to make it easier to take heap snapshots.
While running in this mode, you can send a `SIGUSR2` signal to the process at any point, and the process will take a heap snapshot.
The heap snapshot will be saved to the project root of the Next.js application and can be loaded in any heap analyzer, such as Chrome DevTools, to see what memory is retained. This mode is not yet compatible with Webpack build workers.
See [how to record and analyze heap snapshots](https://developer.chrome.com/docs/devtools/memory-problems/heap-snapshots) for more information.
## Webpack build worker
The Webpack build worker allows you to run Webpack compilations inside a separate Node.js worker which will decrease memory usage of your application during builds.
This option is enabled by default if your application does not have a custom Webpack configuration starting in `v14.1.0`.
If you are using an older version of Next.js or you have a custom Webpack configuration, you can enable this option by setting `experimental.webpackBuildWorker: true` inside your `next.config.js`.
> **Good to know**: This feature may not be compatible with all custom Webpack plugins.
## Disable Webpack cache
The [Webpack cache](https://webpack.js.org/configuration/cache/) saves generated Webpack modules in memory and/or to disk to improve the speed of builds. This can
help with performance, but it will also increase the memory usage of your application to store the cached data.
You can disable this behavior by adding a [custom Webpack configuration](/docs/app/api-reference/config/next-config-js/webpack) to your application:
```js filename="next.config.mjs"
/** @type {import('next').NextConfig} */
const nextConfig = {
webpack: (
config,
{ buildId, dev, isServer, defaultLoaders, nextRuntime, webpack }
) => {
if (config.cache && !dev) {
config.cache = Object.freeze({
type: 'memory',
})
}
// Important: return the modified config
return config
},
}
export default nextConfig
```
## Disable static analysis
Typechecking may require a lot of memory, especially in large projects.
However, most projects have a dedicated CI runner that already handles these tasks.
When the build produces out-of-memory issues during the "Running TypeScript" step, you can disable this task during builds:
```js filename="next.config.mjs"
/** @type {import('next').NextConfig} */
const nextConfig = {
typescript: {
// !! WARN !!
// Dangerously allow production builds to successfully complete even if
// your project has type errors.
// !! WARN !!
ignoreBuildErrors: true,
},
}
export default nextConfig
```
- [Ignoring TypeScript Errors](/docs/app/api-reference/config/typescript#disabling-typescript-errors-in-production)
Keep in mind that this may produce faulty deploys due to type errors.
We strongly recommend only promoting builds to production after static analysis has completed.
If you deploy to Vercel, you can check out the [guide for staging deployments](https://vercel.com/docs/deployments/managing-deployments#staging-and-promoting-a-production-deployment) to learn how to promote builds to production after custom tasks have succeeded.
## Disable source maps
Generating source maps consumes extra memory during the build process.
You can disable source map generation by adding `productionBrowserSourceMaps: false` and `experimental.serverSourceMaps: false` to your Next.js configuration.
When using the `cacheComponents` feature, Next.js will use source maps by default during the prerender phase of `next build`.
If you consistently encounter memory issues during that phase (after "Generating static pages"),
you can try disabling source maps in that phase by adding `enablePrerenderSourceMaps: false` to your Next.js configuration.
> **Good to know**: Some plugins may turn on source maps and may require custom configuration to disable.
## Edge memory issues
Next.js `v14.1.3` fixed a memory issue when using the Edge runtime. Please update to this version (or later) to see if it addresses your issue.
## Preloading Entries
When the Next.js server starts, it preloads each page's JavaScript modules into memory, rather than at request time.
This optimization allows for faster response times, in exchange for a larger initial memory footprint.
To disable this optimization, set the `experimental.preloadEntriesOnStart` flag to `false`.
```ts filename="next.config.ts" switcher
import type { NextConfig } from 'next'
const config: NextConfig = {
experimental: {
preloadEntriesOnStart: false,
},
}
export default config
```
```js filename="next.config.mjs" switcher
/** @type {import('next').NextConfig} */
const config = {
experimental: {
preloadEntriesOnStart: false,
},
}
export default config
```
Next.js doesn't unload these JavaScript modules, meaning that even with this optimization disabled, the memory footprint of your Next.js server will eventually be the same if all pages are eventually requested.
@@ -0,0 +1,175 @@
---
title: Migrating to Cache Components
nav_title: Migrating to Cache Components
description: Learn how to migrate from route segment configs to Cache Components in Next.js.
---
When [Cache Components](/docs/app/api-reference/config/next-config-js/cacheComponents) is enabled, route segment configs like `dynamic`, `revalidate`, and `fetchCache` are replaced by [`use cache`](/docs/app/api-reference/directives/use-cache) and [`cacheLife`](/docs/app/api-reference/functions/cacheLife).
## `dynamic = "force-dynamic"`
**Not needed.** All pages are dynamic by default.
```tsx filename="app/page.tsx" switcher
// Before - No longer needed
export const dynamic = 'force-dynamic'
export default function Page() {
return <div>...</div>
}
```
```jsx filename="app/page.js" switcher
// Before - No longer needed
export const dynamic = 'force-dynamic'
export default function Page() {
return <div>...</div>
}
```
```tsx filename="app/page.tsx" switcher
// After - Just remove it
export default function Page() {
return <div>...</div>
}
```
```jsx filename="app/page.js" switcher
// After - Just remove it
export default function Page() {
return <div>...</div>
}
```
## `dynamic = "force-static"`
Start by removing it. When unhandled uncached or runtime data access is detected during development and build time, Next.js raises an error. Otherwise, the prerendering step automatically extracts the static HTML shell.
For uncached data access, add [`use cache`](/docs/app/api-reference/directives/use-cache) as close to the data access as possible with a long [`cacheLife`](/docs/app/api-reference/functions/cacheLife) like `'max'` to maintain cached behavior. If needed, add it at the top of the page or layout.
For runtime data access (`cookies()`, `headers()`, etc.), errors will direct you to wrap it with `<Suspense>`. Since you started by using `force-static`, you must remove the runtime data access to prevent any request time work.
```tsx filename="app/page.tsx" switcher
// Before
export const dynamic = 'force-static'
export default async function Page() {
const data = await fetch('https://api.example.com/data')
return <div>...</div>
}
```
```jsx filename="app/page.js" switcher
// Before
export const dynamic = 'force-static'
export default async function Page() {
const data = await fetch('https://api.example.com/data')
return <div>...</div>
}
```
```tsx filename="app/page.tsx" switcher
import { cacheLife } from 'next/cache'
// After - Use 'use cache' instead
export default async function Page() {
'use cache'
cacheLife('max')
const data = await fetch('https://api.example.com/data')
return <div>...</div>
}
```
```jsx filename="app/page.js" switcher
import { cacheLife } from 'next/cache'
// After - Use 'use cache' instead
export default async function Page() {
'use cache'
cacheLife('max')
const data = await fetch('https://api.example.com/data')
return <div>...</div>
}
```
## `revalidate`
**Replace with `cacheLife`.** Use the `cacheLife` function to define cache duration instead of the route segment config.
```tsx filename="app/page.tsx" switcher
// Before
export const revalidate = 3600 // 1 hour
export default async function Page() {
return <div>...</div>
}
```
```jsx filename="app/page.js" switcher
// Before
export const revalidate = 3600 // 1 hour
export default async function Page() {
return <div>...</div>
}
```
```tsx filename="app/page.tsx" switcher
// After - Use cacheLife
import { cacheLife } from 'next/cache'
export default async function Page() {
'use cache'
cacheLife('hours')
return <div>...</div>
}
```
```jsx filename="app/page.js" switcher
// After - Use cacheLife
import { cacheLife } from 'next/cache'
export default async function Page() {
'use cache'
cacheLife('hours')
return <div>...</div>
}
```
## `fetchCache`
**Not needed.** With `use cache`, all data fetching within a cached scope is automatically cached, making `fetchCache` unnecessary.
```tsx filename="app/page.tsx" switcher
// Before
export const fetchCache = 'force-cache'
```
```jsx filename="app/page.js" switcher
// Before
export const fetchCache = 'force-cache'
```
```tsx filename="app/page.tsx" switcher
// After - Use 'use cache' to control caching behavior
export default async function Page() {
'use cache'
// All fetches here are cached
return <div>...</div>
}
```
```jsx filename="app/page.js" switcher
// After - Use 'use cache' to control caching behavior
export default async function Page() {
'use cache'
// All fetches here are cached
return <div>...</div>
}
```
## `runtime = 'edge'`
**Not supported.** Cache Components requires the Node.js runtime. Switch to the Node.js runtime (the default) by removing the `runtime = 'edge'` export. If you need edge behavior for specific routes, use [Proxy](/docs/app/api-reference/file-conventions/proxy) instead.
@@ -0,0 +1,970 @@
---
title: How to migrate from Pages to the App Router
nav_title: App Router
description: Learn how to upgrade your existing Next.js application from the Pages Router to the App Router.
---
This guide will help you:
- [Update your Next.js application from version 12 to version 13](#nextjs-version)
- [Upgrade features that work in both the `pages` and the `app` directories](#upgrading-new-features)
- [Incrementally migrate your existing application from `pages` to `app`](#migrating-from-pages-to-app)
## Upgrading
### Node.js Version
The minimum Node.js version is now **v18.17**. See the [Node.js documentation](https://nodejs.org/docs/latest-v18.x/api/) for more information.
### Next.js Version
To update to Next.js version 13, run the following command using your preferred package manager:
```bash package="pnpm"
pnpm add next@latest react@latest react-dom@latest
```
```bash package="npm"
npm install next@latest react@latest react-dom@latest
```
```bash package="yarn"
yarn add next@latest react@latest react-dom@latest
```
```bash package="bun"
bun add next@latest react@latest react-dom@latest
```
### ESLint Version
If you're using ESLint, you need to upgrade your ESLint version:
```bash package="pnpm"
pnpm add -D eslint-config-next@latest
```
```bash package="npm"
npm install -D eslint-config-next@latest
```
```bash package="yarn"
yarn add -D eslint-config-next@latest
```
```bash package="bun"
bun add -D eslint-config-next@latest
```
> **Good to know**: You may need to restart the ESLint server in VS Code for the ESLint changes to take effect. Open the Command Palette (`cmd+shift+p` on Mac; `ctrl+shift+p` on Windows) and search for `ESLint: Restart ESLint Server`.
## Next Steps
After you've updated, see the following sections for next steps:
- [Upgrade new features](#upgrading-new-features): A guide to help you upgrade to new features such as the improved Image and Link Components.
- [Migrate from the `pages` to `app` directory](#migrating-from-pages-to-app): A step-by-step guide to help you incrementally migrate from the `pages` to the `app` directory.
## Upgrading New Features
Next.js 13 introduced the new [App Router](/docs/app) with new features and conventions. The new Router is available in the `app` directory and co-exists with the `pages` directory.
Upgrading to Next.js 13 does **not** require using the App Router. You can continue using `pages` with new features that work in both directories, such as the updated [Image component](#image-component), [Link component](#link-component), [Script component](#script-component), and [Font optimization](#font-optimization).
### `<Image/>` Component
Next.js 12 introduced new improvements to the Image Component with a temporary import: `next/future/image`. These improvements included less client-side JavaScript, easier ways to extend and style images, better accessibility, and native browser lazy loading.
In version 13, this new behavior is now the default for `next/image`.
There are two codemods to help you migrate to the new Image Component:
- [**`next-image-to-legacy-image` codemod**](/docs/app/guides/upgrading/codemods#next-image-to-legacy-image): Safely and automatically renames `next/image` imports to `next/legacy/image`. Existing components will maintain the same behavior.
- [**`next-image-experimental` codemod**](/docs/app/guides/upgrading/codemods#next-image-experimental): Dangerously adds inline styles and removes unused props. This will change the behavior of existing components to match the new defaults. To use this codemod, you need to run the `next-image-to-legacy-image` codemod first.
### `<Link>` Component
The [`<Link>` Component](/docs/app/api-reference/components/link) no longer requires manually adding an `<a>` tag as a child. This behavior was added as an experimental option in [version 12.2](https://nextjs.org/blog/next-12-2) and is now the default. In Next.js 13, `<Link>` always renders `<a>` and allows you to forward props to the underlying tag.
For example:
```jsx
import Link from 'next/link'
// Next.js 12: `<a>` has to be nested otherwise it's excluded
<Link href="/about">
<a>About</a>
</Link>
// Next.js 13: `<Link>` always renders `<a>` under the hood
<Link href="/about">
About
</Link>
```
To upgrade your links to Next.js 13, you can use the [`new-link` codemod](/docs/app/guides/upgrading/codemods#new-link).
### `<Script>` Component
The behavior of [`next/script`](/docs/app/api-reference/components/script) has been updated to support both `pages` and `app`, but some changes need to be made to ensure a smooth migration:
- Move any `beforeInteractive` scripts you previously included in `_document.js` to the root layout file (`app/layout.tsx`).
- The experimental `worker` strategy does not yet work in `app` and scripts denoted with this strategy will either have to be removed or modified to use a different strategy (e.g. `lazyOnload`).
- `onLoad`, `onReady`, and `onError` handlers will not work in Server Components so make sure to move them to a [Client Component](/docs/app/getting-started/server-and-client-components) or remove them altogether.
### Font Optimization
Previously, Next.js helped you optimize fonts by [inlining font CSS](/docs/app/api-reference/components/font). Version 13 introduces the new [`next/font`](/docs/app/api-reference/components/font) module which gives you the ability to customize your font loading experience while still ensuring great performance and privacy. `next/font` is supported in both the `pages` and `app` directories.
While [inlining CSS](/docs/app/api-reference/components/font) still works in `pages`, it does not work in `app`. You should use [`next/font`](/docs/app/api-reference/components/font) instead.
See the [Font Optimization](/docs/app/api-reference/components/font) page to learn how to use `next/font`.
## Migrating from `pages` to `app`
> **🎥 Watch:** Learn how to incrementally adopt the App Router → [YouTube (16 minutes)](https://www.youtube.com/watch?v=YQMSietiFm0).
Moving to the App Router may be the first time using React features that Next.js builds on top of such as Server Components, Suspense, and more. When combined with new Next.js features such as [special files](/docs/app/api-reference/file-conventions) and [layouts](/docs/app/api-reference/file-conventions/layout), migration means new concepts, mental models, and behavioral changes to learn.
We recommend reducing the combined complexity of these updates by breaking down your migration into smaller steps. The `app` directory is intentionally designed to work simultaneously with the `pages` directory to allow for incremental page-by-page migration.
- The `app` directory supports nested routes _and_ layouts. [Learn more](/docs/app/getting-started/layouts-and-pages).
- Use nested folders to define routes and a special `page.js` file to make a route segment publicly accessible. [Learn more](#step-4-migrating-pages).
- [Special file conventions](/docs/app/api-reference/file-conventions) are used to create UI for each route segment. The most common special files are `page.js` and `layout.js`.
- Use `page.js` to define UI unique to a route.
- Use `layout.js` to define UI that is shared across multiple routes.
- `.js`, `.jsx`, or `.tsx` file extensions can be used for special files.
- You can colocate other files inside the `app` directory such as components, styles, tests, and more. [Learn more](/docs/app).
- Data fetching functions like `getServerSideProps` and `getStaticProps` have been replaced with [a new API](/docs/app/getting-started/fetching-data) inside `app`. `getStaticPaths` has been replaced with [`generateStaticParams`](/docs/app/api-reference/functions/generate-static-params).
- `pages/_app.js` and `pages/_document.js` have been replaced with a single `app/layout.js` root layout. [Learn more](/docs/app/api-reference/file-conventions/layout#root-layout).
- `pages/_error.js` has been replaced with more granular `error.js` special files. [Learn more](/docs/app/getting-started/error-handling).
- `pages/404.js` has been replaced with the [`not-found.js`](/docs/app/api-reference/file-conventions/not-found) file.
- `pages/api/*` API Routes have been replaced with the [`route.js`](/docs/app/api-reference/file-conventions/route) (Route Handler) special file.
### Step 1: Creating the `app` directory
Update to the latest Next.js version (requires 13.4 or greater):
```bash package="pnpm"
pnpm add next@latest
```
```bash package="npm"
npm install next@latest
```
```bash package="yarn"
yarn add next@latest
```
```bash package="bun"
bun add next@latest
```
Then, create a new `app` directory at the root of your project (or `src/` directory).
### Step 2: Creating a Root Layout
Create a new `app/layout.tsx` file inside the `app` directory. This is a [root layout](/docs/app/api-reference/file-conventions/layout#root-layout) that will apply to all routes inside `app`.
```tsx filename="app/layout.tsx" switcher
export default function RootLayout({
// Layouts must accept a children prop.
// This will be populated with nested layouts or pages
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}
```
```jsx filename="app/layout.js" switcher
export default function RootLayout({
// Layouts must accept a children prop.
// This will be populated with nested layouts or pages
children,
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}
```
- The `app` directory **must** include a root layout.
- The root layout must define `<html>`, and `<body>` tags since Next.js does not automatically create them
- The root layout replaces the `pages/_app.tsx` and `pages/_document.tsx` files.
- `.js`, `.jsx`, or `.tsx` extensions can be used for layout files.
To manage `<head>` HTML elements, you can use the [built-in SEO support](/docs/app/getting-started/metadata-and-og-images):
```tsx filename="app/layout.tsx" switcher
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: 'Home',
description: 'Welcome to Next.js',
}
```
```jsx filename="app/layout.js" switcher
export const metadata = {
title: 'Home',
description: 'Welcome to Next.js',
}
```
#### Migrating `_document.js` and `_app.js`
If you have an existing `_app` or `_document` file, you can copy the contents (e.g. global styles) to the root layout (`app/layout.tsx`). Styles in `app/layout.tsx` will _not_ apply to `pages/*`. You should keep `_app`/`_document` while migrating to prevent your `pages/*` routes from breaking. Once fully migrated, you can then safely delete them.
If you are using any React Context providers, they will need to be moved to a [Client Component](/docs/app/getting-started/server-and-client-components).
#### Migrating the `getLayout()` pattern to Layouts (Optional)
Next.js recommended adding a [property to Page components](/docs/pages/building-your-application/routing/pages-and-layouts#layout-pattern) to achieve per-page layouts in the `pages` directory. This pattern can be replaced with native support for [nested layouts](/docs/app/api-reference/file-conventions/layout) in the `app` directory.
<details>
<summary>See before and after example</summary>
**Before**
```jsx filename="components/DashboardLayout.js"
export default function DashboardLayout({ children }) {
return (
<div>
<h2>My Dashboard</h2>
{children}
</div>
)
}
```
```jsx filename="pages/dashboard/index.js"
import DashboardLayout from '../components/DashboardLayout'
export default function Page() {
return <p>My Page</p>
}
Page.getLayout = function getLayout(page) {
return <DashboardLayout>{page}</DashboardLayout>
}
```
**After**
- Remove the `Page.getLayout` property from `pages/dashboard/index.js` and follow the [steps for migrating pages](#step-4-migrating-pages) to the `app` directory.
```jsx filename="app/dashboard/page.js"
export default function Page() {
return <p>My Page</p>
}
```
- Move the contents of `DashboardLayout` into a new [Client Component](/docs/app/getting-started/server-and-client-components) to retain `pages` directory behavior.
```jsx filename="app/dashboard/DashboardLayout.js"
'use client' // this directive should be at top of the file, before any imports.
// This is a Client Component
export default function DashboardLayout({ children }) {
return (
<div>
<h2>My Dashboard</h2>
{children}
</div>
)
}
```
- Import the `DashboardLayout` into a new `layout.js` file inside the `app` directory.
```jsx filename="app/dashboard/layout.js"
import DashboardLayout from './DashboardLayout'
// This is a Server Component
export default function Layout({ children }) {
return <DashboardLayout>{children}</DashboardLayout>
}
```
- You can incrementally move non-interactive parts of `DashboardLayout.js` (Client Component) into `layout.js` (Server Component) to reduce the amount of component JavaScript you send to the client.
</details>
### Step 3: Migrating `next/head`
In the `pages` directory, the `next/head` React component is used to manage `<head>` HTML elements such as `title` and `meta` . In the `app` directory, `next/head` is replaced with the new [built-in SEO support](/docs/app/getting-started/metadata-and-og-images).
**Before:**
```tsx filename="pages/index.tsx" switcher
import Head from 'next/head'
export default function Page() {
return (
<>
<Head>
<title>My page title</title>
</Head>
</>
)
}
```
```jsx filename="pages/index.js" switcher
import Head from 'next/head'
export default function Page() {
return (
<>
<Head>
<title>My page title</title>
</Head>
</>
)
}
```
**After:**
```tsx filename="app/page.tsx" switcher
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: 'My Page Title',
}
export default function Page() {
return '...'
}
```
```jsx filename="app/page.js" switcher
export const metadata = {
title: 'My Page Title',
}
export default function Page() {
return '...'
}
```
[See all metadata options](/docs/app/api-reference/functions/generate-metadata).
### Step 4: Migrating Pages
- Pages in the [`app` directory](/docs/app) are [Server Components](/docs/app/getting-started/server-and-client-components) by default. This is different from the `pages` directory where pages are [Client Components](/docs/app/getting-started/server-and-client-components).
- [Data fetching](/docs/app/getting-started/fetching-data) has changed in `app`. `getServerSideProps`, `getStaticProps` and `getInitialProps` have been replaced with a simpler API.
- The `app` directory uses nested folders to define routes and a special `page.js` file to make a route segment publicly accessible.
- | `pages` Directory | `app` Directory | Route |
| ----------------- | --------------------- | -------------- |
| `index.js` | `page.js` | `/` |
| `about.js` | `about/page.js` | `/about` |
| `blog/[slug].js` | `blog/[slug]/page.js` | `/blog/post-1` |
We recommend breaking down the migration of a page into two main steps:
- Step 1: Move the default exported Page Component into a new Client Component.
- Step 2: Import the new Client Component into a new `page.js` file inside the `app` directory.
> **Good to know**: This is the easiest migration path because it has the most comparable behavior to the `pages` directory.
**Step 1: Create a new Client Component**
- Create a new separate file inside the `app` directory (i.e. `app/home-page.tsx` or similar) that exports a Client Component. To define Client Components, add the `'use client'` directive to the top of the file (before any imports).
- Similar to the Pages Router, there is an [optimization step](/docs/app/getting-started/server-and-client-components#on-the-client-first-load) to prerender Client Components to static HTML on the initial page load.
- Move the default exported page component from `pages/index.js` to `app/home-page.tsx`.
```tsx filename="app/home-page.tsx" switcher
'use client'
// This is a Client Component (same as components in the `pages` directory)
// It receives data as props, has access to state and effects, and is
// prerendered on the server during the initial page load.
export default function HomePage({ recentPosts }) {
return (
<div>
{recentPosts.map((post) => (
<div key={post.id}>{post.title}</div>
))}
</div>
)
}
```
```jsx filename="app/home-page.js" switcher
'use client'
// This is a Client Component. It receives data as props and
// has access to state and effects just like Page components
// in the `pages` directory.
export default function HomePage({ recentPosts }) {
return (
<div>
{recentPosts.map((post) => (
<div key={post.id}>{post.title}</div>
))}
</div>
)
}
```
**Step 2: Create a new page**
- Create a new `app/page.tsx` file inside the `app` directory. This is a Server Component by default.
- Import the `home-page.tsx` Client Component into the page.
- If you were fetching data in `pages/index.js`, move the data fetching logic directly into the Server Component using the new [data fetching APIs](/docs/app/getting-started/fetching-data). See the [data fetching upgrade guide](#step-6-migrating-data-fetching-methods) for more details.
```tsx filename="app/page.tsx" switcher
// Import your Client Component
import HomePage from './home-page'
async function getPosts() {
const res = await fetch('https://...')
const posts = await res.json()
return posts
}
export default async function Page() {
// Fetch data directly in a Server Component
const recentPosts = await getPosts()
// Forward fetched data to your Client Component
return <HomePage recentPosts={recentPosts} />
}
```
```jsx filename="app/page.js" switcher
// Import your Client Component
import HomePage from './home-page'
async function getPosts() {
const res = await fetch('https://...')
const posts = await res.json()
return posts
}
export default async function Page() {
// Fetch data directly in a Server Component
const recentPosts = await getPosts()
// Forward fetched data to your Client Component
return <HomePage recentPosts={recentPosts} />
}
```
- If your previous page used `useRouter`, you'll need to update to the new routing hooks. [Learn more](/docs/app/api-reference/functions/use-router).
- Start your development server and visit [`http://localhost:3000`](http://localhost:3000). You should see your existing index route, now served through the app directory.
### Step 5: Migrating Routing Hooks
A new router has been added to support the new behavior in the `app` directory.
In `app`, you should use the three new hooks imported from `next/navigation`: [`useRouter()`](/docs/app/api-reference/functions/use-router), [`usePathname()`](/docs/app/api-reference/functions/use-pathname), and [`useSearchParams()`](/docs/app/api-reference/functions/use-search-params).
- The new `useRouter` hook is imported from `next/navigation` and has different behavior to the `useRouter` hook in `pages` which is imported from `next/router`.
- The [`useRouter` hook imported from `next/router`](/docs/pages/api-reference/functions/use-router) is not supported in the `app` directory but can continue to be used in the `pages` directory.
- The new `useRouter` does not return the `pathname` string. Use the separate `usePathname` hook instead.
- The new `useRouter` does not return the `query` object. Search parameters and dynamic route parameters are now separate. Use the `useSearchParams` and `useParams` hooks instead.
- You can use `useSearchParams` and `usePathname` together to listen to page changes. See the [Router Events](/docs/app/api-reference/functions/use-router#router-events) section for more details.
- These new hooks are only supported in Client Components. They cannot be used in Server Components.
```tsx filename="app/example-client-component.tsx" switcher
'use client'
import { useRouter, usePathname, useSearchParams } from 'next/navigation'
export default function ExampleClientComponent() {
const router = useRouter()
const pathname = usePathname()
const searchParams = useSearchParams()
// ...
}
```
```jsx filename="app/example-client-component.js" switcher
'use client'
import { useRouter, usePathname, useSearchParams } from 'next/navigation'
export default function ExampleClientComponent() {
const router = useRouter()
const pathname = usePathname()
const searchParams = useSearchParams()
// ...
}
```
In addition, the new `useRouter` hook has the following changes:
- `isFallback` has been removed because `fallback` has [been replaced](#replacing-fallback).
- The `locale`, `locales`, `defaultLocales`, `domainLocales` values have been removed because built-in i18n Next.js features are no longer necessary in the `app` directory. [Learn more about i18n](/docs/app/guides/internationalization).
- `basePath` has been removed. The alternative will not be part of `useRouter`. It has not yet been implemented.
- `asPath` has been removed because the concept of `as` has been removed from the new router.
- `isReady` has been removed because it is no longer necessary. During [prerendering](/docs/app/glossary#prerendering), any component that uses the [`useSearchParams()`](/docs/app/api-reference/functions/use-search-params) hook will skip the prerendering step and instead be rendered on the client at runtime.
- `route` has been removed. `usePathname` or `useSelectedLayoutSegments()` provide an alternative.
[View the `useRouter()` API reference](/docs/app/api-reference/functions/use-router).
#### Sharing components between `pages` and `app`
To keep components compatible between the `pages` and `app` routers, refer to the [`useRouter` hook from `next/compat/router`](/docs/pages/api-reference/functions/use-router#the-nextcompatrouter-export).
This is the `useRouter` hook from the `pages` directory, but intended to be used while sharing components between routers. Once you are ready to use it only on the `app` router, update to the new [`useRouter` from `next/navigation`](/docs/app/api-reference/functions/use-router).
### Step 6: Migrating Data Fetching Methods
The `pages` directory uses `getServerSideProps` and `getStaticProps` to fetch data for pages. Inside the `app` directory, these previous data fetching functions are replaced with a [simpler API](/docs/app/getting-started/fetching-data) built on top of `fetch()` and `async` React Server Components.
```tsx filename="app/page.tsx" switcher
export default async function Page() {
// This request should be cached until manually invalidated.
// Similar to `getStaticProps`.
// `force-cache` is the default and can be omitted.
const staticData = await fetch(`https://...`, { cache: 'force-cache' })
// This request should be refetched on every request.
// Similar to `getServerSideProps`.
const dynamicData = await fetch(`https://...`, { cache: 'no-store' })
// This request should be cached with a lifetime of 10 seconds.
// Similar to `getStaticProps` with the `revalidate` option.
const revalidatedData = await fetch(`https://...`, {
next: { revalidate: 10 },
})
return <div>...</div>
}
```
```jsx filename="app/page.js" switcher
export default async function Page() {
// This request should be cached until manually invalidated.
// Similar to `getStaticProps`.
// `force-cache` is the default and can be omitted.
const staticData = await fetch(`https://...`, { cache: 'force-cache' })
// This request should be refetched on every request.
// Similar to `getServerSideProps`.
const dynamicData = await fetch(`https://...`, { cache: 'no-store' })
// This request should be cached with a lifetime of 10 seconds.
// Similar to `getStaticProps` with the `revalidate` option.
const revalidatedData = await fetch(`https://...`, {
next: { revalidate: 10 },
})
return <div>...</div>
}
```
#### Server-side Rendering (`getServerSideProps`)
In the `pages` directory, `getServerSideProps` is used to fetch data on the server and forward props to the default exported React component in the file. The initial HTML for the page is prerendered from the server, followed by "hydrating" the page in the browser (making it interactive).
```jsx filename="pages/dashboard.js"
// `pages` directory
export async function getServerSideProps() {
const res = await fetch(`https://...`)
const projects = await res.json()
return { props: { projects } }
}
export default function Dashboard({ projects }) {
return (
<ul>
{projects.map((project) => (
<li key={project.id}>{project.name}</li>
))}
</ul>
)
}
```
In the App Router, we can colocate our data fetching inside our React components using [Server Components](/docs/app/getting-started/server-and-client-components). This allows us to send less JavaScript to the client, while maintaining the rendered HTML from the server.
By setting the `cache` option to `no-store`, we can indicate that the fetched data should [never be cached](/docs/app/getting-started/fetching-data). This is similar to `getServerSideProps` in the `pages` directory.
```tsx filename="app/dashboard/page.tsx" switcher
// `app` directory
// This function can be named anything
async function getProjects() {
const res = await fetch(`https://...`, { cache: 'no-store' })
const projects = await res.json()
return projects
}
export default async function Dashboard() {
const projects = await getProjects()
return (
<ul>
{projects.map((project) => (
<li key={project.id}>{project.name}</li>
))}
</ul>
)
}
```
```jsx filename="app/dashboard/page.js" switcher
// `app` directory
// This function can be named anything
async function getProjects() {
const res = await fetch(`https://...`, { cache: 'no-store' })
const projects = await res.json()
return projects
}
export default async function Dashboard() {
const projects = await getProjects()
return (
<ul>
{projects.map((project) => (
<li key={project.id}>{project.name}</li>
))}
</ul>
)
}
```
#### Accessing Request Object
In the `pages` directory, you can retrieve request-based data based on the Node.js HTTP API.
For example, you can retrieve the `req` object from `getServerSideProps` and use it to retrieve the request's cookies and headers.
```jsx filename="pages/index.js"
// `pages` directory
export async function getServerSideProps({ req, query }) {
const authHeader = req.getHeaders()['authorization'];
const theme = req.cookies['theme'];
return { props: { ... }}
}
export default function Page(props) {
return ...
}
```
The `app` directory exposes new read-only functions to retrieve request data:
- [`headers`](/docs/app/api-reference/functions/headers): Based on the Web Headers API, and can be used inside [Server Components](/docs/app/getting-started/server-and-client-components) to retrieve request headers.
- [`cookies`](/docs/app/api-reference/functions/cookies): Based on the Web Cookies API, and can be used inside [Server Components](/docs/app/getting-started/server-and-client-components) to retrieve cookies.
```tsx filename="app/page.tsx" switcher
// `app` directory
import { cookies, headers } from 'next/headers'
async function getData() {
const authHeader = (await headers()).get('authorization')
return '...'
}
export default async function Page() {
// You can use `cookies` or `headers` inside Server Components
// directly or in your data fetching function
const theme = (await cookies()).get('theme')
const data = await getData()
return '...'
}
```
```jsx filename="app/page.js" switcher
// `app` directory
import { cookies, headers } from 'next/headers'
async function getData() {
const authHeader = (await headers()).get('authorization')
return '...'
}
export default async function Page() {
// You can use `cookies` or `headers` inside Server Components
// directly or in your data fetching function
const theme = (await cookies()).get('theme')
const data = await getData()
return '...'
}
```
#### Static Site Generation (`getStaticProps`)
In the `pages` directory, the `getStaticProps` function is used to prerender a page at build time. This function can be used to fetch data from an external API or directly from a database, and pass this data down to the entire page as it's being generated during the build.
```jsx filename="pages/index.js"
// `pages` directory
export async function getStaticProps() {
const res = await fetch(`https://...`)
const projects = await res.json()
return { props: { projects } }
}
export default function Index({ projects }) {
return projects.map((project) => <div>{project.name}</div>)
}
```
In the `app` directory, data fetching with [`fetch()`](/docs/app/api-reference/functions/fetch) will default to `cache: 'force-cache'`, which will cache the request data until manually invalidated. This is similar to `getStaticProps` in the `pages` directory.
```jsx filename="app/page.js"
// `app` directory
// This function can be named anything
async function getProjects() {
const res = await fetch(`https://...`)
const projects = await res.json()
return projects
}
export default async function Index() {
const projects = await getProjects()
return projects.map((project) => <div>{project.name}</div>)
}
```
#### Dynamic paths (`getStaticPaths`)
In the `pages` directory, the `getStaticPaths` function is used to define the dynamic paths that should be prerendered at build time.
```jsx filename="pages/posts/[id].js"
// `pages` directory
import PostLayout from '@/components/post-layout'
export async function getStaticPaths() {
return {
paths: [{ params: { id: '1' } }, { params: { id: '2' } }],
}
}
export async function getStaticProps({ params }) {
const res = await fetch(`https://.../posts/${params.id}`)
const post = await res.json()
return { props: { post } }
}
export default function Post({ post }) {
return <PostLayout post={post} />
}
```
In the `app` directory, `getStaticPaths` is replaced with [`generateStaticParams`](/docs/app/api-reference/functions/generate-static-params).
[`generateStaticParams`](/docs/app/api-reference/functions/generate-static-params) behaves similarly to `getStaticPaths`, but has a simplified API for returning route parameters and can be used inside [layouts](/docs/app/api-reference/file-conventions/layout). The return shape of `generateStaticParams` is an array of segments instead of an array of nested `param` objects or a string of resolved paths.
```jsx filename="app/posts/[id]/page.js"
// `app` directory
import PostLayout from '@/components/post-layout'
export async function generateStaticParams() {
return [{ id: '1' }, { id: '2' }]
}
async function getPost(params) {
const res = await fetch(`https://.../posts/${(await params).id}`)
const post = await res.json()
return post
}
export default async function Post({ params }) {
const post = await getPost(params)
return <PostLayout post={post} />
}
```
Using the name `generateStaticParams` is more appropriate than `getStaticPaths` for the new model in the `app` directory. The `get` prefix is replaced with a more descriptive `generate`, which sits better alone now that `getStaticProps` and `getServerSideProps` are no longer necessary. The `Paths` suffix is replaced by `Params`, which is more appropriate for nested routing with multiple dynamic segments.
---
#### Replacing `fallback`
In the `pages` directory, the `fallback` property returned from `getStaticPaths` is used to define the behavior of a page that isn't prerendered at build time. This property can be set to `true` to show a fallback page while the page is being generated, `false` to show a 404 page, or `blocking` to generate the page at request time.
```jsx filename="pages/posts/[id].js"
// `pages` directory
export async function getStaticPaths() {
return {
paths: [],
fallback: 'blocking'
};
}
export async function getStaticProps({ params }) {
...
}
export default function Post({ post }) {
return ...
}
```
In the `app` directory the [`config.dynamicParams` property](/docs/app/api-reference/file-conventions/route-segment-config/dynamicParams) controls how params outside of [`generateStaticParams`](/docs/app/api-reference/functions/generate-static-params) are handled:
- **`true`**: (default) Dynamic segments not included in `generateStaticParams` are generated on demand.
- **`false`**: Dynamic segments not included in `generateStaticParams` will return a 404.
This replaces the `fallback: true | false | 'blocking'` option of `getStaticPaths` in the `pages` directory. The `fallback: 'blocking'` option is not included in `dynamicParams` because the difference between `'blocking'` and `true` is negligible with streaming.
```jsx filename="app/posts/[id]/page.js"
// `app` directory
export const dynamicParams = true;
export async function generateStaticParams() {
return [...]
}
async function getPost(params) {
...
}
export default async function Post({ params }) {
const post = await getPost(params);
return ...
}
```
With [`dynamicParams`](/docs/app/api-reference/file-conventions/route-segment-config/dynamicParams) set to `true` (the default), when a route segment is requested that hasn't been generated, it will be server-rendered and cached.
#### Incremental Static Regeneration (`getStaticProps` with `revalidate`)
In the `pages` directory, the `getStaticProps` function allows you to add a `revalidate` field to automatically regenerate a page after a certain amount of time.
```jsx filename="pages/index.js"
// `pages` directory
export async function getStaticProps() {
const res = await fetch(`https://.../posts`)
const posts = await res.json()
return {
props: { posts },
revalidate: 60,
}
}
export default function Index({ posts }) {
return (
<Layout>
<PostList posts={posts} />
</Layout>
)
}
```
In the `app` directory, data fetching with [`fetch()`](/docs/app/api-reference/functions/fetch) can use `revalidate`, which will cache the request for the specified amount of seconds.
```jsx filename="app/page.js"
// `app` directory
async function getPosts() {
const res = await fetch(`https://.../posts`, { next: { revalidate: 60 } })
const data = await res.json()
return data.posts
}
export default async function PostList() {
const posts = await getPosts()
return posts.map((post) => <div>{post.name}</div>)
}
```
#### API Routes
API Routes continue to work in the `pages/api` directory without any changes. However, they have been replaced by [Route Handlers](/docs/app/api-reference/file-conventions/route) in the `app` directory.
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="app/api/route.ts" switcher
export async function GET(request: Request) {}
```
```js filename="app/api/route.js" switcher
export async function GET(request) {}
```
> **Good to know**: If you previously used API routes to call an external API from the client, you can now use [Server Components](/docs/app/getting-started/server-and-client-components) instead to securely fetch data. Learn more about [data fetching](/docs/app/getting-started/fetching-data).
#### Single-Page Applications
If you are also migrating to Next.js from a Single-Page Application (SPA) at the same time, see our [documentation](/docs/app/guides/single-page-applications) to learn more.
### Step 7: Styling
In the `pages` directory, global stylesheets are restricted to only `pages/_app.js`. With the `app` directory, this restriction has been lifted. Global styles can be added to any layout, page, or component.
- [CSS Modules](/docs/app/getting-started/css#css-modules)
- [Tailwind CSS](/docs/app/getting-started/css#tailwind-css)
- [Global Styles](/docs/app/getting-started/css#global-css)
- [CSS-in-JS](/docs/app/guides/css-in-js)
- [External Stylesheets](/docs/app/getting-started/css#external-stylesheets)
- [Sass](/docs/app/guides/sass)
#### Tailwind CSS
If you're using Tailwind CSS, you'll need to add the `app` directory to your `tailwind.config.js` file:
```js filename="tailwind.config.js"
module.exports = {
content: [
'./app/**/*.{js,ts,jsx,tsx,mdx}', // <-- Add this line
'./pages/**/*.{js,ts,jsx,tsx,mdx}',
'./components/**/*.{js,ts,jsx,tsx,mdx}',
],
}
```
You'll also need to import your global styles in your `app/layout.js` file:
```jsx filename="app/layout.js"
import '../styles/globals.css'
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}
```
Learn more about [styling with Tailwind CSS](/docs/app/getting-started/css#tailwind-css)
## Using App Router together with Pages Router
When navigating between routes served by the different Next.js routers, there will be a hard navigation. Automatic link prefetching with `next/link` will not prefetch across routers.
Instead, you can [optimize navigations](https://vercel.com/guides/optimizing-hard-navigations) between App Router and Pages Router to retain the prefetched and fast page transitions. [Learn more](https://vercel.com/guides/optimizing-hard-navigations).
## Codemods
Next.js provides Codemod transformations to help upgrade your codebase when a feature is deprecated. See [Codemods](/docs/app/guides/upgrading/codemods) for more information.
@@ -0,0 +1,611 @@
---
title: How to migrate from Create React App to Next.js
nav_title: Create React App
description: Learn how to migrate your existing React application from Create React App to Next.js.
---
This guide will help you migrate an existing Create React App (CRA) site to Next.js.
## Why Switch?
There are several reasons why you might want to switch from Create React App to Next.js:
### Slow initial page loading time
Create React App uses purely client-side rendering. Client-side only applications, also known as [single-page applications (SPAs)](/docs/app/guides/single-page-applications), often experience slow initial page loading time. This happens due to a couple of reasons:
1. The browser needs to wait for the React code and your entire application bundle to download and run before your code is able to send requests to load data.
2. Your application code grows with every new feature and dependency you add.
### No automatic code splitting
The previous issue of slow loading times can be somewhat mitigated with code splitting. However, if you try to do code splitting manually, you can inadvertently introduce network waterfalls. Next.js provides automatic code splitting and tree-shaking built into its router and build pipeline.
### Network waterfalls
A common cause of poor performance occurs when applications make sequential client-server requests to fetch data. One pattern for data fetching in a [SPA](/docs/app/guides/single-page-applications) is to render a placeholder, and then fetch data after the component has mounted. Unfortunately, a child component can only begin fetching data after its parent has finished loading its own data, resulting in a “waterfall” of requests.
While client-side data fetching is supported in Next.js, Next.js also lets you move data fetching to the server. This often eliminates client-server waterfalls altogether.
### Fast and intentional loading states
With built-in support for [streaming through React Suspense](/docs/app/getting-started/linking-and-navigating#streaming), you can define which parts of your UI load first and in what order, without creating network waterfalls.
This enables you to build pages that are faster to load and eliminate [layout shifts](https://vercel.com/blog/how-core-web-vitals-affect-seo).
### Choose the data fetching strategy
Depending on your needs, Next.js allows you to choose your data fetching strategy on a page or component-level basis. For example, you could fetch data from your CMS and render blog posts at build time (SSG) for quick load speeds, or fetch data at request time (SSR) when necessary.
### Proxy
[Next.js Proxy](/docs/app/api-reference/file-conventions/proxy) allows you to run code on the server before a request is completed. For instance, you can avoid a flash of unauthenticated content by redirecting a user to a login page in the proxy for authenticated-only pages. You can also use it for features like A/B testing, experimentation, and [internationalization](/docs/app/guides/internationalization).
### Built-in Optimizations
[Images](/docs/app/api-reference/components/image), [fonts](/docs/app/api-reference/components/font), and [third-party scripts](/docs/app/guides/scripts) often have a large impact on an applications performance. Next.js includes specialized components and APIs that automatically optimize them for you.
## Migration Steps
Our goal is to get a working Next.js application as quickly as possible so that you can then adopt Next.js features incrementally. To begin with, well treat your application as a purely client-side application ([SPA](/docs/app/guides/single-page-applications)) without immediately replacing your existing router. This reduces complexity and merge conflicts.
> **Note**: If you are using advanced CRA configurations such as a custom `homepage` field in your `package.json`, a custom service worker, or specific Babel/webpack tweaks, please see the **Additional Considerations** section at the end of this guide for tips on replicating or adapting these features in Next.js.
### Step 1: Install the Next.js Dependency
Install Next.js in your existing project:
```bash package="pnpm"
pnpm add next@latest
```
```bash package="npm"
npm install next@latest
```
```bash package="yarn"
yarn add next@latest
```
```bash package="bun"
bun add next@latest
```
### Step 2: Create the Next.js Configuration File
Create a `next.config.ts` at the root of your project (same level as your `package.json`). This file holds your [Next.js configuration options](/docs/app/api-reference/config/next-config-js).
```js filename="next.config.ts"
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
output: 'export', // Outputs a Single-Page Application (SPA)
distDir: 'build', // Changes the build output directory to `build`
}
export default nextConfig
```
> **Note**: Using `output: 'export'` means youre doing a static export. You will **not** have access to server-side features like SSR or APIs. You can remove this line to leverage Next.js server features.
### Step 3: Create the Root Layout
A Next.js [App Router](/docs/app) application must include a [root layout](/docs/app/api-reference/file-conventions/layout#root-layout) file, which is a [React Server Component](/docs/app/getting-started/server-and-client-components) that will wrap all your pages.
The closest equivalent of the root layout file in a CRA application is `public/index.html`, which includes your `<html>`, `<head>`, and `<body>` tags.
1. Create a new `app` directory inside your `src` folder (or at your project root if you prefer `app` at the root).
2. Inside the `app` directory, create a `layout.tsx` (or `layout.js`) file:
```tsx filename="app/layout.tsx" switcher
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return '...'
}
```
```jsx filename="app/layout.js" switcher
export default function RootLayout({ children }) {
return '...'
}
```
Now copy the content of your old `index.html` into this `<RootLayout>` component. Replace `body div#root` (and `body noscript`) with `<div id="root">{children}</div>`.
```tsx filename="app/layout.tsx" switcher
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<head>
<meta charSet="UTF-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>React App</title>
<meta name="description" content="Web site created..." />
</head>
<body>
<div id="root">{children}</div>
</body>
</html>
)
}
```
```jsx filename="app/layout.js" switcher
export default function RootLayout({ children }) {
return (
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>React App</title>
<meta name="description" content="Web site created..." />
</head>
<body>
<div id="root">{children}</div>
</body>
</html>
)
}
```
> **Good to know**: Next.js ignores CRAs `public/manifest.json`, additional iconography, and [testing configuration](/docs/app/guides/testing) by default. If you need these, Next.js has support with its [Metadata API](/docs/app/getting-started/metadata-and-og-images) and [Testing](/docs/app/guides/testing) setup.
### Step 4: Metadata
Next.js automatically includes the `<meta charset="UTF-8" />` and `<meta name="viewport" content="width=device-width, initial-scale=1" />` tags, so you can remove them from `<head>`:
```tsx filename="app/layout.tsx" switcher
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<head>
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<title>React App</title>
<meta name="description" content="Web site created..." />
</head>
<body>
<div id="root">{children}</div>
</body>
</html>
)
}
```
```jsx filename="app/layout.js" switcher
export default function RootLayout({ children }) {
return (
<html lang="en">
<head>
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<title>React App</title>
<meta name="description" content="Web site created..." />
</head>
<body>
<div id="root">{children}</div>
</body>
</html>
)
}
```
Any [metadata files](/docs/app/getting-started/metadata-and-og-images#file-based-metadata) such as `favicon.ico`, `icon.png`, `robots.txt` are automatically added to the application `<head>` tag as long as you have them placed into the top level of the `app` directory. After moving [all supported files](/docs/app/getting-started/metadata-and-og-images#file-based-metadata) into the `app` directory you can safely delete their `<link>` tags:
```tsx filename="app/layout.tsx" switcher
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<head>
<title>React App</title>
<meta name="description" content="Web site created..." />
</head>
<body>
<div id="root">{children}</div>
</body>
</html>
)
}
```
```jsx filename="app/layout.js" switcher
export default function RootLayout({ children }) {
return (
<html lang="en">
<head>
<title>React App</title>
<meta name="description" content="Web site created..." />
</head>
<body>
<div id="root">{children}</div>
</body>
</html>
)
}
```
Finally, Next.js can manage your last `<head>` tags with the [Metadata API](/docs/app/getting-started/metadata-and-og-images). Move your final metadata info into an exported [`metadata` object](/docs/app/api-reference/functions/generate-metadata#metadata-object):
```tsx filename="app/layout.tsx" switcher
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: 'React App',
description: 'Web site created with Next.js.',
}
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>
<div id="root">{children}</div>
</body>
</html>
)
}
```
```jsx filename="app/layout.js" switcher
export const metadata = {
title: 'React App',
description: 'Web site created with Next.js.',
}
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
<div id="root">{children}</div>
</body>
</html>
)
}
```
With the above changes, you shifted from declaring everything in your `index.html` to using Next.js' convention-based approach built into the framework ([Metadata API](/docs/app/getting-started/metadata-and-og-images)). This approach enables you to more easily improve your SEO and web shareability of your pages.
### Step 5: Styles
Like CRA, Next.js supports [CSS Modules](/docs/app/getting-started/css#css-modules) out of the box. It also supports [global CSS imports](/docs/app/getting-started/css#global-css).
If you have a global CSS file, import it into your `app/layout.tsx`:
```tsx filename="app/layout.tsx" switcher
import '../index.css'
export const metadata = {
title: 'React App',
description: 'Web site created with Next.js.',
}
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>
<div id="root">{children}</div>
</body>
</html>
)
}
```
If you're using Tailwind CSS, see our [installation docs](/docs/app/getting-started/css#tailwind-css).
### Step 6: Create the Entrypoint Page
Create React App uses `src/index.tsx` (or `index.js`) as the entry point. In Next.js (App Router), each folder inside the `app` directory corresponds to a route, and each folder should have a `page.tsx`.
Since we want to keep the app as an SPA for now and intercept **all** routes, well use an [optional catch-all route](/docs/app/api-reference/file-conventions/dynamic-routes#optional-catch-all-segments).
1. **Create a `[[...slug]]` directory inside `app`.**
```bash
app
┣ [[...slug]]
┃ ┗ page.tsx
┣ layout.tsx
```
2. **Add the following to `page.tsx`**:
```tsx filename="app/[[...slug]]/page.tsx" switcher
export function generateStaticParams() {
return [{ slug: [''] }]
}
export default function Page() {
return '...' // We'll update this
}
```
```jsx filename="app/[[...slug]]/page.js" switcher
export function generateStaticParams() {
return [{ slug: [''] }]
}
export default function Page() {
return '...' // We'll update this
}
```
This tells Next.js to generate a single route for the empty slug (`/`), effectively mapping **all** routes to the same page. This page is a [Server Component](/docs/app/getting-started/server-and-client-components), prerendered into static HTML.
### Step 7: Add a Client-Only Entrypoint
Next, well embed your CRAs root App component inside a [Client Component](/docs/app/getting-started/server-and-client-components) so that all logic remains client-side. If this is your first time using Next.js, it's worth knowing that clients components (by default) are still prerendered on the server. You can think about them as having the additional capability of running client-side JavaScript.
Create a `client.tsx` (or `client.js`) in `app/[[...slug]]/`:
```tsx filename="app/[[...slug]]/client.tsx" switcher
'use client'
import dynamic from 'next/dynamic'
const App = dynamic(() => import('../../App'), { ssr: false })
export function ClientOnly() {
return <App />
}
```
```jsx filename="app/[[...slug]]/client.js" switcher
'use client'
import dynamic from 'next/dynamic'
const App = dynamic(() => import('../../App'), { ssr: false })
export function ClientOnly() {
return <App />
}
```
- The `'use client'` directive makes this file a **Client Component**.
- The `dynamic` import with `ssr: false` disables server-side rendering for the `<App />` component, making it truly client-only (SPA).
Now update your `page.tsx` (or `page.js`) to use your new component:
```tsx filename="app/[[...slug]]/page.tsx" switcher
import { ClientOnly } from './client'
export function generateStaticParams() {
return [{ slug: [''] }]
}
export default function Page() {
return <ClientOnly />
}
```
```jsx filename="app/[[...slug]]/page.js" switcher
import { ClientOnly } from './client'
export function generateStaticParams() {
return [{ slug: [''] }]
}
export default function Page() {
return <ClientOnly />
}
```
### Step 8: Update Static Image Imports
In CRA, importing an image file returns its public URL as a string:
```tsx
import image from './img.png'
export default function App() {
return <img src={image} />
}
```
With Next.js, static image imports return an object. The object can then be used directly with the Next.js [`<Image>` component](/docs/app/api-reference/components/image), or you can use the object's `src` property with your existing `<img>` tag.
The `<Image>` component has the added benefits of [automatic image optimization](/docs/app/api-reference/components/image). The `<Image>` component automatically sets the `width` and `height` attributes of the resulting `<img>` based on the image's dimensions. This prevents layout shifts when the image loads. However, this can cause issues if your app contains images with only one of their dimensions being styled without the other styled to `auto`. When not styled to `auto`, the dimension will default to the `<img>` dimension attribute's value, which can cause the image to appear distorted.
Keeping the `<img>` tag will reduce the amount of changes in your application and prevent the above issues. You can then optionally later migrate to the `<Image>` component to take advantage of optimizing images by [configuring a loader](/docs/app/api-reference/components/image#loader), or moving to the default Next.js server which has automatic image optimization.
**Convert absolute import paths for images imported from `/public` into relative imports:**
```tsx
// Before
import logo from '/logo.png'
// After
import logo from '../public/logo.png'
```
**Pass the image `src` property instead of the whole image object to your `<img>` tag:**
```tsx
// Before
<img src={logo} />
// After
<img src={logo.src} />
```
Alternatively, you can reference the public URL for the image asset based on the filename. For example, `public/logo.png` will serve the image at `/logo.png` for your application, which would be the `src` value.
> **Warning:** If you're using TypeScript, you might encounter type errors when accessing the `src` property. To fix them, you need to add `next-env.d.ts` to the [`include` array](https://www.typescriptlang.org/tsconfig#include) of your `tsconfig.json` file. Next.js will automatically generate this file when you run your application on step 9.
### Step 9: Migrate Environment Variables
Next.js supports [environment variables](/docs/app/guides/environment-variables) similarly to CRA but **requires** a `NEXT_PUBLIC_` prefix for any variable you want to expose in the browser.
The main difference is the prefix used to expose environment variables on the client-side. Change all environment variables with the `REACT_APP_` prefix to `NEXT_PUBLIC_`.
### Step 10: Update Scripts in `package.json`
Update your `package.json` scripts to use Next.js commands. Also, add `.next` and `next-env.d.ts` to your `.gitignore`:
```json filename="package.json"
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "npx serve@latest ./build"
}
}
```
```txt filename=".gitignore"
# ...
.next
next-env.d.ts
```
Now you can run:
```bash package="pnpm"
pnpm dev
```
```bash package="npm"
npm run dev
```
```bash package="yarn"
yarn dev
```
```bash package="bun"
bun dev
```
Open [http://localhost:3000](http://localhost:3000). You should see your application now running on Next.js (in SPA mode).
### Step 11: Clean Up
You can now remove artifacts that are specific to Create React App:
- `public/index.html`
- `src/index.tsx`
- `src/react-app-env.d.ts`
- The `reportWebVitals` setup
- The `react-scripts` dependency (uninstall it from `package.json`)
## Additional Considerations
### Using a Custom `homepage` in CRA
If you used the `homepage` field in your CRA `package.json` to serve the app under a specific subpath, you can replicate that in Next.js using the [`basePath` configuration](/docs/app/api-reference/config/next-config-js/basePath) in `next.config.ts`:
```ts filename="next.config.ts"
import { NextConfig } from 'next'
const nextConfig: NextConfig = {
basePath: '/my-subpath',
// ...
}
export default nextConfig
```
### Handling a Custom `Service Worker`
If you used CRAs service worker (e.g., `serviceWorker.js` from `create-react-app`), you can learn how to create [Progressive Web Applications (PWAs)](/docs/app/guides/progressive-web-apps) with Next.js.
### Proxying API Requests
If your CRA app used the `proxy` field in `package.json` to forward requests to a backend server, you can replicate this with [Next.js rewrites](/docs/app/api-reference/config/next-config-js/rewrites) in `next.config.ts`:
```ts filename="next.config.ts"
import { NextConfig } from 'next'
const nextConfig: NextConfig = {
async rewrites() {
return [
{
source: '/api/:path*',
destination: 'https://your-backend.com/:path*',
},
]
},
}
```
### Custom Webpack
If you had a custom webpack or Babel configuration in CRA, you can extend Next.jss config in `next.config.ts`:
```ts filename="next.config.ts"
import { NextConfig } from 'next'
const nextConfig: NextConfig = {
webpack: (config, { isServer }) => {
// Modify the webpack config here
return config
},
}
export default nextConfig
```
> **Note**: This will require using Webpack by adding `--webpack` to your `dev` script.
### TypeScript Setup
Next.js automatically sets up TypeScript if you have a `tsconfig.json`. Make sure `next-env.d.ts` is listed in your `tsconfig.json` `include` array:
```json
{
"include": ["next-env.d.ts", "app/**/*", "src/**/*"]
}
```
## Bundler Compatibility
Create React App uses webpack for bundling. Next.js now defaults to [Turbopack](/docs/app/api-reference/config/next-config-js/turbopack) for faster local development:
```bash
next dev # Uses Turbopack by default
```
To use Webpack instead (similar to CRA):
```bash
next dev --webpack
```
You can still provide a [custom webpack configuration](/docs/app/api-reference/config/next-config-js/webpack) if you need to migrate advanced webpack settings from CRA.
## Next Steps
If everything worked, you now have a functioning Next.js application running as a single-page application. You arent yet leveraging Next.js features like server-side rendering or file-based routing, but you can now do so incrementally:
- **Migrate from React Router** to the [Next.js App Router](/docs/app) for:
- Automatic code splitting
- [Streaming server rendering](/docs/app/api-reference/file-conventions/loading)
- [React Server Components](/docs/app/getting-started/server-and-client-components)
- **Optimize images** with the [`<Image>` component](/docs/app/api-reference/components/image)
- **Optimize fonts** with [`next/font`](/docs/app/api-reference/components/font)
- **Optimize third-party scripts** with the [`<Script>` component](/docs/app/guides/scripts)
- **Enable ESLint** with Next.js [recommended rules](/docs/app/api-reference/config/eslint#setup-eslint)
> **Note**: Using a static export (`output: 'export'`) [does not currently support](https://github.com/vercel/next.js/issues/54393) the `useParams` hook or other server features. To use all Next.js features, remove `output: 'export'` from your `next.config.ts`.
+614
View File
@@ -0,0 +1,614 @@
---
title: How to migrate from Vite to Next.js
nav_title: Vite
description: Learn how to migrate your existing React application from Vite to Next.js.
---
This guide will help you migrate an existing Vite application to Next.js.
## Why Switch?
There are several reasons why you might want to switch from Vite to Next.js:
### Slow initial page loading time
If you have built your application with the [default Vite plugin for React](https://github.com/vitejs/vite-plugin-react/tree/main/packages/plugin-react), your application is a purely client-side application. Client-side only applications, also known as single-page applications (SPAs), often experience slow initial page loading time. This happens due to a couple of reasons:
1. The browser needs to wait for the React code and your entire application bundle to download and run before your code is able to send requests to load some data.
2. Your application code grows with every new feature and extra dependency you add.
### No automatic code splitting
The previous issue of slow loading times can be somewhat managed with code splitting. However, if you try to do code splitting manually, you'll often make performance worse. It's easy to inadvertently introduce network waterfalls when code-splitting manually. Next.js provides automatic code splitting built into its router.
### Network waterfalls
A common cause of poor performance occurs when applications make sequential client-server requests to fetch data. One common pattern for data fetching in an SPA is to initially render a placeholder, and then fetch data after the component has mounted. Unfortunately, this means that a child component that fetches data can't start fetching until the parent component has finished loading its own data.
While fetching data on the client is supported with Next.js, it also gives you the option to shift data fetching to the server, which can eliminate client-server waterfalls.
### Fast and intentional loading states
With built-in support for [streaming through React Suspense](/docs/app/getting-started/linking-and-navigating#streaming), you can be more intentional about which parts of your UI you want to load first and in what order without introducing network waterfalls.
This enables you to build pages that are faster to load and eliminate [layout shifts](https://vercel.com/blog/how-core-web-vitals-affect-seo).
### Choose the data fetching strategy
Depending on your needs, Next.js allows you to choose your data fetching strategy on a page and component basis. You can decide to fetch at build time, at request time on the server, or on the client. For example, you can fetch data from your CMS and render your blog posts at build time, which can then be efficiently cached on a CDN.
### Proxy
[Next.js Proxy](/docs/app/api-reference/file-conventions/proxy) allows you to run code on the server before a request is completed. This is especially useful to avoid having a flash of unauthenticated content when the user visits an authenticated-only page by redirecting the user to a login page. The proxy is also useful for experimentation and [internationalization](/docs/app/guides/internationalization).
### Built-in Optimizations
[Images](/docs/app/api-reference/components/image), [fonts](/docs/app/api-reference/components/font), and [third-party scripts](/docs/app/guides/scripts) often have significant impact on an application's performance. Next.js comes with built-in components that automatically optimize those for you.
## Migration Steps
Our goal with this migration is to get a working Next.js application as quickly as possible, so that
you can then adopt Next.js features incrementally. To begin with, we'll keep it as a purely
client-side application (SPA) without migrating your existing router. This helps minimize the
chances of encountering issues during the migration process and reduces merge conflicts.
### Step 1: Install the Next.js Dependency
The first thing you need to do is to install `next` as a dependency:
```bash package="pnpm"
pnpm add next@latest
```
```bash package="npm"
npm install next@latest
```
```bash package="yarn"
yarn add next@latest
```
```bash package="bun"
bun add next@latest
```
### Step 2: Create the Next.js Configuration File
Create a `next.config.mjs` at the root of your project. This file will hold your
[Next.js configuration options](/docs/app/api-reference/config/next-config-js).
```js filename="next.config.mjs"
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'export', // Outputs a Single-Page Application (SPA).
distDir: './dist', // Changes the build output directory to `./dist/`.
}
export default nextConfig
```
> **Good to know:** You can use either `.js` or `.mjs` for your Next.js configuration file.
### Step 3: Update TypeScript Configuration
If you're using TypeScript, you need to update your `tsconfig.json` file with the following changes
to make it compatible with Next.js. If you're not using TypeScript, you can skip this step.
1. Remove the [project reference](https://www.typescriptlang.org/tsconfig#references) to `tsconfig.node.json`
2. Add `./dist/types/**/*.ts` and `./next-env.d.ts` to the [`include` array](https://www.typescriptlang.org/tsconfig#include)
3. Add `./node_modules` to the [`exclude` array](https://www.typescriptlang.org/tsconfig#exclude)
4. Add `{ "name": "next" }` to the [`plugins` array in `compilerOptions`](https://www.typescriptlang.org/tsconfig#plugins): `"plugins": [{ "name": "next" }]`
5. Set [`esModuleInterop`](https://www.typescriptlang.org/tsconfig#esModuleInterop) to `true`: `"esModuleInterop": true`
6. Set [`jsx`](https://www.typescriptlang.org/tsconfig#jsx) to `react-jsx`: `"jsx": "react-jsx"`
7. Set [`allowJs`](https://www.typescriptlang.org/tsconfig#allowJs) to `true`: `"allowJs": true`
8. Set [`forceConsistentCasingInFileNames`](https://www.typescriptlang.org/tsconfig#forceConsistentCasingInFileNames) to `true`: `"forceConsistentCasingInFileNames": true`
9. Set [`incremental`](https://www.typescriptlang.org/tsconfig#incremental) to `true`: `"incremental": true`
Here's an example of a working `tsconfig.json` with those changes:
```json filename="tsconfig.json"
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"esModuleInterop": true,
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"allowJs": true,
"forceConsistentCasingInFileNames": true,
"incremental": true,
"plugins": [{ "name": "next" }]
},
"include": ["./src", "./dist/types/**/*.ts", "./next-env.d.ts"],
"exclude": ["./node_modules"]
}
```
You can find more information about configuring TypeScript on the
[Next.js docs](/docs/app/api-reference/config/typescript#ide-plugin).
### Step 4: Create the Root Layout
A Next.js [App Router](/docs/app) application must include a
[root layout](/docs/app/api-reference/file-conventions/layout#root-layout)
file, which is a [React Server Component](/docs/app/getting-started/server-and-client-components)
that will wrap all pages in your application. This file is defined at the top level of the `app`
directory.
The closest equivalent to the root layout file in a Vite application is the
[`index.html` file](https://vitejs.dev/guide/#index-html-and-project-root), which contains your
`<html>`, `<head>`, and `<body>` tags.
In this step, you'll convert your `index.html` file into a root layout file:
1. Create a new `app` directory in your `src` folder.
2. Create a new `layout.tsx` file inside that `app` directory:
```tsx filename="app/layout.tsx" switcher
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return '...'
}
```
```jsx filename="app/layout.js" switcher
export default function RootLayout({ children }) {
return '...'
}
```
> **Good to know**: `.js`, `.jsx`, or `.tsx` extensions can be used for Layout files.
3. Copy the content of your `index.html` file into the previously created `<RootLayout>` component while
replacing the `body.div#root` and `body.script` tags with `<div id="root">{children}</div>`:
```tsx filename="app/layout.tsx" switcher
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/icon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>My App</title>
<meta name="description" content="My App is a..." />
</head>
<body>
<div id="root">{children}</div>
</body>
</html>
)
}
```
```jsx filename="app/layout.js" switcher
export default function RootLayout({ children }) {
return (
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/icon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>My App</title>
<meta name="description" content="My App is a..." />
</head>
<body>
<div id="root">{children}</div>
</body>
</html>
)
}
```
4. Next.js already includes by default the
[meta charset](https://developer.mozilla.org/docs/Web/HTML/Element/meta#charset) and
[meta viewport](https://developer.mozilla.org/docs/Web/HTML/Viewport_meta_tag) tags, so you
can safely remove those from your `<head>`:
```tsx filename="app/layout.tsx" switcher
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<head>
<link rel="icon" type="image/svg+xml" href="/icon.svg" />
<title>My App</title>
<meta name="description" content="My App is a..." />
</head>
<body>
<div id="root">{children}</div>
</body>
</html>
)
}
```
```jsx filename="app/layout.js" switcher
export default function RootLayout({ children }) {
return (
<html lang="en">
<head>
<link rel="icon" type="image/svg+xml" href="/icon.svg" />
<title>My App</title>
<meta name="description" content="My App is a..." />
</head>
<body>
<div id="root">{children}</div>
</body>
</html>
)
}
```
5. Any [metadata files](/docs/app/getting-started/metadata-and-og-images#file-based-metadata)
such as `favicon.ico`, `icon.png`, `robots.txt` are automatically added to the application
`<head>` tag as long as you have them placed into the top level of the `app` directory. After
moving
[all supported files](/docs/app/getting-started/metadata-and-og-images#file-based-metadata)
into the `app` directory you can safely delete their `<link>` tags:
```tsx filename="app/layout.tsx" switcher
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<head>
<title>My App</title>
<meta name="description" content="My App is a..." />
</head>
<body>
<div id="root">{children}</div>
</body>
</html>
)
}
```
```jsx filename="app/layout.js" switcher
export default function RootLayout({ children }) {
return (
<html lang="en">
<head>
<title>My App</title>
<meta name="description" content="My App is a..." />
</head>
<body>
<div id="root">{children}</div>
</body>
</html>
)
}
```
6. Finally, Next.js can manage your last `<head>` tags with the
[Metadata API](/docs/app/getting-started/metadata-and-og-images). Move your final metadata
info into an exported
[`metadata` object](/docs/app/api-reference/functions/generate-metadata#metadata-object):
```tsx filename="app/layout.tsx" switcher
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: 'My App',
description: 'My App is a...',
}
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>
<div id="root">{children}</div>
</body>
</html>
)
}
```
```jsx filename="app/layout.js" switcher
export const metadata = {
title: 'My App',
description: 'My App is a...',
}
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
<div id="root">{children}</div>
</body>
</html>
)
}
```
With the above changes, you shifted from declaring everything in your `index.html` to using Next.js'
convention-based approach built into the framework
([Metadata API](/docs/app/getting-started/metadata-and-og-images)). This approach enables you
to more easily improve your SEO and web shareability of your pages.
### Step 5: Create the Entrypoint Page
On Next.js you declare an entrypoint for your application by creating a `page.tsx` file. The
closest equivalent of this file on Vite is your `main.tsx` file. In this step, youll set up the
entrypoint of your application.
1. **Create a `[[...slug]]` directory in your `app` directory.**
Since in this guide we're aiming first to set up our Next.js as an SPA (Single Page Application), you need your page entrypoint to catch all possible routes of your application. For that, create a new `[[...slug]]` directory in your `app` directory.
This directory is what is called an [optional catch-all route segment](/docs/app/api-reference/file-conventions/dynamic-routes#optional-catch-all-segments). Next.js uses a file-system based router where folders are used to define routes. This special directory will make sure that all routes of your application will be directed to its containing `page.tsx` file.
2. **Create a new `page.tsx` file inside the `app/[[...slug]]` directory with the following content:**
```tsx filename="app/[[...slug]]/page.tsx" switcher
import '../../index.css'
export function generateStaticParams() {
return [{ slug: [''] }]
}
export default function Page() {
return '...' // We'll update this
}
```
```jsx filename="app/[[...slug]]/page.js" switcher
import '../../index.css'
export function generateStaticParams() {
return [{ slug: [''] }]
}
export default function Page() {
return '...' // We'll update this
}
```
> **Good to know**: `.js`, `.jsx`, or `.tsx` extensions can be used for Page files.
This file is a [Server Component](/docs/app/getting-started/server-and-client-components). When you run `next build`, the file is prerendered into a static asset. It does _not_ require any dynamic code.
This file imports our global CSS and tells [`generateStaticParams`](/docs/app/api-reference/functions/generate-static-params) we are only going to generate one route, the index route at `/`.
Now, let's move the rest of our Vite application which will run client-only.
```tsx filename="app/[[...slug]]/client.tsx" switcher
'use client'
import React from 'react'
import dynamic from 'next/dynamic'
const App = dynamic(() => import('../../App'), { ssr: false })
export function ClientOnly() {
return <App />
}
```
```jsx filename="app/[[...slug]]/client.js" switcher
'use client'
import React from 'react'
import dynamic from 'next/dynamic'
const App = dynamic(() => import('../../App'), { ssr: false })
export function ClientOnly() {
return <App />
}
```
This file is a [Client Component](/docs/app/getting-started/server-and-client-components), defined by the `'use client'`
directive. Client Components are still [prerendered to HTML](/docs/app/getting-started/server-and-client-components#how-do-server-and-client-components-work-in-nextjs) on the server before being sent to the client.
Since we want a client-only application to start, we can configure Next.js to disable prerendering from the `App` component down.
```tsx
const App = dynamic(() => import('../../App'), { ssr: false })
```
Now, update your entrypoint page to use the new component:
```tsx filename="app/[[...slug]]/page.tsx" switcher
import '../../index.css'
import { ClientOnly } from './client'
export function generateStaticParams() {
return [{ slug: [''] }]
}
export default function Page() {
return <ClientOnly />
}
```
```jsx filename="app/[[...slug]]/page.js" switcher
import '../../index.css'
import { ClientOnly } from './client'
export function generateStaticParams() {
return [{ slug: [''] }]
}
export default function Page() {
return <ClientOnly />
}
```
### Step 6: Update Static Image Imports
Next.js handles static image imports slightly different from Vite. With Vite, importing an image
file will return its public URL as a string:
```tsx filename="App.tsx"
import image from './img.png' // `image` will be '/assets/img.2d8efhg.png' in production
export default function App() {
return <img src={image} />
}
```
With Next.js, static image imports return an object. The object can then be used directly with the
Next.js [`<Image>` component](/docs/app/api-reference/components/image), or you can use the object's
`src` property with your existing `<img>` tag.
The `<Image>` component has the added benefits of
[automatic image optimization](/docs/app/api-reference/components/image). The `<Image>`
component automatically sets the `width` and `height` attributes of the resulting `<img>` based on
the image's dimensions. This prevents layout shifts when the image loads. However, this can cause
issues if your app contains images with only one of their dimensions being styled without the other
styled to `auto`. When not styled to `auto`, the dimension will default to the `<img>` dimension
attribute's value, which can cause the image to appear distorted.
Keeping the `<img>` tag will reduce the amount of changes in your application and prevent the above
issues. You can then optionally later migrate to the `<Image>` component to take advantage of optimizing images by [configuring a loader](/docs/app/api-reference/components/image#loader), or moving to the default Next.js server which has automatic image optimization.
1. **Convert absolute import paths for images imported from `/public` into relative imports:**
```tsx
// Before
import logo from '/logo.png'
// After
import logo from '../public/logo.png'
```
2. **Pass the image `src` property instead of the whole image object to your `<img>` tag:**
```tsx
// Before
<img src={logo} />
// After
<img src={logo.src} />
```
Alternatively, you can reference the public URL for the image asset based on the filename. For example, `public/logo.png` will serve the image at `/logo.png` for your application, which would be the `src` value.
> **Warning:** If you're using TypeScript, you might encounter type errors when accessing the `src`
> property. You can safely ignore those for now. They will be fixed by the end of this guide.
### Step 7: Migrate the Environment Variables
Next.js has support for `.env`
[environment variables](/docs/app/guides/environment-variables)
similar to Vite. The main difference is the prefix used to expose environment variables on the
client-side.
- Change all environment variables with the `VITE_` prefix to `NEXT_PUBLIC_`.
Vite exposes a few built-in environment variables on the special `import.meta.env` object which
arent supported by Next.js. You need to update their usage as follows:
- `import.meta.env.MODE` ⇒ `process.env.NODE_ENV`
- `import.meta.env.PROD` ⇒ `process.env.NODE_ENV === 'production'`
- `import.meta.env.DEV` ⇒ `process.env.NODE_ENV !== 'production'`
- `import.meta.env.SSR` ⇒ `typeof window !== 'undefined'`
Next.js also doesn't provide a built-in `BASE_URL` environment variable. However, you can still
configure one, if you need it:
1. **Add the following to your `.env` file:**
```bash filename=".env"
# ...
NEXT_PUBLIC_BASE_PATH="/some-base-path"
```
2. **Set [`basePath`](/docs/app/api-reference/config/next-config-js/basePath) to `process.env.NEXT_PUBLIC_BASE_PATH` in your `next.config.mjs` file:**
```js filename="next.config.mjs"
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'export', // Outputs a Single-Page Application (SPA).
distDir: './dist', // Changes the build output directory to `./dist/`.
basePath: process.env.NEXT_PUBLIC_BASE_PATH, // Sets the base path to `/some-base-path`.
}
export default nextConfig
```
3. **Update `import.meta.env.BASE_URL` usages to `process.env.NEXT_PUBLIC_BASE_PATH`**
### Step 8: Update Scripts in `package.json`
You should now be able to run your application to test if you successfully migrated to Next.js. But
before that, you need to update your `scripts` in your `package.json` with Next.js related commands,
and add `.next` and `next-env.d.ts` to your `.gitignore`:
```json filename="package.json"
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start"
}
}
```
```txt filename=".gitignore"
# ...
.next
next-env.d.ts
dist
```
Now run `npm run dev`, and open [`http://localhost:3000`](http://localhost:3000). You should see your application now running on Next.js.
> **Example:** Check out [this pull request](https://github.com/inngest/vite-to-nextjs/pull/1) for a
> working example of a Vite application migrated to Next.js.
### Step 9: Clean Up
You can now clean up your codebase from Vite related artifacts:
- Delete `main.tsx`
- Delete `index.html`
- Delete `vite-env.d.ts`
- Delete `tsconfig.node.json`
- Delete `vite.config.ts`
- Uninstall Vite dependencies
## Next Steps
If everything went according to plan, you now have a functioning Next.js application running as a
single-page application. However, you aren't yet taking advantage of most of Next.js' benefits, but
you can now start making incremental changes to reap all the benefits. Here's what you might want to
do next:
- Migrate from React Router to the [Next.js App Router](/docs/app) to get:
- Automatic code splitting
- [Streaming Server-Rendering](/docs/app/api-reference/file-conventions/loading)
- [React Server Components](/docs/app/getting-started/server-and-client-components)
- [Optimize images with the `<Image>` component](/docs/app/api-reference/components/image)
- [Optimize fonts with `next/font`](/docs/app/api-reference/components/font)
- [Optimize third-party scripts with the `<Script>` component](/docs/app/guides/scripts)
- [Update your ESLint configuration to support Next.js rules](/docs/app/api-reference/config/eslint)
+4
View File
@@ -0,0 +1,4 @@
---
title: Migrating
description: Learn how to migrate from popular frameworks to Next.js
---
+7
View File
@@ -0,0 +1,7 @@
---
title: How to build multi-tenant apps in Next.js
nav_title: Multi-tenant
description: Learn how to build multi-tenant apps with the App Router.
---
If you are looking to build a single Next.js application that serves multiple tenants, we have [built an example](https://vercel.com/templates/next.js/platforms-starter-kit) showing our recommended architecture.
+142
View File
@@ -0,0 +1,142 @@
---
title: How to build micro-frontends using multi-zones and Next.js
nav_title: Multi-zones
description: Learn how to build micro-frontends using Next.js Multi-Zones to deploy multiple Next.js apps under a single 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. */}
<details open>
<summary>Examples</summary>
- [With Zones](https://github.com/vercel/next.js/tree/canary/examples/with-zones)
</details>
Multi-Zones are an approach to micro-frontends that separate a large application on a domain into smaller Next.js applications that each serve a set of paths. This is useful when there are collections of pages unrelated to the other pages in the application. By moving those pages to a separate zone (i.e., a separate application), you can reduce the size of each application which improves build times and removes code that is only necessary for one of the zones. Since applications are decoupled, Multi-Zones also allows other applications on the domain to use their own choice of framework.
For example, let's say you have the following set of pages that you would like to split up:
- `/blog/*` for all blog posts
- `/dashboard/*` for all pages when the user is logged-in to the dashboard
- `/*` for the rest of your website not covered by other zones
With Multi-Zones support, you can create three applications that all are served on the same domain and look the same to the user, but you can develop and deploy each of the applications independently.
<Image
alt="Three zones: A, B, C. Showing a hard navigation between routes from different zones, and soft navigations between routes within the same zone."
srcLight="/docs/light/multi-zones.png"
srcDark="/docs/dark/multi-zones.png"
width="1600"
height="750"
/>
Navigating between pages in the same zone will perform soft navigations, a navigation that does not require reloading the page. For example, in this diagram, navigating from `/` to `/products` will be a soft navigation.
Navigating from a page in one zone to a page in another zone, such as from `/` to `/dashboard`, will perform a hard navigation, unloading the resources of the current page and loading the resources of the new page. Pages that are frequently visited together should live in the same zone to avoid hard navigations.
## How to define a zone
A zone is a normal Next.js application where you also configure an [assetPrefix](/docs/app/api-reference/config/next-config-js/assetPrefix) to avoid conflicts with pages and static files in other zones.
```js filename="next.config.js"
/** @type {import('next').NextConfig} */
const nextConfig = {
assetPrefix: '/blog-static',
}
```
Next.js assets, such as JavaScript and CSS, will be prefixed with `assetPrefix` to make sure that they don't conflict with assets from other zones. These assets will be served under `/assetPrefix/_next/...` for each of the zones.
The default application handling all paths not routed to another more specific zone does not need an `assetPrefix`.
In versions older than Next.js 15, you may also need an additional rewrite to handle the static assets. This is no longer necessary in Next.js 15.
```js filename="next.config.js"
/** @type {import('next').NextConfig} */
const nextConfig = {
assetPrefix: '/blog-static',
async rewrites() {
return {
beforeFiles: [
{
source: '/blog-static/_next/:path+',
destination: '/_next/:path+',
},
],
}
},
}
```
## How to route requests to the right zone
With the Multi Zones set-up, you need to route the paths to the correct zone since they are served by different applications. You can use any HTTP proxy to do this, but one of the Next.js applications can also be used to route requests for the entire domain.
To route to the correct zone using a Next.js application, you can use [`rewrites`](/docs/app/api-reference/config/next-config-js/rewrites). For each path served by a different zone, you would add a rewrite rule to send that path to the domain of the other zone, and you also need to rewrite the requests for the static assets. For example:
```js filename="next.config.js"
async rewrites() {
return [
{
source: '/blog',
destination: `${process.env.BLOG_DOMAIN}/blog`,
},
{
source: '/blog/:path+',
destination: `${process.env.BLOG_DOMAIN}/blog/:path+`,
},
{
source: '/blog-static/:path+',
destination: `${process.env.BLOG_DOMAIN}/blog-static/:path+`,
}
];
}
```
`destination` should be a URL that is served by the zone, including scheme and domain. This should point to the zone's production domain, but it can also be used to route requests to `localhost` in local development.
> **Good to know**: URL paths should be unique to a zone. For example, two zones trying to serve `/blog` would create a routing conflict.
### Routing requests using proxy
Routing requests through [`rewrites`](/docs/app/api-reference/config/next-config-js/rewrites) is recommended to minimize latency overhead for the requests, but proxy can also be used when there is a need for a dynamic decision when routing. For example, if you are using a feature flag to decide where a path should be routed such as during a migration, you can use proxy.
```js filename="proxy.js"
export async function proxy(request) {
const { pathname, search } = request.nextUrl
if (pathname === '/your-path' && myFeatureFlag.isEnabled()) {
return NextResponse.rewrite(`${rewriteDomain}${pathname}${search}`)
}
}
```
## Linking between zones
Links to paths in a different zone should use an `a` tag instead of the Next.js [`<Link>`](/docs/pages/api-reference/components/link) component. This is because Next.js will try to prefetch and soft navigate to any relative path in `<Link>` component, which will not work across zones.
## Sharing code
The Next.js applications that make up the different zones can live in any repository. However, it is often convenient to put these zones in a [monorepo](https://en.wikipedia.org/wiki/Monorepo) to more easily share code. For zones that live in different repositories, code can also be shared using public or private NPM packages.
Since the pages in different zones may be released at different times, feature flags can be useful for enabling or disabling features in unison across the different zones.
<AppOnly>
## Server Actions
When using [Server Actions](/docs/app/getting-started/mutating-data) with Multi-Zones, you must explicitly allow the user-facing origin since your user facing domain may serve multiple applications. In your `next.config.js` file, add the following lines:
```js filename="next.config.js"
const nextConfig = {
experimental: {
serverActions: {
allowedOrigins: ['your-production-domain.com'],
},
},
}
```
See [`serverActions.allowedOrigins`](/docs/app/api-reference/config/next-config-js/serverActions#allowedorigins) for more information.
</AppOnly>
+405
View File
@@ -0,0 +1,405 @@
---
title: How to set up instrumentation with OpenTelemetry
nav_title: OpenTelemetry
description: Learn how to instrument your Next.js app with OpenTelemetry.
---
{/* 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. */}
Observability is crucial for understanding and optimizing the behavior and performance of your Next.js app.
As applications become more complex, it becomes increasingly difficult to identify and diagnose issues that may arise. By leveraging observability tools, such as logging and metrics, developers can gain insights into their application's behavior and identify areas for optimization. With observability, developers can proactively address issues before they become major problems and provide a better user experience. Therefore, it is highly recommended to use observability in your Next.js applications to improve performance, optimize resources, and enhance user experience.
We recommend using OpenTelemetry for instrumenting your apps.
It's a platform-agnostic way to instrument apps that allows you to change your observability provider without changing your code.
Read [Official OpenTelemetry docs](https://opentelemetry.io/docs/) for more information about OpenTelemetry and how it works.
This documentation uses terms like _Span_, _Trace_ or _Exporter_ throughout this doc, all of which can be found in [the OpenTelemetry Observability Primer](https://opentelemetry.io/docs/concepts/observability-primer/).
Next.js supports OpenTelemetry instrumentation out of the box, which means that we already instrumented Next.js itself.
<PagesOnly>
When you enable OpenTelemetry we will automatically wrap all your code like
`getStaticProps` in _spans_ with helpful attributes.
</PagesOnly>
## Getting Started
OpenTelemetry is extensible but setting it up properly can be quite verbose.
That's why we prepared a package `@vercel/otel` that helps you get started quickly.
### Using `@vercel/otel`
To get started, install the following packages:
```bash package="pnpm"
pnpm add @vercel/otel @opentelemetry/sdk-logs @opentelemetry/api-logs @opentelemetry/instrumentation
```
```bash package="npm"
npm install @vercel/otel @opentelemetry/sdk-logs @opentelemetry/api-logs @opentelemetry/instrumentation
```
```bash package="yarn"
yarn add @vercel/otel @opentelemetry/sdk-logs @opentelemetry/api-logs @opentelemetry/instrumentation
```
```bash package="bun"
bun add @vercel/otel @opentelemetry/sdk-logs @opentelemetry/api-logs @opentelemetry/instrumentation
```
<AppOnly>
Next, create a custom [`instrumentation.ts`](/docs/app/guides/instrumentation) (or `.js`) file in the **root directory** of the project (or inside `src` folder if using one):
</AppOnly>
<PagesOnly>
Next, create a custom [`instrumentation.ts`](/docs/pages/guides/instrumentation) (or `.js`) file in the **root directory** of the project (or inside `src` folder if using one):
</PagesOnly>
```ts filename="your-project/instrumentation.ts" switcher
import { registerOTel } from '@vercel/otel'
export function register() {
registerOTel({ serviceName: 'next-app' })
}
```
```js filename="your-project/instrumentation.js" switcher
import { registerOTel } from '@vercel/otel'
export function register() {
registerOTel({ serviceName: 'next-app' })
}
```
See the [`@vercel/otel` documentation](https://www.npmjs.com/package/@vercel/otel) for additional configuration options.
<AppOnly>
> **Good to know**:
>
> - The `instrumentation` file should be in the root of your project and not inside the `app` or `pages` directory. If you're using the `src` folder, then place the file inside `src` alongside `pages` and `app`.
> - If you use the [`pageExtensions` config option](/docs/app/api-reference/config/next-config-js/pageExtensions) to add a suffix, you will also need to update the `instrumentation` filename to match.
> - We have created a basic [with-opentelemetry](https://github.com/vercel/next.js/tree/canary/examples/with-opentelemetry) example that you can use.
</AppOnly>
<PagesOnly>
> **Good to know**:
>
> - The `instrumentation` file should be in the root of your project and not inside the `app` or `pages` directory. If you're using the `src` folder, then place the file inside `src` alongside `pages` and `app`.
> - If you use the [`pageExtensions` config option](/docs/pages/api-reference/config/next-config-js/pageExtensions) to add a suffix, you will also need to update the `instrumentation` filename to match.
> - We have created a basic [with-opentelemetry](https://github.com/vercel/next.js/tree/canary/examples/with-opentelemetry) example that you can use.
</PagesOnly>
### Manual OpenTelemetry configuration
The `@vercel/otel` package provides many configuration options and should serve most of common use cases. But if it doesn't suit your needs, you can configure OpenTelemetry manually.
Firstly you need to install OpenTelemetry packages:
```bash package="pnpm"
pnpm add @opentelemetry/sdk-node @opentelemetry/resources @opentelemetry/semantic-conventions @opentelemetry/sdk-trace-node @opentelemetry/exporter-trace-otlp-http
```
```bash package="npm"
npm install @opentelemetry/sdk-node @opentelemetry/resources @opentelemetry/semantic-conventions @opentelemetry/sdk-trace-node @opentelemetry/exporter-trace-otlp-http
```
```bash package="yarn"
yarn add @opentelemetry/sdk-node @opentelemetry/resources @opentelemetry/semantic-conventions @opentelemetry/sdk-trace-node @opentelemetry/exporter-trace-otlp-http
```
```bash package="bun"
bun add @opentelemetry/sdk-node @opentelemetry/resources @opentelemetry/semantic-conventions @opentelemetry/sdk-trace-node @opentelemetry/exporter-trace-otlp-http
```
Now you can initialize `NodeSDK` in your `instrumentation.ts`.
Unlike `@vercel/otel`, `NodeSDK` is not compatible with edge runtime, so you need to make sure that you are importing them only when `process.env.NEXT_RUNTIME === 'nodejs'`. We recommend creating a new file `instrumentation.node.ts` which you conditionally import only when using node:
```ts filename="instrumentation.ts" switcher
export async function register() {
if (process.env.NEXT_RUNTIME === 'nodejs') {
await import('./instrumentation.node.ts')
}
}
```
```js filename="instrumentation.js" switcher
export async function register() {
if (process.env.NEXT_RUNTIME === 'nodejs') {
await import('./instrumentation.node.js')
}
}
```
```ts filename="instrumentation.node.ts" switcher
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'
import { resourceFromAttributes } from '@opentelemetry/resources'
import { NodeSDK } from '@opentelemetry/sdk-node'
import { SimpleSpanProcessor } from '@opentelemetry/sdk-trace-node'
import { ATTR_SERVICE_NAME } from '@opentelemetry/semantic-conventions'
const sdk = new NodeSDK({
resource: resourceFromAttributes({
[ATTR_SERVICE_NAME]: 'next-app',
}),
spanProcessor: new SimpleSpanProcessor(new OTLPTraceExporter()),
})
sdk.start()
```
```js filename="instrumentation.node.js" switcher
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'
import { resourceFromAttributes } from '@opentelemetry/resources'
import { NodeSDK } from '@opentelemetry/sdk-node'
import { SimpleSpanProcessor } from '@opentelemetry/sdk-trace-node'
import { ATTR_SERVICE_NAME } from '@opentelemetry/semantic-conventions'
const sdk = new NodeSDK({
resource: resourceFromAttributes({
[ATTR_SERVICE_NAME]: 'next-app',
}),
spanProcessor: new SimpleSpanProcessor(new OTLPTraceExporter()),
})
sdk.start()
```
Doing this is equivalent to using `@vercel/otel`, but it's possible to modify and extend some features that are not exposed by the `@vercel/otel`. If edge runtime support is necessary, you will have to use `@vercel/otel`.
## Testing your instrumentation
You need an OpenTelemetry collector with a compatible backend to test OpenTelemetry traces locally.
We recommend using our [OpenTelemetry dev environment](https://github.com/vercel/opentelemetry-collector-dev-setup).
If everything works well you should be able to see the root server span labeled as `GET /requested/pathname`.
All other spans from that particular trace will be nested under it.
Next.js traces more spans than are emitted by default.
To see more spans, you must set `NEXT_OTEL_VERBOSE=1`.
## Deployment
### Using OpenTelemetry Collector
When you are deploying with OpenTelemetry Collector, you can use `@vercel/otel`.
It will work both on Vercel and when self-hosted.
#### Deploying on Vercel
We made sure that OpenTelemetry works out of the box on Vercel.
Follow [Vercel documentation](https://vercel.com/docs/concepts/observability/otel-overview/quickstart) to connect your project to an observability provider.
#### Self-hosting
Deploying to other platforms is also straightforward. You will need to spin up your own OpenTelemetry Collector to receive and process the telemetry data from your Next.js app.
To do this, follow the [OpenTelemetry Collector Getting Started guide](https://opentelemetry.io/docs/collector/getting-started/), which will walk you through setting up the collector and configuring it to receive data from your Next.js app.
Once you have your collector up and running, you can deploy your Next.js app to your chosen platform following their respective deployment guides.
### Custom Exporters
OpenTelemetry Collector is not necessary. You can use a custom OpenTelemetry exporter with [`@vercel/otel`](#using-vercelotel) or [manual OpenTelemetry configuration](#manual-opentelemetry-configuration).
## Custom Spans
You can add a custom span with [OpenTelemetry APIs](https://opentelemetry.io/docs/instrumentation/js/instrumentation).
```bash package="pnpm"
pnpm add @opentelemetry/api
```
```bash package="npm"
npm install @opentelemetry/api
```
```bash package="yarn"
yarn add @opentelemetry/api
```
```bash package="bun"
bun add @opentelemetry/api
```
The following example demonstrates a function that fetches GitHub stars and adds a custom `fetchGithubStars` span to track the fetch request's result:
```ts
import { trace } from '@opentelemetry/api'
export async function fetchGithubStars() {
return await trace
.getTracer('nextjs-example')
.startActiveSpan('fetchGithubStars', async (span) => {
try {
return await getValue()
} finally {
span.end()
}
})
}
```
The `register` function will execute before your code runs in a new environment.
You can start creating new spans, and they should be correctly added to the exported trace.
## Default Spans in Next.js
Next.js automatically instruments several spans for you to provide useful insights into your application's performance.
Attributes on spans follow [OpenTelemetry semantic conventions](https://opentelemetry.io/docs/reference/specification/trace/semantic_conventions/). We also add some custom attributes under the `next` namespace:
- `next.span_name` - duplicates span name
- `next.span_type` - each span type has a unique identifier
- `next.route` - The route pattern of the request (e.g., `/[param]/user`).
- `next.rsc` (true/false) - Whether the request is an RSC request, such as prefetch.
- `next.page`
- This is an internal value used by an app router.
- You can think about it as a route to a special file (like `page.ts`, `layout.ts`, `loading.ts` and others)
- It can be used as a unique identifier only when paired with `next.route` because `/layout` can be used to identify both `/(groupA)/layout.ts` and `/(groupB)/layout.ts`
### `[http.method] [next.route]`
- `next.span_type`: `BaseServer.handleRequest`
This span represents the root span for each incoming request to your Next.js application. It tracks the HTTP method, route, target, and status code of the request.
Attributes:
- [Common HTTP attributes](https://opentelemetry.io/docs/reference/specification/trace/semantic_conventions/http/#common-attributes)
- `http.method`
- `http.status_code`
- [Server HTTP attributes](https://opentelemetry.io/docs/reference/specification/trace/semantic_conventions/http/#http-server-semantic-conventions)
- `http.route`
- `http.target`
- `next.span_name`
- `next.span_type`
- `next.route`
### `render route (app) [next.route]`
- `next.span_type`: `AppRender.getBodyResult`.
This span represents the process of rendering a route in the app router.
Attributes:
- `next.span_name`
- `next.span_type`
- `next.route`
### `fetch [http.method] [http.url]`
- `next.span_type`: `AppRender.fetch`
This span represents the fetch request executed in your code.
Attributes:
- [Common HTTP attributes](https://opentelemetry.io/docs/reference/specification/trace/semantic_conventions/http/#common-attributes)
- `http.method`
- [Client HTTP attributes](https://opentelemetry.io/docs/reference/specification/trace/semantic_conventions/http/#http-client)
- `http.url`
- `net.peer.name`
- `net.peer.port` (only if specified)
- `next.span_name`
- `next.span_type`
This span can be turned off by setting `NEXT_OTEL_FETCH_DISABLED=1` in your environment. This is useful when you want to use a custom fetch instrumentation library.
### `executing api route (app) [next.route]`
- `next.span_type`: `AppRouteRouteHandlers.runHandler`.
This span represents the execution of an API Route Handler in the app router.
Attributes:
- `next.span_name`
- `next.span_type`
- `next.route`
### `getServerSideProps [next.route]`
- `next.span_type`: `Render.getServerSideProps`.
This span represents the execution of `getServerSideProps` for a specific route.
Attributes:
- `next.span_name`
- `next.span_type`
- `next.route`
### `getStaticProps [next.route]`
- `next.span_type`: `Render.getStaticProps`.
This span represents the execution of `getStaticProps` for a specific route.
Attributes:
- `next.span_name`
- `next.span_type`
- `next.route`
### `render route (pages) [next.route]`
- `next.span_type`: `Render.renderDocument`.
This span represents the process of rendering the document for a specific route.
Attributes:
- `next.span_name`
- `next.span_type`
- `next.route`
### `generateMetadata [next.page]`
- `next.span_type`: `ResolveMetadata.generateMetadata`.
This span represents the process of generating metadata for a specific page (a single route can have multiple of these spans).
Attributes:
- `next.span_name`
- `next.span_type`
- `next.page`
### `resolve page components`
- `next.span_type`: `NextNodeServer.findPageComponents`.
This span represents the process of resolving page components for a specific page.
Attributes:
- `next.span_name`
- `next.span_type`
- `next.route`
### `resolve segment modules`
- `next.span_type`: `NextNodeServer.getLayoutOrPageModule`.
This span represents loading of code modules for a layout or a page.
Attributes:
- `next.span_name`
- `next.span_type`
- `next.segment`
### `start response`
- `next.span_type`: `NextNodeServer.startResponse`.
This zero-length span represents the time when the first byte has been sent in the response.
+311
View File
@@ -0,0 +1,311 @@
---
title: Optimizing package bundling
nav_title: Package Bundling
description: Learn how to analyze and optimize your application's server and client bundles with the Next.js Bundle Analyzer for Turbopack, and the `@next/bundle-analyzer` plugin for Webpack.
related:
description: Learn more about optimizing your application for production.
links:
- app/guides/production-checklist
---
Bundling is the process of combining your application code and its dependencies into optimized output files for the client and server. Smaller bundles load faster, reduce JavaScript execution time, improve [Core Web Vitals](https://web.dev/articles/vitals), and lower server cold start times.
Next.js automatically optimizes bundles by code splitting, tree-shaking, and other techniques. However, there are some cases where you may need to optimize your bundles manually.
There are two tools for analyzing your application's bundles:
- [Next.js Bundle Analyzer for Turbopack (experimental)](#nextjs-bundle-analyzer-experimental)
- [`@next/bundle-analyzer` plugin for Webpack](#nextbundle-analyzer-for-webpack)
This guide will walk you through how to use each tool and how to [optimize large bundles](#optimizing-large-bundles).
## Next.js Bundle Analyzer (Experimental)
> Available in v16.1 and later. You can share feedback in the [dedicated GitHub discussion](https://github.com/vercel/next.js/discussions/86731) and view the demo at [turbopack-bundle-analyzer-demo.vercel.sh](https://turbopack-bundle-analyzer-demo.vercel.sh/).
The Next.js Bundle Analyzer is integrated with Turbopack's module graph. You can inspect server and client modules with precise import tracing, making it easier to find large dependencies. Open the interactive Bundle Analyzer demo to explore the module graph.
### Step 1: Run the Turbopack Bundle Analyzer
To get started, run the following command and open up the interactive view in your browser.
```bash filename="Terminal" package="npm"
npx next experimental-analyze
```
```bash filename="Terminal" package="yarn"
yarn next experimental-analyze
```
```bash filename="Terminal" package="pnpm"
pnpm next experimental-analyze
```
```bash filename="Terminal" package="bun"
bunx next experimental-analyze
```
### Step 2: Filter and inspect modules
Within the UI, you can filter by route, environment (client or server), and type (JavaScript, CSS, JSON), or search by file:
<Video
caption="Next.js bundle analyzer UI walkthrough"
src="/videos/bundle-analyzer.mp4"
width={1708}
height={1080}
/>
### Step 3: Trace modules with import chains
The treemap shows each module as a rectangle. Where the size of the module is represented by the area of the rectangle.
Click a module to see its size, inspect its full import chain and see exactly where its used in your application:
<Image
caption="Next.js Bundle Analyzer import chain view"
srcLight="/docs/light/bundle-analyzer.png"
srcDark="/docs/dark/bundle-analyzer.png"
width={1600}
height={874}
/>
### Step 4: Write output to disk for sharing or diffing
If you want to share the analysis with teammates or compare bundle sizes before/after optimizations, you can skip the interactive view and save the analysis as a static file with the `--output` flag:
```bash filename="Terminal" package="npm"
npx next experimental-analyze --output
```
```bash filename="Terminal" package="yarn"
yarn next experimental-analyze --output
```
```bash filename="Terminal" package="pnpm"
pnpm next experimental-analyze --output
```
```bash filename="Terminal" package="bun"
bunx next experimental-analyze --output
```
This command writes the output to `.next/diagnostics/analyze`. You can copy this directory elsewhere to compare results:
```bash filename="Terminal"
cp -r .next/diagnostics/analyze ./analyze-before-refactor
```
> More options are available for the Bundle Analyzer, see Next.js CLI reference docs for the full list.
## `@next/bundle-analyzer` for Webpack
The [`@next/bundle-analyzer`](https://www.npmjs.com/package/@next/bundle-analyzer) is a plugin that helps you manage the size of your application bundles. It generates a visual report of the size of each package and their dependencies. You can use the information to remove large dependencies, split, or [lazy-load](/docs/app/guides/lazy-loading) your code.
### Step 1: Installation
Install the plugin by running the following command:
```bash package="pnpm"
pnpm add @next/bundle-analyzer
```
```bash package="npm"
npm install @next/bundle-analyzer
```
```bash package="yarn"
yarn add @next/bundle-analyzer
```
```bash package="bun"
bun add @next/bundle-analyzer
```
Then, add the bundle analyzer's settings to your `next.config.js`.
```js filename="next.config.js"
/** @type {import('next').NextConfig} */
const nextConfig = {}
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true',
})
module.exports = withBundleAnalyzer(nextConfig)
```
### Step 2: Generating a report
Run the following command to analyze your bundles:
```bash
ANALYZE=true npm run build
# or
ANALYZE=true yarn build
# or
ANALYZE=true pnpm build
```
The report will open three new tabs in your browser, which you can inspect.
## Optimizing large bundles
Once you've identified a large module, the solution will depend on your use case. Below are common causes and how to fix them:
### Packages with many exports
If you're using a package that exports hundreds of modules (such as icon and utility libraries), you can optimize how those imports are resolved using the [`optimizePackageImports`](/docs/app/api-reference/config/next-config-js/optimizePackageImports) option in your `next.config.js` file. This option will only load the modules you _actually_ use, while still giving you the convenience of writing import statements with many named exports.
```js filename="next.config.js"
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
optimizePackageImports: ['icon-library'],
},
}
module.exports = nextConfig
```
> **Good to know:** Next.js also optimizes some libraries automatically, thus they do not need to be included in the `optimizePackageImports` list. See the [full list](/docs/app/api-reference/config/next-config-js/optimizePackageImports) of supported packages.
### Heavy client workloads
A common cause of large client bundles is doing expensive rendering work in Client Components. This often happens with libraries that exist only to transform data into UI, such as syntax highlighting, chart rendering, or markdown parsing.
If that work does not require browser APIs or user interaction, it can be run in a Server Component.
In this example, a prism based highlighter runs in a Client Component. Even though the final output is just a `<code>` block, the entire highlighting library is bundled into the client JavaScript bundle:
```tsx filename="app/blog/[slug]/page.tsx"
'use client'
import Highlight from 'prism-react-renderer'
import theme from 'prism-react-renderer/themes/github'
export default function Page() {
const code = `export function hello() {
console.log("hi")
}`
return (
<article>
<h1>Blog Post Title</h1>
{/* The prism package and its tokenization logic are shipped to the client */}
<Highlight code={code} language="tsx" theme={theme}>
{({ className, style, tokens, getLineProps, getTokenProps }) => (
<pre className={className} style={style}>
<code>
{tokens.map((line, i) => (
<div key={i} {...getLineProps({ line })}>
{line.map((token, key) => (
<span key={key} {...getTokenProps({ token })} />
))}
</div>
))}
</code>
</pre>
)}
</Highlight>
</article>
)
}
```
This increases bundle size because the client must download and execute the highlighting library, even though the result is static HTML.
Instead, move the highlighting logic to a Server Component and render the final HTML on the server. The client will only receive the rendered markup.
```tsx filename="app/blog/[slug]/page.tsx"
import { codeToHtml } from 'shiki'
export default async function Page() {
const code = `export function hello() {
console.log("hi")
}`
// The Shiki package runs on the server and is never bundled for the client.
const highlightedHtml = await codeToHtml(code, {
lang: 'tsx',
theme: 'github-dark',
})
return (
<article>
<h1>Blog Post Title</h1>
{/* Client receives plain markup */}
<pre>
<code dangerouslySetInnerHTML={{ __html: highlightedHtml }} />
</pre>
</article>
)
}
```
<AppOnly>
### Opting specific packages out of bundling
Packages imported inside Server Components and Route Handlers are automatically bundled by Next.js.
You can opt specific packages out of bundling using the [`serverExternalPackages`](/docs/app/api-reference/config/next-config-js/serverExternalPackages) option in your `next.config.js`.
```js filename="next.config.js"
/** @type {import('next').NextConfig} */
const nextConfig = {
serverExternalPackages: ['package-name'],
}
module.exports = nextConfig
```
</AppOnly>
<PagesOnly>
### External packages that aren't pre-bundled
By default, packages imported into your application are not bundled. This can impact performance if external packages are not pre-bundled, for example, if imported from a monorepo or `node_modules`.
To bundle specific packages, you can use the [`transpilePackages`](/docs/app/api-reference/config/next-config-js/transpilePackages) option in your `next.config.js`.
```js filename="next.config.js"
/** @type {import('next').NextConfig} */
const nextConfig = {
transpilePackages: ['package-name'],
}
module.exports = nextConfig
```
To automatically bundle all packages, you can use the [`bundlePagesRouterDependencies`](/docs/pages/api-reference/config/next-config-js/bundlePagesRouterDependencies) option in your `next.config.js`.
```js filename="next.config.js"
/** @type {import('next').NextConfig} */
const nextConfig = {
bundlePagesRouterDependencies: true,
}
module.exports = nextConfig
```
### Opting specific packages out of bundling
If you identify packages that shouldn't be in the bundle, you can opt specific packages out of automatic bundling using the [`serverExternalPackages`](/docs/pages/api-reference/config/next-config-js/serverExternalPackages) option in your `next.config.js`:
```js filename="next.config.js"
/** @type {import('next').NextConfig} */
const nextConfig = {
// Automatically bundle external packages:
bundlePagesRouterDependencies: true,
// Opt specific packages out of bundling:
serverExternalPackages: ['package-name'],
}
module.exports = nextConfig
```
</PagesOnly>
+368
View File
@@ -0,0 +1,368 @@
---
title: Prefetching
description: Learn how to configure prefetching in Next.js
---
Prefetching makes navigating between different routes in your application feel instant. Next.js tries to intelligently prefetch by default, based on the links used in your application code.
This guide will explain how prefetching works and show common implementation patterns:
- [Automatic prefetch](#automatic-prefetch)
- [Manual prefetch](#manual-prefetch)
- [Hover-triggered prefetch](#hover-triggered-prefetch)
- [Extending or ejecting link](#extending-or-ejecting-link)
- [Disabled prefetch](#disabled-prefetch)
## How does prefetching work?
When navigating between routes, the browser requests assets for the page like HTML and JavaScript files. Prefetching is the process of fetching these resources _ahead_ of time, before you navigate to a new route.
Next.js automatically splits your application into smaller JavaScript chunks based on routes. Instead of loading all the code upfront like traditional SPAs, only the code needed for the current route is loaded. This reduces the initial load time while other parts of the app are loaded in the background. By the time you click the link, the resources for the new route have already been loaded into the browser cache.
When navigating to the new page, there's no full page reload or browser loading spinner. Instead, Next.js performs a [client-side transition](/docs/app/getting-started/linking-and-navigating#client-side-transitions), making the page navigation feel instant.
## Prefetching static vs. dynamic routes
| | **Static page** | **Dynamic page** |
| ------------------------------------------------------- | --------------- | ------------------------------------------------------------------------------- |
| **Prefetched** | Yes, full route | No, unless [`loading.js`](/docs/app/api-reference/file-conventions/loading) |
| [**Client Cache TTL**](/docs/app/glossary#client-cache) | 5 min (default) | Off, unless [enabled](/docs/app/api-reference/config/next-config-js/staleTimes) |
| **Server roundtrip on click** | No | Yes, streamed after [shell](/docs/app/getting-started/caching) |
> **Good to know:** During the initial navigation, the browser fetches the HTML, JavaScript, and React Server Components (RSC) Payload. For subsequent navigations, the browser will fetch the RSC Payload for Server Components and JS bundle for Client Components.
## Automatic prefetch
```tsx filename="app/ui/nav-link.tsx" switcher
import Link from 'next/link'
export default function NavLink() {
return <Link href="/about">About</Link>
}
```
```jsx filename="app/ui/nav-link.js" switcher
import Link from 'next/link'
export default function NavLink() {
return <Link href="/about">About</Link>
}
```
| **Context** | **Prefetched payload** | **Client Cache TTL** |
| ----------------- | -------------------------------- | ------------------------------------------------------------------------------ |
| No `loading.js` | Entire page | Until app reload |
| With `loading.js` | Layout to first loading boundary | 30s ([configurable](/docs/app/api-reference/config/next-config-js/staleTimes)) |
Automatic prefetching runs only in production. Disable with `prefetch={false}` or use the wrapper in [Disabled Prefetch](#disabled-prefetch).
## Manual prefetch
To do manual prefetching, import the `useRouter` hook from `next/navigation`, and call `router.prefetch()` to warm routes outside the viewport or in response to analytics, hover, scroll, etc.
```tsx
'use client'
import { useRouter } from 'next/navigation'
import { CustomLink } from '@components/link'
export function PricingCard() {
const router = useRouter()
return (
<div onMouseEnter={() => router.prefetch('/pricing')}>
{/* other UI elements */}
<CustomLink href="/pricing">View Pricing</CustomLink>
</div>
)
}
```
If the intent is to prefetch a URL when a component loads, see the extending or rejecting a link [example].
## Hover-triggered prefetch
> **Proceed with caution:** Extending `Link` opts you into maintaining prefetching, cache invalidation, and accessibility concerns. Proceed only if defaults are insufficient.
Next.js tries to do the right prefetching by default, but power users can eject and modify based on their needs. You have the control between performance and resource consumption.
For example, you might have to only trigger prefetches on hover, instead of when entering the viewport (the default behavior):
```tsx
'use client'
import Link from 'next/link'
import { useState } from 'react'
export function HoverPrefetchLink({
href,
children,
}: {
href: string
children: React.ReactNode
}) {
const [active, setActive] = useState(false)
return (
<Link
href={href}
prefetch={active ? null : false}
onMouseEnter={() => setActive(true)}
>
{children}
</Link>
)
}
```
`prefetch={null}` restores default (static) prefetching once the user shows intent.
## Extending or ejecting link
You can extend the `<Link>` component to create your own custom prefetching strategy. For example, using the [ForesightJS](https://foresightjs.com/docs/integrations/nextjs) library which prefetches links by predicting the direction of the user's cursor.
Alternatively, you can use [`useRouter`](/docs/app/api-reference/functions/use-router) to recreate some of the native `<Link>` behavior. However, be aware this opts you into maintaining prefetching and cache invalidation.
```tsx
'use client'
import { useRouter } from 'next/navigation'
import { useEffect } from 'react'
function ManualPrefetchLink({
href,
children,
}: {
href: string
children: React.ReactNode
}) {
const router = useRouter()
useEffect(() => {
let cancelled = false
const poll = () => {
if (!cancelled) router.prefetch(href, { onInvalidate: poll })
}
poll()
return () => {
cancelled = true
}
}, [href, router])
return (
<a
href={href}
onClick={(event) => {
event.preventDefault()
router.push(href)
}}
>
{children}
</a>
)
}
```
[`onInvalidate`](/docs/app/api-reference/functions/use-router#userouter) is invoked when Next.js suspects cached data is stale, allowing you to refresh the prefetch.
> **Good to know:** Using an `a` tag will cause a full page navigation to the destination route, you can use `onClick` to prevent the full page navigation, and then invoke `router.push` to navigate to the destination.
## Disabled prefetch
You can fully disable prefetching for certain routes for more fine-grained control over resource consumption.
```tsx
'use client'
import Link, { LinkProps } from 'next/link'
function NoPrefetchLink({
prefetch,
...rest
}: LinkProps & { children: React.ReactNode }) {
return <Link {...rest} prefetch={false} />
}
```
For example, you may still want to have consistent usage of `<Link>` in your application, but links in your footer might not need to be prefetched when entering the viewport.
## Prefetching optimizations
### Client cache
Next.js stores prefetched React Server Component payloads in memory, keyed by route segments. When navigating between sibling routes (e.g. `/dashboard/settings` → `/dashboard/analytics`), it reuses the parent layout and only fetches the updated leaf page. This reduces network traffic and improves navigation speed.
### Prefetch scheduling
Next.js maintains a small task queue, which prefetches in the following order:
1. Links in the viewport
2. Links showing user intent (hover or touch)
3. Newer links replace older ones
4. Links scrolled off-screen are discarded
The scheduler prioritizes likely navigations while minimizing unused downloads.
### Partial Prerendering (PPR)
When PPR is enabled, a page is divided into a [static shell](/docs/app/guides/streaming#the-static-shell) and a streamed dynamic section:
- The shell, which can be prefetched, streams immediately
- Uncached data streams when ready
- Data invalidations (`revalidateTag`, `revalidatePath`) silently refresh associated prefetches
## Troubleshooting
### Triggering unwanted side-effects during prefetching
If your layouts or pages are not [pure](https://react.dev/learn/keeping-components-pure#purity-components-as-formulas) and have side-effects (e.g. tracking analytics), these might be triggered when the route is prefetched, not when the user visits the page.
To avoid this, you should move side-effects to a `useEffect` hook or a Server Action triggered from a Client Component.
**Before**:
```tsx filename="app/dashboard/layout.tsx" switcher
import { trackPageView } from '@/lib/analytics'
export default function Layout({ children }: { children: React.ReactNode }) {
// This runs during prefetch
trackPageView()
return <div>{children}</div>
}
```
```jsx filename="app/dashboard/layout.js" switcher
import { trackPageView } from '@/lib/analytics'
export default function Layout({ children }) {
// This runs during prefetch
trackPageView()
return <div>{children}</div>
}
```
**After**:
```tsx filename="app/ui/analytics-tracker.tsx" switcher
'use client'
import { useEffect } from 'react'
import { trackPageView } from '@/lib/analytics'
export function AnalyticsTracker() {
useEffect(() => {
trackPageView()
}, [])
return null
}
```
```jsx filename="app/ui/analytics-tracker.js" switcher
'use client'
import { useEffect } from 'react'
import { trackPageView } from '@/lib/analytics'
export function AnalyticsTracker() {
useEffect(() => {
trackPageView()
}, [])
return null
}
```
```tsx filename="app/dashboard/layout.tsx" switcher
import { AnalyticsTracker } from '@/app/ui/analytics-tracker'
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<div>
<AnalyticsTracker />
{children}
</div>
)
}
```
```jsx filename="app/dashboard/layout.js" switcher
import { AnalyticsTracker } from '@/app/ui/analytics-tracker'
export default function Layout({ children }) {
return (
<div>
<AnalyticsTracker />
{children}
</div>
)
}
```
### Preventing too many prefetches
Next.js automatically prefetches links in the viewport when using the `<Link>` component.
There may be cases where you want to prevent this to avoid unnecessary usage of resources, such as when rendering a large list of links (e.g. an infinite scroll table).
You can disable prefetching by setting the `prefetch` prop of the `<Link>` component to `false`.
```tsx filename="app/ui/no-prefetch-link.tsx" switcher
<Link prefetch={false} href={`/blog/${post.id}`}>
{post.title}
</Link>
```
However, this means static routes will only be fetched on click, and dynamic routes will wait for the server to render before navigating.
To reduce resource usage without disabling prefetch entirely, you can defer prefetching until the user hovers over a link. This targets only links the user is likely to visit.
```tsx filename="app/ui/hover-prefetch-link.tsx" switcher
'use client'
import Link from 'next/link'
import { useState } from 'react'
export function HoverPrefetchLink({
href,
children,
}: {
href: string
children: React.ReactNode
}) {
const [active, setActive] = useState(false)
return (
<Link
href={href}
prefetch={active ? null : false}
onMouseEnter={() => setActive(true)}
>
{children}
</Link>
)
}
```
```jsx filename="app/ui/hover-prefetch-link.js" switcher
'use client'
import Link from 'next/link'
import { useState } from 'react'
export function HoverPrefetchLink({ href, children }) {
const [active, setActive] = useState(false)
return (
<Link
href={href}
prefetch={active ? null : false}
onMouseEnter={() => setActive(true)}
>
{children}
</Link>
)
}
```
+503
View File
@@ -0,0 +1,503 @@
---
title: Preserving UI state across navigations
nav_title: Preserving UI state
description: Learn how to control which UI state is preserved and which resets when navigating between pages.
related:
title: Related
description: Learn more about Cache Components and preserving UI state.
links:
- app/getting-started/caching
---
> **Good to know:** This guide assumes [Cache Components](/docs/app/getting-started/caching) is enabled. Enable it by setting [`cacheComponents: true`](/docs/app/api-reference/config/next-config-js/cacheComponents) in your Next config file.
Before Cache Components, preserving page-level state across navigations required workarounds like hoisting state to a [shared layout](/docs/app/getting-started/layouts-and-pages#nesting-layouts) or using an external store. With Cache Components, Next.js preserves state and DOM out of the box.
Instead of unmounting pages on navigation, Next.js hides them using React's [`<Activity>`](https://react.dev/reference/react/Activity) component. The DOM nodes stay in the document (hidden with `display: none`), so both React state and DOM state are preserved: form drafts, scroll positions, expanded `<details>` elements, video playback progress, and more.
Next.js preserves up to 3 routes. Beyond that, the oldest route is evicted and will re-render fresh.
> **Good to know:** Opt-out strategies are being considered for gradual migration.
## Choosing what to preserve
Activity preserves all component state and DOM state by default. For each piece of state, you decide whether that's the right behavior for your UI. The patterns below show common scenarios and how to handle both sides.
### Expandable UI (dropdowns, accordions, panels)
When a user navigates away and returns, Activity preserves the open/closed state of expandable elements.
**When to keep it:** A sidebar with expanded sections, a FAQ accordion, or a filters panel. The user set up their view intentionally, and restoring it avoids re-doing that work.
**When to reset it:** A dropdown menu or popover triggered by a button click. These are transient interactions, not persistent view state. Returning to a page with a dropdown already open is not user friendly.
To reset transient open/closed state, close it in a `useLayoutEffect` cleanup function:
```tsx highlight={8-13}
'use client'
import { useState, useLayoutEffect } from 'react'
function SettingsDropdown() {
const [isOpen, setIsOpen] = useState(false)
// Close dropdown when this component becomes hidden
useLayoutEffect(() => {
return () => {
setIsOpen(false)
}
}, [])
return (
<div>
<button onClick={() => setIsOpen((o) => !o)}>Options</button>
{isOpen && (
<ul>
<li>
<button>Edit Profile</button>
</li>
<li>
<button>Change Password</button>
</li>
</ul>
)}
</div>
)
}
```
When Activity hides this component, the cleanup function runs and resets `isOpen`. When the page becomes visible again, the dropdown is closed. Using `useLayoutEffect` ensures the cleanup runs synchronously before the component is hidden, avoiding any flash of stale state.
You can also use `Link`'s [`onNavigate`](/docs/app/api-reference/components/link#onnavigate) callback to close dropdowns immediately when a navigation link is clicked.
### Dialog and initialization logic
Activity preserves dialog open/closed state. This also affects Effects that run based on that state.
**When to keep it:** A multi-step wizard or a settings panel that the user was actively working in. Preserving the step and input state avoids losing progress.
**When to reset it:** A dialog that runs initialization logic (like focusing an input) each time it opens. If the user navigated away while the dialog was open, Activity preserves `isDialogOpen: true`. Opening it again sets it to `true` when it's already `true`, so no state change happens and the Effect doesn't re-run.
Consider this example:
```tsx
'use client'
import { useState, useRef, useEffect } from 'react'
function ProductTab() {
const [isDialogOpen, setIsDialogOpen] = useState(false)
const inputRef = useRef<HTMLInputElement>(null)
useEffect(() => {
if (isDialogOpen) {
inputRef.current?.focus()
}
}, [isDialogOpen])
// ...
}
```
If the user navigated away while the dialog was open, returning and opening the dialog won't trigger the focus Effect because `isDialogOpen` was already `true`.
To fix this, derive the dialog state from something outside the preserved component state like a search param:
```tsx highlight={3,7-9,20,25}
'use client'
import { useSearchParams, useRouter } from 'next/navigation'
import { useEffect, useRef } from 'react'
function ProductTab() {
const searchParams = useSearchParams()
const router = useRouter()
const isDialogOpen = searchParams.get('edit') === 'true'
const inputRef = useRef<HTMLInputElement>(null)
useEffect(() => {
if (isDialogOpen) {
inputRef.current?.focus()
}
}, [isDialogOpen])
return (
<div>
<button onClick={() => router.push('?edit=true')}>Edit Product</button>
{isDialogOpen && (
<dialog open>
<input ref={inputRef} placeholder="Product name" />
<button onClick={() => router.replace('?', { scroll: false })}>
Close
</button>
</dialog>
)}
</div>
)
}
```
With this approach, `isDialogOpen` derives from the URL rather than component state. When navigating away and returning, the search param is cleared (the URL changed), so `isDialogOpen` becomes `false`. Opening the dialog sets the param, which changes `isDialogOpen` and triggers the Effect.
### Form input values
Activity preserves form input values: text typed into fields, selected options, checkbox states.
**When to keep it:** A search page with filters, a draft the user was composing, or a settings form with unsaved changes. Preserving input state is one of the biggest UX wins because the user doesn't lose work.
**When to reset it:** A "create new item" page where returning should start fresh, or a contact form after successful submission.
To reset form fields when Activity hides the component, use a callback ref:
```tsx
<form
ref={(form) => {
// Cleanup function - runs when Activity hides this component
return () => form?.reset()
}}
>
{/* fields */}
</form>
```
This resets the form whenever the user navigates away.
### Action state (`useActionState`)
Activity preserves [`useActionState`](https://react.dev/reference/react/useActionState) results: success messages, error messages, and any other state returned by the action.
**When to keep it:** A ticket redemption form showing "Ticket redeemed successfully", or a settings form showing "Changes saved". Seeing the result of a previous action when returning to the page is useful confirmation so the user can see what happened.
**When to reset it:** A "new transaction" flow where each visit should start fresh, or a form where stale success/error messages would be confusing in a new context.
You can think of `useActionState` as a `useReducer` that allows side effects. It doesn't have to only handle form submissions; you can dispatch any action to it. Adding a `RESET` action gives you a clean way to clear state when Activity hides the component (see [Reset state](https://react.dev/reference/react/useActionState#reset-state) in the React docs):
```tsx highlight={5-6,9-21,26-35}
'use client'
import { useActionState, useLayoutEffect, useRef, startTransition } from 'react'
type Action = { type: 'SUBMIT'; data: FormData } | { type: 'RESET' }
type State = { success: boolean; error: string | null }
function CommentForm() {
const [state, dispatch, isPending] = useActionState(
async (prev: State, action: Action) => {
if (action.type === 'RESET') {
return { success: false, error: null }
}
// Handle the form submission
const res = await saveComment(action.data)
if (!res.ok) return { success: false, error: res.message }
shouldReset.current = true
return { success: true, error: null }
},
{ success: false, error: null }
)
const shouldReset = useRef(false)
// Dispatch RESET when Activity hides this component
useLayoutEffect(() => {
return () => {
if (shouldReset.current) {
shouldReset.current = false
startTransition(() => {
dispatch({ type: 'RESET' })
})
}
}
}, [dispatch])
return (
<form action={(formData) => dispatch({ type: 'SUBMIT', data: formData })}>
<textarea name="comment" />
<button type="submit" disabled={isPending}>
{isPending ? 'Posting...' : 'Post Comment'}
</button>
{state.success && <p>Comment posted!</p>}
{state.error && <p>{state.error}</p>}
</form>
)
}
```
Here's what happens step by step:
1. The user submits the form. The reducer receives a `SUBMIT` action with the `FormData`, calls `saveComment`, and returns `{ success: true }`. It also sets `shouldReset.current = true` to mark that a reset is needed.
2. The user navigates away. Activity hides the component and runs the `useLayoutEffect` cleanup. Because `shouldReset.current` is `true`, it dispatches a `RESET` action.
3. The reducer receives `RESET` and returns the initial state (`{ success: false, error: null }`). The stale success message is cleared.
4. If the user navigates back, the form is ready for a new submission. If they never submitted (step 1 didn't happen), `shouldReset.current` is still `false`, so no `RESET` is dispatched. The form stays as-is.
## State and authentication
Activity preserves local component state (`useState`, DOM input values) across navigations, including authentication changes. This is standard React behavior: props changing (such as receiving a new user) triggers a re-render but does not reset existing state. A draft composed by one user shouldn't be visible to another.
For logout flows, using `window.location.href` instead of `router.push` triggers a full page reload, clearing all client-side state.
To reset specific state when the user changes without a full reload:
```tsx
'use client'
import { useState, useEffect, useRef } from 'react'
function UserScopedForm({ userId }: { userId: string | null }) {
const [draft, setDraft] = useState('')
const lastUserIdRef = useRef<string | null>(null)
useEffect(() => {
if (lastUserIdRef.current !== null && lastUserIdRef.current !== userId) {
setDraft('') // Reset on user change
}
lastUserIdRef.current = userId
}, [userId])
return <textarea value={draft} onChange={(e) => setDraft(e.target.value)} />
}
```
Alternatively, key components by user ID to let React handle the reset: `<Form key={userId} />`.
## Global styles
Page-level styles (CSS variables, z-index, global classes) can affect visible pages when the originating component is hidden by Activity. You likely want to disable them when hidden: a hidden page's accent color or z-index overrides shouldn't leak into the visible page.
Use a callback ref to toggle the stylesheet's `media` attribute:
```tsx
<style
ref={(style) => {
if (style) style.media = '' // Enable when visible
return () => {
if (style) style.media = 'not all' // Disable when hidden
}
}}
>
{`:root { --page-accent: blue; }`}
</style>
```
Or use `useLayoutEffect` when managing multiple style elements or more complex cleanup:
```tsx
'use client'
import { useLayoutEffect, useRef } from 'react'
function PageWithStyles() {
const styleRef = useRef<HTMLStyleElement>(null)
useLayoutEffect(() => {
if (styleRef.current) styleRef.current.media = ''
return () => {
if (styleRef.current) styleRef.current.media = 'not all'
}
}, [])
return <style ref={styleRef}>{`:root { --page-accent: blue; }`}</style>
}
```
When Activity hides the component, the cleanup sets `media="not all"`, which disables the stylesheet. When visible again, the effect re-runs and resets `media` to enable it.
## Testing
Hidden Activity content has `display: none` but remains in the document. This applies both to routes preserved by Cache Components and to content you hide with `<Activity>` directly. It affects end-to-end testing with tools like Playwright, Cypress, or Puppeteer:
- **DOM queries can find hidden elements.** Selectors may match elements regardless of visibility.
- **Interactions with hidden elements fail or timeout.** Most tools wait for elements to become visible before interacting.
- **Assertions may match hidden content.** Be explicit about visibility when asserting element presence.
### Use visibility-aware selectors
In Playwright, `getByRole` queries automatically filter by visibility:
```ts
// Good - getByRole filters by visibility automatically
await page.getByRole('button', { name: 'Submit' }).click()
await page.getByRole('textbox', { name: 'Email' }).fill('test@example.com')
// Also good - getByLabel, getByPlaceholder filter by visibility
await page.getByLabel('Email').fill('test@example.com')
await page.getByPlaceholder('Search...').fill('query')
```
When `getByRole` isn't suitable, use `.locator()` with visibility filtering:
```ts
// Fallback - filter by visibility explicitly
await page.locator('.product-card').filter({ visible: true }).first().click()
await page
.locator('[data-testid="timer"]')
.filter({ visible: true })
.textContent()
// Avoid - may match hidden elements in Activity boundaries
await page.locator('.product-card').first().click()
```
`getByRole` is robust to Activity, tabbed navigation, accordions, and any other pattern that keeps hidden content in the DOM. It queries the accessibility tree, which excludes hidden elements. For other testing tools, check their documentation for visibility-aware selectors. For example, Cypress uses `.should('be.visible')` or `{ visible: true }` options.
## Using Activity in your components
Cache Components uses Activity automatically at the route level, but you can also use `<Activity>` directly in your own components. This is useful for tabs, expandable panels, or any UI where you want to hide content without unmounting it.
### Prerendering hidden content
Activity can prerender content the user hasn't seen yet. Hidden boundaries render at lower priority. Combined with Suspense, this lets you prefetch data for content the user is likely to view next.
A Server Component can start fetching data immediately and pass the promise to a client component. The client component uses Activity to hide the content until the user requests it, and `use()` to resolve the promise when rendering:
```tsx filename="app/page.tsx"
import { Suspense } from 'react'
import { ExpandableComments } from './expandable-comments'
async function getCommentsData() {
return db.comments.findMany()
}
export default function Page() {
const commentsPromise = getCommentsData()
return (
<article>
<h1>Post Title</h1>
<p>Main content visible immediately...</p>
<ExpandableComments commentsPromise={commentsPromise} />
</article>
)
}
```
```tsx filename="app/expandable-comments.tsx"
'use client'
import { Activity, Suspense, useState, use } from 'react'
type Comment = { id: string; text: string; author: string }
export function ExpandableComments({
commentsPromise,
}: {
commentsPromise: Promise<Comment[]>
}) {
const [expanded, setExpanded] = useState(false)
return (
<>
<button onClick={() => setExpanded((e) => !e)}>
{expanded ? 'Hide Comments' : 'Show Comments'}
</button>
<Activity mode={expanded ? 'visible' : 'hidden'}>
<Suspense fallback={<CommentsSkeleton />}>
<Comments commentsPromise={commentsPromise} />
</Suspense>
</Activity>
</>
)
}
function Comments({
commentsPromise,
}: {
commentsPromise: Promise<Comment[]>
}) {
const comments = use(commentsPromise)
return (
<ul>
{comments.map((c) => (
<li key={c.id}>{c.text}</li>
))}
</ul>
)
}
function CommentsSkeleton() {
return <div>Loading comments...</div>
}
```
The Server Component starts fetching comments immediately and passes the promise down. While hidden, the data streams at lower priority. When the user clicks "Show Comments", the `Comments` component resolves the promise with `use()` and the content appears instantly.
### Effect and media cleanup
When Activity hides content, React runs effect cleanup functions just like it does on unmount. This means timers, subscriptions, and media playback pause automatically if you have proper cleanup:
```tsx
'use client'
import { useEffect, useState } from 'react'
function LiveTimer() {
const [count, setCount] = useState(0)
useEffect(() => {
const id = setInterval(() => setCount((c) => c + 1), 1000)
return () => clearInterval(id) // Pauses when hidden
}, [])
return <p>Count: {count}</p>
}
```
For media elements like `<video>` and `<audio>`, `display: none` does not stop playback. Add explicit cleanup with `useLayoutEffect`:
```tsx
'use client'
import { useLayoutEffect, useRef } from 'react'
function VideoPlayer({ src }: { src: string }) {
const videoRef = useRef<HTMLVideoElement>(null)
useLayoutEffect(() => {
const video = videoRef.current
return () => {
video?.pause() // Pauses when hidden, preserves playback position
}
}, [])
return <video ref={videoRef} src={src} controls />
}
```
When the component becomes visible again, effects re-run and playback position is preserved since the DOM node was never removed.
### Distinguishing first mount from re-show
Effects run on every hide-to-visible transition, not just the initial mount. If you need to distinguish the first mount from subsequent visibility changes, use a ref:
```tsx
'use client'
import { useEffect, useRef } from 'react'
function TrackedComponent() {
const hasMountedRef = useRef(false)
useEffect(() => {
if (!hasMountedRef.current) {
hasMountedRef.current = true
console.log('First mount')
} else {
console.log('Became visible again')
}
}, [])
return <div>...</div>
}
```
The ref persists across hide/show cycles (refs aren't cleaned up), so `hasMountedRef.current` stays `true` after the first mount. Each time Activity becomes visible, the Effect runs again, but now it takes the `else` branch.
## Examples
The [Activity Patterns Demo](https://react-activity-patterns.vercel.app/) ([source](https://github.com/vercel-labs/react-activity-patterns)) is a Next.js app with Cache Components enabled and three routes. Navigate between them to see state preservation in action:
- **Data** — sortable table and selectable list that keep their state across navigations, plus a reviews section that prerenders in the background
- **Forms** — filter panel with DOM state (`<details>`, checkboxes, text inputs) that persists, and a newsletter form that resets after submission using `useLayoutEffect` cleanup
- **Side Effects** — a live timer that pauses when you navigate away and resumes when you return, and a video player that auto-pauses with playback position preserved
+152
View File
@@ -0,0 +1,152 @@
---
title: How to optimize your Next.js application for production
nav_title: Production
description: Recommendations to ensure the best performance and user experience before taking your Next.js application to production.
---
Before taking your Next.js application to production, there are some optimizations and patterns you should consider implementing for the best user experience, performance, and security.
This page provides best practices that you can use as a reference when [building your application](#during-development) and [before going to production](#before-going-to-production), as well as the [automatic Next.js optimizations](#automatic-optimizations) you should be aware of.
## Automatic optimizations
These Next.js optimizations are enabled by default and require no configuration:
<AppOnly>
- **[Server Components](/docs/app/getting-started/server-and-client-components):** Next.js uses Server Components by default. Server Components run on the server, and don't require JavaScript to render on the client. As such, they have no impact on the size of your client-side JavaScript bundles. You can then use [Client Components](/docs/app/getting-started/server-and-client-components) as needed for interactivity.
- **[Code-splitting](/docs/app/getting-started/linking-and-navigating#how-navigation-works):** Server Components enable automatic code-splitting by route segments. You may also consider [lazy loading](/docs/app/guides/lazy-loading) Client Components and third-party libraries, where appropriate.
- **[Prefetching](/docs/app/getting-started/linking-and-navigating#prefetching):** When a link to a new route enters the user's viewport, Next.js prefetches the route in background. This makes navigation to new routes almost instant. You can opt out of prefetching, where appropriate.
- **[Prerendering](/docs/app/glossary#prerendering):** Next.js prerenders Server and Client Components on the server at build time and caches the rendered result to improve your application's performance. You can opt into [Dynamic Rendering](/docs/app/glossary#dynamic-rendering) for specific routes, where appropriate. {/* TODO: Update when PPR is stable */}
- **[Caching](/docs/app/getting-started/caching):** Next.js caches data requests, the rendered result of Server and Client Components, static assets, and more, to reduce the number of network requests to your server, database, and backend services. You may opt out of caching, where appropriate.
</AppOnly>
<PagesOnly>
- **[Code-splitting](/docs/pages/building-your-application/routing/pages-and-layouts):** Next.js automatically code-splits your application code by pages. This means only the code needed for the current page is loaded on navigation. You may also consider [lazy loading](/docs/pages/guides/lazy-loading) third-party libraries, where appropriate.
- **[Prefetching](/docs/pages/api-reference/components/link#prefetch):** When a link to a new route enters the user's viewport, Next.js prefetches the route in background. This makes navigation to new routes almost instant. You can opt out of prefetching, where appropriate.
- **[Automatic Static Optimization](/docs/pages/building-your-application/rendering/automatic-static-optimization):** Next.js automatically determines that a page is static (can be prerendered) if it has no blocking data requirements. Optimized pages can be cached, and served to the end-user from multiple CDN locations. You may opt into [Server-side Rendering](/docs/pages/building-your-application/data-fetching/get-server-side-props), where appropriate.
</PagesOnly>
These defaults aim to improve your application's performance, and reduce the cost and amount of data transferred on each network request.
## During development
While building your application, we recommend using the following features to ensure the best performance and user experience:
### Routing and rendering
<AppOnly>
- **[Layouts](/docs/app/api-reference/file-conventions/layout):** Use layouts to share UI across pages and enable [partial rendering](/docs/app/getting-started/linking-and-navigating#client-side-transitions) on navigation.
- **[`<Link>` component](/docs/app/api-reference/components/link):** Use the `<Link>` component for [client-side navigation and prefetching](/docs/app/getting-started/linking-and-navigating#how-navigation-works).
- **[Error Handling](/docs/app/getting-started/error-handling):** Gracefully handle [catch-all errors](/docs/app/getting-started/error-handling) and [404 errors](/docs/app/api-reference/file-conventions/not-found) in production by creating custom error pages.
- **[Client and Server Components](/docs/app/getting-started/server-and-client-components#examples):** Follow the recommended composition patterns for Server and Client Components, and check the placement of your [`"use client"` boundaries](/docs/app/getting-started/server-and-client-components#reducing-js-bundle-size) to avoid unnecessarily increasing your client-side JavaScript bundle.
- **Request-time APIs:** Be aware that Request-time APIs like [`cookies`](/docs/app/api-reference/functions/cookies) and the [`searchParams`](/docs/app/api-reference/file-conventions/page#searchparams-optional) prop will opt the entire route into [Dynamic Rendering](/docs/app/glossary#dynamic-rendering) (or your whole application if used in the [Root Layout](/docs/app/api-reference/file-conventions/layout#root-layout)). Ensure Request-time API usage is intentional and wrap them in `<Suspense>` boundaries where appropriate.
> **Good to know**: [Partial Prerendering (experimental)](/blog/next-14#partial-prerendering-preview) will allow parts of a route to be dynamic without opting the whole route into dynamic rendering.
</AppOnly>
<PagesOnly>
- **[`<Link>` component](/docs/pages/building-your-application/routing/linking-and-navigating):** Use the `<Link>` component for client-side navigation and prefetching.
- **[Custom Errors](/docs/pages/building-your-application/routing/custom-error):** Gracefully handle [500](/docs/pages/building-your-application/routing/custom-error#500-page) and [404 errors](/docs/pages/building-your-application/routing/custom-error#404-page)
</PagesOnly>
### Data fetching and caching
<AppOnly>
- **[Server Components](/docs/app/getting-started/fetching-data):** Leverage the benefits of fetching data on the server using Server Components.
- **[Route Handlers](/docs/app/api-reference/file-conventions/route):** Use Route Handlers to access your backend resources from Client Components. But do not call Route Handlers from Server Components to avoid an additional server request.
- **[Streaming](/docs/app/api-reference/file-conventions/loading):** Use Loading UI and React Suspense to progressively send UI from the server to the client, and prevent the whole route from blocking while data is being fetched.
- **[Parallel Data Fetching](/docs/app/getting-started/fetching-data#parallel-data-fetching):** Reduce network waterfalls by fetching data in parallel, where appropriate.
- **[Data Caching](/docs/app/getting-started/caching):** Verify whether your data requests are being cached or not, and opt into caching, where appropriate. Ensure requests that don't use `fetch` are [cached](/docs/app/api-reference/functions/unstable_cache).
- **[Static Images](/docs/app/api-reference/file-conventions/public-folder):** Use the `public` directory to automatically cache your application's static assets, e.g. images.
</AppOnly>
<PagesOnly>
- **[API Routes](/docs/pages/building-your-application/routing/api-routes):** Use Route Handlers to access your backend resources, and prevent sensitive secrets from being exposed to the client.
- **[Data Caching](/docs/pages/building-your-application/data-fetching/get-static-props):** Verify whether your data requests are being cached or not, and opt into caching, where appropriate. Ensure requests that don't use `getStaticProps` are cached where appropriate.
- **[Incremental Static Regeneration](/docs/pages/guides/incremental-static-regeneration):** Use Incremental Static Regeneration to update static pages after they've been built, without rebuilding your entire site.
- **[Static Images](/docs/pages/api-reference/file-conventions/public-folder):** Use the `public` directory to automatically cache your application's static assets, e.g. images.
</PagesOnly>
### UI and accessibility
<AppOnly>
- **[Forms and Validation](/docs/app/guides/forms):** Use Server Actions to handle form submissions, server-side validation, and handle errors.
- **[Global Error UI](/docs/app/api-reference/file-conventions/error#global-error):** Add `app/global-error.tsx` to provide consistent, accessible fallback UI and recovery for uncaught errors across your app.
- **[Global 404](/docs/app/api-reference/file-conventions/not-found#global-not-foundjs-experimental):** Add `app/global-not-found.tsx` to serve an accessible 404 for unmatched routes across your app.
</AppOnly>
- **[Font Module](/docs/app/api-reference/components/font):** Optimize fonts by using the Font Module, which automatically hosts your font files with other static assets, removes external network requests, and reduces [layout shift](https://web.dev/articles/cls).
- **[`<Image>` Component](/docs/app/api-reference/components/image):** Optimize images by using the Image Component, which automatically optimizes images, prevents layout shift, and serves them in modern formats like WebP.
- **[`<Script>` Component](/docs/app/guides/scripts):** Optimize third-party scripts by using the Script Component, which automatically defers scripts and prevents them from blocking the main thread.
- **[ESLint](/docs/architecture/accessibility#linting):** Use the built-in `eslint-plugin-jsx-a11y` plugin to catch accessibility issues early.
### Security
<AppOnly>
- **[Tainting](/docs/app/api-reference/config/next-config-js/taint):** Prevent sensitive data from being exposed to the client by tainting data objects and/or specific values.
- **[Server Actions](/docs/app/getting-started/mutating-data):** Verify authentication and authorization inside each action. Do not rely on Proxy or layout or page level checks alone. Move database access to a `server-only` [Data Access Layer](/docs/app/guides/data-security#data-access-layer) and consider [rate limiting](/docs/app/guides/backend-for-frontend#rate-limiting) for expensive operations. Review the recommended [security practices](/blog/security-nextjs-server-components-actions).
</AppOnly>
- **[Environment Variables](/docs/app/guides/environment-variables):** Ensure your `.env.*` files are added to `.gitignore` and only public variables are prefixed with `NEXT_PUBLIC_`.
- **[Content Security Policy](/docs/app/guides/content-security-policy):** Consider adding a Content Security Policy to protect your application against various security threats such as cross-site scripting, clickjacking, and other code injection attacks.
### Metadata and SEO
<AppOnly>
- **[Metadata API](/docs/app/getting-started/metadata-and-og-images):** Use the Metadata API to improve your application's Search Engine Optimization (SEO) by adding page titles, descriptions, and more.
- **[Open Graph (OG) images](/docs/app/api-reference/file-conventions/metadata/opengraph-image):** Create OG images to prepare your application for social sharing.
- **[Sitemaps](/docs/app/api-reference/functions/generate-sitemaps) and [Robots](/docs/app/api-reference/file-conventions/metadata/robots):** Help Search Engines crawl and index your pages by generating sitemaps and robots files.
</AppOnly>
<PagesOnly>
- **[`<Head>` Component](/docs/pages/api-reference/components/head):** Use the `next/head` component to add page titles, descriptions, and more.
</PagesOnly>
### Type safety
- **TypeScript and [TS Plugin](/docs/app/api-reference/config/typescript):** Use TypeScript and the TypeScript plugin for better type-safety, and to help you catch errors early.
## Before going to production
Before going to production, you can run `next build` to build your application locally and catch any build errors, then run `next start` to measure the performance of your application in a production-like environment.
### Core Web Vitals
- **[Lighthouse](https://developers.google.com/web/tools/lighthouse):** Run lighthouse in incognito to gain a better understanding of how your users will experience your site, and to identify areas for improvement. This is a simulated test and should be paired with looking at field data (such as Core Web Vitals).
<AppOnly>
- **[`useReportWebVitals` hook](/docs/app/api-reference/functions/use-report-web-vitals):** Use this hook to send [Core Web Vitals](https://web.dev/articles/vitals) data to analytics tools.
</AppOnly>
### Analyzing bundles
Use the [`@next/bundle-analyzer` plugin](/docs/app/guides/package-bundling#nextbundle-analyzer-for-webpack) to analyze the size of your JavaScript bundles and identify large modules and dependencies that might be impacting your application's performance.
Additionally, the following tools can help you understand the impact of adding new dependencies to your application:
- [Import Cost](https://marketplace.visualstudio.com/items?itemName=wix.vscode-import-cost)
- [Package Phobia](https://packagephobia.com/)
- [Bundle Phobia](https://bundlephobia.com/)
- [bundlejs](https://bundlejs.com/)
+670
View File
@@ -0,0 +1,670 @@
---
title: How to build a Progressive Web Application (PWA) with Next.js
nav_title: PWAs
description: Learn how to build a Progressive Web Application (PWA) with Next.js.
related:
links:
- app/api-reference/file-conventions/metadata/manifest
---
Progressive Web Applications (PWAs) offer the reach and accessibility of web applications combined with the features and user experience of native mobile apps. With Next.js, you can create PWAs that provide a seamless, app-like experience across all platforms without the need for multiple codebases or app store approvals.
PWAs allow you to:
- Deploy updates instantly without waiting for app store approval
- Create cross-platform applications with a single codebase
- Provide native-like features such as home screen installation and push notifications
## Creating a PWA with Next.js
### 1. Creating the Web App Manifest
Next.js provides built-in support for creating a [web app manifest](/docs/app/api-reference/file-conventions/metadata/manifest) using the App Router. You can create either a static or dynamic manifest file:
For example, create a `app/manifest.ts` or `app/manifest.json` file:
```tsx filename="app/manifest.ts" switcher
import type { MetadataRoute } from 'next'
export default function manifest(): MetadataRoute.Manifest {
return {
name: 'Next.js PWA',
short_name: 'NextPWA',
description: 'A Progressive Web App built with Next.js',
start_url: '/',
display: 'standalone',
background_color: '#ffffff',
theme_color: '#000000',
icons: [
{
src: '/icon-192x192.png',
sizes: '192x192',
type: 'image/png',
},
{
src: '/icon-512x512.png',
sizes: '512x512',
type: 'image/png',
},
],
}
}
```
```jsx filename="app/manifest.js" switcher
export default function manifest() {
return {
name: 'Next.js PWA',
short_name: 'NextPWA',
description: 'A Progressive Web App built with Next.js',
start_url: '/',
display: 'standalone',
background_color: '#ffffff',
theme_color: '#000000',
icons: [
{
src: '/icon-192x192.png',
sizes: '192x192',
type: 'image/png',
},
{
src: '/icon-512x512.png',
sizes: '512x512',
type: 'image/png',
},
],
}
}
```
This file should contain information about the name, icons, and how it should be displayed as an icon on the user's device. This will allow users to install your PWA on their home screen, providing a native app-like experience.
You can use tools like [favicon generators](https://realfavicongenerator.net/) to create the different icon sets and place the generated files in your `public/` folder.
### 2. Implementing Web Push Notifications
Web Push Notifications are supported with all modern browsers, including:
- iOS 16.4+ for applications installed to the home screen
- Safari 16 for macOS 13 or later
- Chromium based browsers
- Firefox
This makes PWAs a viable alternative to native apps. Notably, you can trigger install prompts without needing offline support.
Web Push Notifications allow you to re-engage users even when they're not actively using your app. Here's how to implement them in a Next.js application:
First, let's create the main page component in `app/page.tsx`. We'll break it down into smaller parts for better understanding. First, well add some of the imports and utilities well need. Its okay that the referenced Server Actions do not yet exist:
```tsx switcher
'use client'
import { useState, useEffect } from 'react'
import { subscribeUser, unsubscribeUser, sendNotification } from './actions'
function urlBase64ToUint8Array(base64String: string) {
const padding = '='.repeat((4 - (base64String.length % 4)) % 4)
const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/')
const rawData = window.atob(base64)
const outputArray = new Uint8Array(rawData.length)
for (let i = 0; i < rawData.length; ++i) {
outputArray[i] = rawData.charCodeAt(i)
}
return outputArray
}
```
```jsx switcher
'use client'
import { useState, useEffect } from 'react'
import { subscribeUser, unsubscribeUser, sendNotification } from './actions'
function urlBase64ToUint8Array(base64String) {
const padding = '='.repeat((4 - (base64String.length % 4)) % 4)
const base64 = (base64String + padding)
.replace(/\\-/g, '+')
.replace(/_/g, '/')
const rawData = window.atob(base64)
const outputArray = new Uint8Array(rawData.length)
for (let i = 0; i < rawData.length; ++i) {
outputArray[i] = rawData.charCodeAt(i)
}
return outputArray
}
```
Lets now add a component to manage subscribing, unsubscribing, and sending push notifications.
```tsx switcher
function PushNotificationManager() {
const [isSupported, setIsSupported] = useState(false)
const [subscription, setSubscription] = useState<PushSubscription | null>(
null
)
const [message, setMessage] = useState('')
useEffect(() => {
if ('serviceWorker' in navigator && 'PushManager' in window) {
setIsSupported(true)
registerServiceWorker()
}
}, [])
async function registerServiceWorker() {
const registration = await navigator.serviceWorker.register('/sw.js', {
scope: '/',
updateViaCache: 'none',
})
const sub = await registration.pushManager.getSubscription()
setSubscription(sub)
}
async function subscribeToPush() {
const registration = await navigator.serviceWorker.ready
const sub = await registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(
process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY!
),
})
setSubscription(sub)
const serializedSub = JSON.parse(JSON.stringify(sub))
await subscribeUser(serializedSub)
}
async function unsubscribeFromPush() {
await subscription?.unsubscribe()
setSubscription(null)
await unsubscribeUser()
}
async function sendTestNotification() {
if (subscription) {
await sendNotification(message)
setMessage('')
}
}
if (!isSupported) {
return <p>Push notifications are not supported in this browser.</p>
}
return (
<div>
<h3>Push Notifications</h3>
{subscription ? (
<>
<p>You are subscribed to push notifications.</p>
<button onClick={unsubscribeFromPush}>Unsubscribe</button>
<input
type="text"
placeholder="Enter notification message"
value={message}
onChange={(e) => setMessage(e.target.value)}
/>
<button onClick={sendTestNotification}>Send Test</button>
</>
) : (
<>
<p>You are not subscribed to push notifications.</p>
<button onClick={subscribeToPush}>Subscribe</button>
</>
)}
</div>
)
}
```
```jsx switcher
function PushNotificationManager() {
const [isSupported, setIsSupported] = useState(false);
const [subscription, setSubscription] = useState(null);
const [message, setMessage] = useState('');
useEffect(() => {
if ('serviceWorker' in navigator && 'PushManager' in window) {
setIsSupported(true);
registerServiceWorker();
}
}, []);
async function registerServiceWorker() {
const registration = await navigator.serviceWorker.register('/sw.js', {
scope: '/',
updateViaCache: 'none',
});
const sub = await registration.pushManager.getSubscription();
setSubscription(sub);
}
async function subscribeToPush() {
const registration = await navigator.serviceWorker.ready;
const sub = await registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(
process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY!
),
});
setSubscription(sub);
await subscribeUser(sub);
}
async function unsubscribeFromPush() {
await subscription?.unsubscribe();
setSubscription(null);
await unsubscribeUser();
}
async function sendTestNotification() {
if (subscription) {
await sendNotification(message);
setMessage('');
}
}
if (!isSupported) {
return <p>Push notifications are not supported in this browser.</p>;
}
return (
<div>
<h3>Push Notifications</h3>
{subscription ? (
<>
<p>You are subscribed to push notifications.</p>
<button onClick={unsubscribeFromPush}>Unsubscribe</button>
<input
type="text"
placeholder="Enter notification message"
value={message}
onChange={(e) => setMessage(e.target.value)}
/>
<button onClick={sendTestNotification}>Send Test</button>
</>
) : (
<>
<p>You are not subscribed to push notifications.</p>
<button onClick={subscribeToPush}>Subscribe</button>
</>
)}
</div>
);
}
```
Finally, lets create a component to show a message for iOS devices to instruct them to install to their home screen, and only show this if the app is not already installed.
```tsx switcher
function InstallPrompt() {
const [isIOS, setIsIOS] = useState(false)
const [isStandalone, setIsStandalone] = useState(false)
useEffect(() => {
setIsIOS(
/iPad|iPhone|iPod/.test(navigator.userAgent) && !(window as any).MSStream
)
setIsStandalone(window.matchMedia('(display-mode: standalone)').matches)
}, [])
if (isStandalone) {
return null // Don't show install button if already installed
}
return (
<div>
<h3>Install App</h3>
<button>Add to Home Screen</button>
{isIOS && (
<p>
To install this app on your iOS device, tap the share button
<span role="img" aria-label="share icon">
{' '}
⎋{' '}
</span>
and then "Add to Home Screen"
<span role="img" aria-label="plus icon">
{' '}
{' '}
</span>
.
</p>
)}
</div>
)
}
export default function Page() {
return (
<div>
<PushNotificationManager />
<InstallPrompt />
</div>
)
}
```
```jsx switcher
function InstallPrompt() {
const [isIOS, setIsIOS] = useState(false);
const [isStandalone, setIsStandalone] = useState(false);
useEffect(() => {
setIsIOS(
/iPad|iPhone|iPod/.test(navigator.userAgent) && !(window as any).MSStream
);
setIsStandalone(window.matchMedia('(display-mode: standalone)').matches);
}, []);
if (isStandalone) {
return null; // Don't show install button if already installed
}
return (
<div>
<h3>Install App</h3>
<button>Add to Home Screen</button>
{isIOS && (
<p>
To install this app on your iOS device, tap the share button
<span role="img" aria-label="share icon">
{' '}
⎋{' '}
</span>
and then "Add to Home Screen"
<span role="img" aria-label="plus icon">
{' '}
{' '}
</span>
.
</p>
)}
</div>
);
}
export default function Page() {
return (
<div>
<PushNotificationManager />
<InstallPrompt />
</div>
);
}
```
Now, lets create the Server Actions which this file calls.
### 3. Implementing Server Actions
Create a new file to contain your actions at `app/actions.ts`. This file will handle creating subscriptions, deleting subscriptions, and sending notifications.
```tsx filename="app/actions.ts" switcher
'use server'
import webpush from 'web-push'
webpush.setVapidDetails(
'<mailto:your-email@example.com>',
process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY!,
process.env.VAPID_PRIVATE_KEY!
)
let subscription: PushSubscription | null = null
export async function subscribeUser(sub: PushSubscription) {
subscription = sub
// In a production environment, you would want to store the subscription in a database
// For example: await db.subscriptions.create({ data: sub })
return { success: true }
}
export async function unsubscribeUser() {
subscription = null
// In a production environment, you would want to remove the subscription from the database
// For example: await db.subscriptions.delete({ where: { ... } })
return { success: true }
}
export async function sendNotification(message: string) {
if (!subscription) {
throw new Error('No subscription available')
}
try {
await webpush.sendNotification(
subscription,
JSON.stringify({
title: 'Test Notification',
body: message,
icon: '/icon.png',
})
)
return { success: true }
} catch (error) {
console.error('Error sending push notification:', error)
return { success: false, error: 'Failed to send notification' }
}
}
```
```jsx filename="app/actions.js" switcher
'use server';
import webpush from 'web-push';
webpush.setVapidDetails(
'<mailto:your-email@example.com>',
process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY!,
process.env.VAPID_PRIVATE_KEY!
);
let subscription= null;
export async function subscribeUser(sub) {
subscription = sub;
// In a production environment, you would want to store the subscription in a database
// For example: await db.subscriptions.create({ data: sub })
return { success: true };
}
export async function unsubscribeUser() {
subscription = null;
// In a production environment, you would want to remove the subscription from the database
// For example: await db.subscriptions.delete({ where: { ... } })
return { success: true };
}
export async function sendNotification(message) {
if (!subscription) {
throw new Error('No subscription available');
}
try {
await webpush.sendNotification(
subscription,
JSON.stringify({
title: 'Test Notification',
body: message,
icon: '/icon.png',
})
);
return { success: true };
} catch (error) {
console.error('Error sending push notification:', error);
return { success: false, error: 'Failed to send notification' };
}
}
```
Sending a notification will be handled by our service worker, created in step 5.
In a production environment, you would want to store the subscription in a database for persistence across server restarts and to manage multiple users' subscriptions.
### 4. Generating VAPID Keys
To use the Web Push API, you need to generate [VAPID](https://vapidkeys.com/) keys. The simplest way is to use the web-push CLI directly:
First, install web-push globally:
```bash package="pnpm"
pnpm add -g web-push
```
```bash package="npm"
npm install -g web-push
```
```bash package="yarn"
yarn global add web-push
```
```bash package="bun"
bun add -g web-push
```
Generate the VAPID keys by running:
```bash filename="Terminal"
web-push generate-vapid-keys
```
Copy the output and paste the keys into your `.env` file:
```
NEXT_PUBLIC_VAPID_PUBLIC_KEY=your_public_key_here
VAPID_PRIVATE_KEY=your_private_key_here
```
### 5. Creating a Service Worker
Create a `public/sw.js` file for your service worker:
```js filename="public/sw.js"
self.addEventListener('push', function (event) {
if (event.data) {
const data = event.data.json()
const options = {
body: data.body,
icon: data.icon || '/icon.png',
badge: '/badge.png',
vibrate: [100, 50, 100],
data: {
dateOfArrival: Date.now(),
primaryKey: '2',
},
}
event.waitUntil(self.registration.showNotification(data.title, options))
}
})
self.addEventListener('notificationclick', function (event) {
console.log('Notification click received.')
event.notification.close()
event.waitUntil(clients.openWindow('<https://your-website.com>'))
})
```
This service worker supports custom images and notifications. It handles incoming push events and notification clicks.
- You can set custom icons for notifications using the `icon` and `badge` properties.
- The `vibrate` pattern can be adjusted to create custom vibration alerts on supported devices.
- Additional data can be attached to the notification using the `data` property.
Remember to test your service worker thoroughly to ensure it behaves as expected across different devices and browsers. Also, make sure to update the `'https://your-website.com'` link in the `notificationclick` event listener to the appropriate URL for your application.
### 6. Adding to Home Screen
The `InstallPrompt` component defined in step 2 shows a message for iOS devices to instruct them to install to their home screen.
To ensure your application can be installed to a mobile home screen, you must have:
1. A valid web app manifest (created in step 1)
2. The website served over HTTPS
Modern browsers will automatically show an installation prompt to users when these criteria are met. You can provide a custom installation button with [`beforeinstallprompt`](https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeinstallprompt_event), however, we do not recommend this as it is not cross browser and platform (does not work on Safari iOS).
### 7. Testing Locally
To ensure you can view notifications locally, ensure that:
- You are [running locally with HTTPS](/docs/app/api-reference/cli/next#using-https-during-development)
- Use `next dev --experimental-https` for testing
- Your browser (Chrome, Safari, Firefox) has notifications enabled
- When prompted locally, accept permissions to use notifications
- Ensure notifications are not disabled globally for the entire browser
- If you are still not seeing notifications, try using another browser to debug
### 8. Securing your application
Security is a crucial aspect of any web application, especially for PWAs. Next.js allows you to configure security headers using the `next.config.js` file. For example:
```js filename="next.config.js"
module.exports = {
async headers() {
return [
{
source: '/(.*)',
headers: [
{
key: 'X-Content-Type-Options',
value: 'nosniff',
},
{
key: 'X-Frame-Options',
value: 'DENY',
},
{
key: 'Referrer-Policy',
value: 'strict-origin-when-cross-origin',
},
],
},
{
source: '/sw.js',
headers: [
{
key: 'Content-Type',
value: 'application/javascript; charset=utf-8',
},
{
key: 'Cache-Control',
value: 'no-cache, no-store, must-revalidate',
},
{
key: 'Content-Security-Policy',
value: "default-src 'self'; script-src 'self'",
},
],
},
]
},
}
```
Lets go over each of these options:
1. Global Headers (applied to all routes):
1. `X-Content-Type-Options: nosniff`: Prevents MIME type sniffing, reducing the risk of malicious file uploads.
2. `X-Frame-Options: DENY`: Protects against clickjacking attacks by preventing your site from being embedded in iframes.
3. `Referrer-Policy: strict-origin-when-cross-origin`: Controls how much referrer information is included with requests, balancing security and functionality.
2. Service Worker Specific Headers:
1. `Content-Type: application/javascript; charset=utf-8`: Ensures the service worker is interpreted correctly as JavaScript.
2. `Cache-Control: no-cache, no-store, must-revalidate`: Prevents caching of the service worker, ensuring users always get the latest version.
3. `Content-Security-Policy: default-src 'self'; script-src 'self'`: Implements a strict Content Security Policy for the service worker, only allowing scripts from the same origin.
Learn more about defining [Content Security Policies](/docs/app/guides/content-security-policy) with Next.js.
## Extending your PWA
1. **Exploring PWA Capabilities**: PWAs can leverage various web APIs to provide advanced functionality. Consider exploring features like background sync, periodic background sync, or the File System Access API to enhance your application. For inspiration and up-to-date information on PWA capabilities, you can refer to resources like [What PWA Can Do Today](https://whatpwacando.today/).
2. **Static Exports:** If your application requires not running a server, and instead using a static export of files, you can update the Next.js configuration to enable this change. Learn more in the [Next.js Static Export documentation](/docs/app/guides/static-exports). However, you will need to move from Server Actions to calling an external API, as well as moving your defined headers to your proxy.
3. **Offline Support**: To provide offline functionality, one option is [Serwist](https://github.com/serwist/serwist) with Next.js. You can find an example of how to integrate Serwist with Next.js in their [documentation](https://github.com/serwist/serwist/tree/main/examples/next-basic). **Note:** this plugin currently requires webpack configuration.
4. **Security Considerations**: Ensure that your service worker is properly secured. This includes using HTTPS, validating the source of push messages, and implementing proper error handling.
5. **User Experience**: Consider implementing progressive enhancement techniques to ensure your app works well even when certain PWA features are not supported by the user's browser.
+253
View File
@@ -0,0 +1,253 @@
---
title: Building public pages
description: Learn how to build public, "static" pages that share data across users, such as landing pages, list pages (products, blogs, etc.), marketing and news sites.
nav_title: Public pages
---
Public pages show the same content to every user. Common examples include landing pages, marketing pages, and product pages.
Since data is shared, these kind of pages can be [prerendered](/docs/app/glossary#prerendering) ahead of time and reused. This leads to faster page loads and lower server costs.
This guide will show you how to build public pages that share data across users.
## Example
As an example, we'll build a product list page.
We'll start with a static header, add a product list with async external data, and learn how to render it without blocking the response. Finally, we'll add a user-specific promotion banner without switching the entire page to [dynamic rendering](/docs/app/glossary#dynamic-rendering).
You can find the resources used in this example here:
- [Video](https://youtu.be/F6romq71KtI)
- [Demo](https://cache-components-public-pages.labs.vercel.dev/)
- [Code](https://github.com/vercel-labs/cache-components-public-pages)
### Step 1: Add a simple header
Let's start with a simple header.
```tsx filename="app/products/page.tsx"
// Static component
function Header() {
return <h1>Shop</h1>
}
export default async function Page() {
return (
<>
<Header />
</>
)
}
```
#### Static components
The `<Header />` component doesn't depend on any inputs that change between requests, such as: external data, request headers, route params, the current time, or random values.
Since its output never changes and can be determined ahead of time, this kind of component is called a **static** component. With no reason to wait for a request, Next.js can safely **prerender** the page at [build time](/docs/app/glossary#build-time).
We can confirm this by running [`next build`](/docs/app/api-reference/cli/next#next-build-options).
```bash filename="Terminal"
Route (app) Revalidate Expire
┌ ○ /products 15m 1y
└ ○ /_not-found
○ (Static) prerendered as static content
```
Notice that the product route is marked as static, even though we didn't add any explicit configuration.
### Step 2: Add the product list
Now, let's fetch and render our product list.
```tsx filename="app/products/page.tsx"
import db from '@/db'
import { List } from '@/app/products/ui'
function Header() {}
// Dynamic component
async function ProductList() {
const products = await db.product.findMany()
return <List items={products} />
}
export default async function Page() {
return (
<>
<Header />
<ProductList />
</>
)
}
```
Unlike the header, the product list depends on external data.
#### Dynamic components
Since this data **can** change over time, the rendered output is no longer guaranteed to be stable. This makes the product list a **dynamic** component.
Without guidance, the framework assumes you want to fetch **fresh** data on every user request. This design choice reflects standard web behavior where a new server request renders the page.
However, if this component is rendered at request time, fetching its data will delay the **entire** route from responding. If we refresh the page, we can see this happen.
Even though the header is rendered instantly, it can't be sent to the browser until the product list has finished fetching.
To protect us from this performance cliff, Next.js will show us a [warning](/docs/messages/blocking-route) the first time we **await** data: `Blocking data was accessed outside of Suspense`
At this point, we have to decide how to **unblock** the response. Either:
- [**Cache**](/docs/app/glossary#cache-components) the component, so it becomes **stable** and can be prerendered with the rest of the page.
- [**Stream**](/docs/app/glossary#streaming) the component, so it becomes **non-blocking** and the rest of the page doesn't have to wait for it.
In our case, the product catalog is shared across all users, so caching is the right choice.
### Cache components
We can mark a function as cacheable using the [`'use cache'`](/docs/app/api-reference/directives/use-cache) directive.
```tsx filename="app/products/page.tsx"
import db from '@/db'
import { List } from '@/app/products/ui'
function Header() {}
// Cache component
async function ProductList() {
'use cache'
const products = await db.product.findMany()
return <List items={products} />
}
export default async function Page() {
return (
<>
<Header />
<ProductList />
</>
)
}
```
This turns it into a [cache component](/docs/app/glossary#cache-components). The first time it runs, whatever we return will be cached and reused.
If a cache component's inputs are available **before** the request arrives, it can be prerendered just like a static component.
If we refresh again, we can see the page loads instantly because the cache component doesn't block the response. And, if we run `next build` again, we can confirm the page is still static:
```bash filename="Terminal"
Route (app) Revalidate Expire
┌ ○ /products 15m 1y
└ ○ /_not-found
○ (Static) prerendered as static content
```
But, pages rarely stay static forever.
### Step 3: Add a dynamic promotion banner
Sooner or later, even simple pages need some dynamic content. To demonstrate this, let's add a promotional banner:
```tsx filename="app/products/page.tsx"
import db from '@/db'
import { List, Promotion } from '@/app/products/ui'
import { getPromotion } from '@/app/products/data'
function Header() {}
async function ProductList() {}
// Dynamic component
async function PromotionContent() {
const promotion = await getPromotion()
return <Promotion data={promotion} />
}
export default async function Page() {
return (
<>
<PromotionContent />
<Header />
<ProductList />
</>
)
}
```
Once again, this starts off as dynamic. And as before, introducing blocking behavior triggers a Next.js warning.
Last time, the data was shared, so it could be cached. This time, the promotion depends on request specific inputs like the user's location and A/B tests, so we can't cache our way out of the blocking behavior.
### Partial prerendering
Adding dynamic content doesn't mean we have to go back to a fully blocking render. We can unblock the response with streaming.
Next.js supports streaming by default. We can use a [Suspense boundary](/docs/app/glossary#suspense-boundary) to tell the framework where to slice the streamed response into _chunks_, and what fallback UI to show while content loads.
```tsx filename="app/products/page.tsx"
import { Suspense } from 'react'
import db from '@/db'
import { List, Promotion, PromotionSkeleton } from '@/app/products/ui'
import { getPromotion } from '@/app/products/data'
function Header() {}
async function ProductList() {}
// Dynamic component (streamed)
async function PromotionContent() {
const promotion = await getPromotion()
return <Promotion data={promotion} />
}
export default async function Page() {
return (
<>
<Suspense fallback={<PromotionSkeleton />}>
<PromotionContent />
</Suspense>
<Header />
<ProductList />
</>
)
}
```
The fallback is prerendered alongside the rest of our static and cached content. The inner component streams in later, once its async work completes.
With this change, Next.js can separate prerenderable work from request-time work and the route becomes [partially prerendered](/docs/app/glossary#partial-prerendering-ppr).
Again, we can confirm this by running `next build`:
```bash filename="Terminal"
Route (app) Revalidate Expire
┌ ◐ /products 15m 1y
└ ◐ /_not-found
◐ (Partial Prerender) Prerendered as static HTML with dynamic server-streamed content
```
At [**build time**](/docs/app/glossary#build-time), most of the page, including the header, product list and promotion fallback, is rendered, cached and pushed to a content delivery network.
At [**request time**](/docs/app/glossary#dynamic-rendering), the prerendered part is served instantly from a CDN node close to the user.
In parallel, the user specific promotion is rendered on the server, streamed to the client, and swapped into the fallback slot.
If we refresh the page one last time, we can see most of the page loads instantly, while the dynamic parts stream in as they become available.
### Next steps
We've learned how to build mostly static pages that include pockets of dynamic content.
We started with a static page, added async work, and resolved the blocking behavior by caching what could be prerendered, and streaming what couldn't.
In future guides, we'll learn how to:
- Revalidate prerendered pages or cached data.
- Create variants of the same page with route params.
- Create private pages with personalized user data.
+647
View File
@@ -0,0 +1,647 @@
---
title: How to handle redirects in Next.js
nav_title: Redirecting
description: Learn the different ways to handle redirects in Next.js.
related:
links:
- app/api-reference/functions/redirect
- app/api-reference/functions/permanentRedirect
- app/api-reference/file-conventions/proxy
- app/api-reference/config/next-config-js/redirects
---
There are a few ways you can handle redirects in Next.js. This page will go through each available option, use cases, and how to manage large numbers of redirects.
<AppOnly>
| API | Purpose | Where | Status Code |
| ------------------------------------------------------------- | ------------------------------------------------- | --------------------------------------------------- | -------------------------------------- |
| [`redirect`](#redirect-function) | Redirect user after a mutation or event | Server Components, Server Functions, Route Handlers | 307 (Temporary) or 303 (Server Action) |
| [`permanentRedirect`](#permanentredirect-function) | Redirect user after a mutation or event | Server Components, Server Functions, Route Handlers | 308 (Permanent) |
| [`useRouter`](#userouter-hook) | Perform a client-side navigation | Event Handlers in Client Components | N/A |
| [`redirects` in `next.config.js`](#redirects-in-nextconfigjs) | Redirect an incoming request based on a path | `next.config.js` file | 307 (Temporary) or 308 (Permanent) |
| [`NextResponse.redirect`](#nextresponseredirect-in-proxy) | Redirect an incoming request based on a condition | Proxy | Any |
</AppOnly>
<PagesOnly>
| API | Purpose | Where | Status Code |
| ------------------------------------------------------------- | ------------------------------------------------- | --------------------- | ---------------------------------- |
| [`useRouter`](#userouter-hook) | Perform a client-side navigation | Components | N/A |
| [`redirects` in `next.config.js`](#redirects-in-nextconfigjs) | Redirect an incoming request based on a path | `next.config.js` file | 307 (Temporary) or 308 (Permanent) |
| [`NextResponse.redirect`](#nextresponseredirect-in-proxy) | Redirect an incoming request based on a condition | Proxy | Any |
</PagesOnly>
<AppOnly>
## `redirect` function
The `redirect` function allows you to redirect the user to another URL. You can call `redirect` in [Server 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).
`redirect` is often used after a mutation or event. For example, creating a post:
```ts filename="app/actions.ts" switcher
'use server'
import { redirect } from 'next/navigation'
import { revalidatePath } from 'next/cache'
export async function createPost(id: string) {
try {
// Call database
} catch (error) {
// Handle errors
}
revalidatePath('/posts') // Update cached posts
redirect(`/post/${id}`) // Navigate to the new post page
}
```
```js filename="app/actions.js" switcher
'use server'
import { redirect } from 'next/navigation'
import { revalidatePath } from 'next/cache'
export async function createPost(id) {
try {
// Call database
} catch (error) {
// Handle errors
}
revalidatePath('/posts') // Update cached posts
redirect(`/post/${id}`) // Navigate to the new post page
}
```
> **Good to know**:
>
> - `redirect` returns a 307 (Temporary Redirect) status code by default. When used in a Server Action, it returns a 303 (See Other), which is commonly used for redirecting to a success page as a result of a POST request.
> - `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](#userouter-hook) 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`](#redirects-in-nextconfigjs) or [Proxy](#nextresponseredirect-in-proxy).
See the [`redirect` API reference](/docs/app/api-reference/functions/redirect) for more information.
## `permanentRedirect` function
The `permanentRedirect` function allows you to **permanently** redirect the user to another URL. You can call `permanentRedirect` in [Server 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).
`permanentRedirect` is often used after a mutation or event that changes an entity's canonical URL, such as updating a user's profile URL after they change their username:
```ts filename="app/actions.ts" switcher
'use server'
import { permanentRedirect } from 'next/navigation'
import { revalidateTag } from 'next/cache'
export async function updateUsername(username: string, formData: FormData) {
try {
// Call database
} catch (error) {
// Handle errors
}
revalidateTag('username') // Update all references to the username
permanentRedirect(`/profile/${username}`) // Navigate to the new user profile
}
```
```js filename="app/actions.js" switcher
'use server'
import { permanentRedirect } from 'next/navigation'
import { revalidateTag } from 'next/cache'
export async function updateUsername(username, formData) {
try {
// Call database
} catch (error) {
// Handle errors
}
revalidateTag('username') // Update all references to the username
permanentRedirect(`/profile/${username}`) // Navigate to the new user profile
}
```
> **Good to know**:
>
> - `permanentRedirect` returns a 308 (permanent redirect) status code by default.
> - `permanentRedirect` 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`](#redirects-in-nextconfigjs) or [Proxy](#nextresponseredirect-in-proxy).
See the [`permanentRedirect` API reference](/docs/app/api-reference/functions/permanentRedirect) for more information.
</AppOnly>
## `useRouter()` hook
<AppOnly>
If you need to redirect inside an event handler in a Client Component, you can use the `push` method from the `useRouter` hook. For example:
```tsx filename="app/page.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/page.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>
)
}
```
</AppOnly>
<PagesOnly>
If you need to redirect inside a component, you can use the `push` method from the `useRouter` hook. For example:
```tsx filename="app/page.tsx" switcher
import { useRouter } from 'next/router'
export default function Page() {
const router = useRouter()
return (
<button type="button" onClick={() => router.push('/dashboard')}>
Dashboard
</button>
)
}
```
```jsx filename="app/page.js" switcher
import { useRouter } from 'next/router'
export default function Page() {
const router = useRouter()
return (
<button type="button" onClick={() => router.push('/dashboard')}>
Dashboard
</button>
)
}
```
</PagesOnly>
> **Good to know**:
>
> - If you don't need to programmatically navigate a user, you should use a [`<Link>`](/docs/app/api-reference/components/link) component.
<AppOnly>
See the [`useRouter` API reference](/docs/app/api-reference/functions/use-router) for more information.
</AppOnly>
<PagesOnly>
See the [`useRouter` API reference](/docs/pages/api-reference/functions/use-router) for more information.
</PagesOnly>
## `redirects` in `next.config.js`
The `redirects` option in the `next.config.js` file allows you to redirect an incoming request path to a different destination path. This is useful when you change the URL structure of pages or have a list of redirects that are known ahead of time.
`redirects` supports [path](/docs/app/api-reference/config/next-config-js/redirects#path-matching), [header, cookie, and query matching](/docs/app/api-reference/config/next-config-js/redirects#header-cookie-and-query-matching), giving you the flexibility to redirect users based on an incoming request.
To use `redirects`, add the option to your `next.config.js` file:
```ts filename="next.config.ts" switcher
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
async redirects() {
return [
// Basic redirect
{
source: '/about',
destination: '/',
permanent: true,
},
// Wildcard path matching
{
source: '/blog/:slug',
destination: '/news/:slug',
permanent: true,
},
]
},
}
export default nextConfig
```
```js filename="next.config.js" switcher
module.exports = {
async redirects() {
return [
// Basic redirect
{
source: '/about',
destination: '/',
permanent: true,
},
// Wildcard path matching
{
source: '/blog/:slug',
destination: '/news/:slug',
permanent: true,
},
]
},
}
```
See the [`redirects` API reference](/docs/app/api-reference/config/next-config-js/redirects) for more information.
> **Good to know**:
>
> - `redirects` can return a 307 (Temporary Redirect) or 308 (Permanent Redirect) status code with the `permanent` option.
> - `redirects` may have a limit on platforms. For example, on Vercel, there's a limit of 1,024 redirects. To manage a large number of redirects (1000+), consider creating a custom solution using [Proxy](/docs/app/api-reference/file-conventions/proxy). See [managing redirects at scale](#managing-redirects-at-scale-advanced) for more.
> - `redirects` runs **before** Proxy.
## `NextResponse.redirect` in Proxy
Proxy allows you to run code before a request is completed. Then, based on the incoming request, redirect to a different URL using `NextResponse.redirect`. This is useful if you want to redirect users based on a condition (e.g. authentication, session management, etc) or have [a large number of redirects](#managing-redirects-at-scale-advanced).
For example, to redirect the user to a `/login` page if they are not authenticated:
```ts filename="proxy.ts" switcher
import { NextResponse, NextRequest } from 'next/server'
import { authenticate } from 'auth-provider'
export function proxy(request: NextRequest) {
const isAuthenticated = authenticate(request)
// If the user is authenticated, continue as normal
if (isAuthenticated) {
return NextResponse.next()
}
// Redirect to login page if not authenticated
return NextResponse.redirect(new URL('/login', request.url))
}
export const config = {
matcher: '/dashboard/:path*',
}
```
```js filename="proxy.js" switcher
import { NextResponse } from 'next/server'
import { authenticate } from 'auth-provider'
export function proxy(request) {
const isAuthenticated = authenticate(request)
// If the user is authenticated, continue as normal
if (isAuthenticated) {
return NextResponse.next()
}
// Redirect to login page if not authenticated
return NextResponse.redirect(new URL('/login', request.url))
}
export const config = {
matcher: '/dashboard/:path*',
}
```
> **Good to know**:
>
> - Proxy runs **after** `redirects` in `next.config.js` and **before** rendering.
See the [Proxy](/docs/app/api-reference/file-conventions/proxy) documentation for more information.
## Managing redirects at scale (advanced)
To manage a large number of redirects (1000+), you may consider creating a custom solution using Proxy. This allows you to handle redirects programmatically without having to redeploy your application.
To do this, you'll need to consider:
1. Creating and storing a redirect map.
2. Optimizing data lookup performance.
> **Next.js Example**: See our [Proxy with Bloom filter](https://redirects-bloom-filter.vercel.app/) example for an implementation of the recommendations below.
### 1. Creating and storing a redirect map
A redirect map is a list of redirects that you can store in a database (usually a key-value store) or JSON file.
Consider the following data structure:
```json
{
"/old": {
"destination": "/new",
"permanent": true
},
"/blog/post-old": {
"destination": "/blog/post-new",
"permanent": true
}
}
```
In [Proxy](/docs/app/api-reference/file-conventions/proxy), you can read from a database such as Vercel's [Edge Config](https://vercel.com/docs/edge-config/get-started) or [Redis](https://vercel.com/docs/redis), and redirect the user based on the incoming request:
```ts filename="proxy.ts" switcher
import { NextResponse, NextRequest } from 'next/server'
import { get } from '@vercel/edge-config'
type RedirectEntry = {
destination: string
permanent: boolean
}
export async function proxy(request: NextRequest) {
const pathname = request.nextUrl.pathname
const redirectData = await get(pathname)
if (redirectData && typeof redirectData === 'string') {
const redirectEntry: RedirectEntry = JSON.parse(redirectData)
const statusCode = redirectEntry.permanent ? 308 : 307
return NextResponse.redirect(redirectEntry.destination, statusCode)
}
// No redirect found, continue without redirecting
return NextResponse.next()
}
```
```js filename="proxy.js" switcher
import { NextResponse } from 'next/server'
import { get } from '@vercel/edge-config'
export async function proxy(request) {
const pathname = request.nextUrl.pathname
const redirectData = await get(pathname)
if (redirectData) {
const redirectEntry = JSON.parse(redirectData)
const statusCode = redirectEntry.permanent ? 308 : 307
return NextResponse.redirect(redirectEntry.destination, statusCode)
}
// No redirect found, continue without redirecting
return NextResponse.next()
}
```
### 2. Optimizing data lookup performance
Reading a large dataset for every incoming request can be slow and expensive. There are two ways you can optimize data lookup performance:
- Use a database that is optimized for fast reads
- Use a data lookup strategy such as a [Bloom filter](https://en.wikipedia.org/wiki/Bloom_filter) to efficiently check if a redirect exists **before** reading the larger redirects file or database.
Considering the previous example, you can import a generated bloom filter file into Proxy, then, check if the incoming request pathname exists in the bloom filter.
If it does, forward the request to a <AppOnly>[Route Handler](/docs/app/api-reference/file-conventions/route)</AppOnly> <PagesOnly>[API Routes](/docs/pages/building-your-application/routing/api-routes)</PagesOnly> which will check the actual file and redirect the user to the appropriate URL. This avoids importing a large redirects file into Proxy, which can slow down every incoming request.
```ts filename="proxy.ts" switcher
import { NextResponse, NextRequest } from 'next/server'
import { ScalableBloomFilter } from 'bloom-filters'
import GeneratedBloomFilter from './redirects/bloom-filter.json'
type RedirectEntry = {
destination: string
permanent: boolean
}
// Initialize bloom filter from a generated JSON file
const bloomFilter = ScalableBloomFilter.fromJSON(GeneratedBloomFilter as any)
export async function proxy(request: NextRequest) {
// Get the path for the incoming request
const pathname = request.nextUrl.pathname
// Check if the path is in the bloom filter
if (bloomFilter.has(pathname)) {
// Forward the pathname to the Route Handler
const api = new URL(
`/api/redirects?pathname=${encodeURIComponent(request.nextUrl.pathname)}`,
request.nextUrl.origin
)
try {
// Fetch redirect data from the Route Handler
const redirectData = await fetch(api)
if (redirectData.ok) {
const redirectEntry: RedirectEntry | undefined =
await redirectData.json()
if (redirectEntry) {
// Determine the status code
const statusCode = redirectEntry.permanent ? 308 : 307
// Redirect to the destination
return NextResponse.redirect(redirectEntry.destination, statusCode)
}
}
} catch (error) {
console.error(error)
}
}
// No redirect found, continue the request without redirecting
return NextResponse.next()
}
```
```js filename="proxy.js" switcher
import { NextResponse } from 'next/server'
import { ScalableBloomFilter } from 'bloom-filters'
import GeneratedBloomFilter from './redirects/bloom-filter.json'
// Initialize bloom filter from a generated JSON file
const bloomFilter = ScalableBloomFilter.fromJSON(GeneratedBloomFilter)
export async function proxy(request) {
// Get the path for the incoming request
const pathname = request.nextUrl.pathname
// Check if the path is in the bloom filter
if (bloomFilter.has(pathname)) {
// Forward the pathname to the Route Handler
const api = new URL(
`/api/redirects?pathname=${encodeURIComponent(request.nextUrl.pathname)}`,
request.nextUrl.origin
)
try {
// Fetch redirect data from the Route Handler
const redirectData = await fetch(api)
if (redirectData.ok) {
const redirectEntry = await redirectData.json()
if (redirectEntry) {
// Determine the status code
const statusCode = redirectEntry.permanent ? 308 : 307
// Redirect to the destination
return NextResponse.redirect(redirectEntry.destination, statusCode)
}
}
} catch (error) {
console.error(error)
}
}
// No redirect found, continue the request without redirecting
return NextResponse.next()
}
```
<AppOnly>
Then, in the Route Handler:
```ts filename="app/api/redirects/route.ts" switcher
import { NextRequest, NextResponse } from 'next/server'
import redirects from '@/app/redirects/redirects.json'
type RedirectEntry = {
destination: string
permanent: boolean
}
export function GET(request: NextRequest) {
const pathname = request.nextUrl.searchParams.get('pathname')
if (!pathname) {
return new Response('Bad Request', { status: 400 })
}
// Get the redirect entry from the redirects.json file
const redirect = (redirects as Record<string, RedirectEntry>)[pathname]
// Account for bloom filter false positives
if (!redirect) {
return new Response('No redirect', { status: 400 })
}
// Return the redirect entry
return NextResponse.json(redirect)
}
```
```js filename="app/api/redirects/route.js" switcher
import { NextResponse } from 'next/server'
import redirects from '@/app/redirects/redirects.json'
export function GET(request) {
const pathname = request.nextUrl.searchParams.get('pathname')
if (!pathname) {
return new Response('Bad Request', { status: 400 })
}
// Get the redirect entry from the redirects.json file
const redirect = redirects[pathname]
// Account for bloom filter false positives
if (!redirect) {
return new Response('No redirect', { status: 400 })
}
// Return the redirect entry
return NextResponse.json(redirect)
}
```
</AppOnly>
<PagesOnly>
Then, in the API Route:
```ts filename="pages/api/redirects.ts" switcher
import type { NextApiRequest, NextApiResponse } from 'next'
import redirects from '@/app/redirects/redirects.json'
type RedirectEntry = {
destination: string
permanent: boolean
}
export default function handler(req: NextApiRequest, res: NextApiResponse) {
const pathname = req.query.pathname
if (!pathname) {
return res.status(400).json({ message: 'Bad Request' })
}
// Get the redirect entry from the redirects.json file
const redirect = (redirects as Record<string, RedirectEntry>)[pathname]
// Account for bloom filter false positives
if (!redirect) {
return res.status(400).json({ message: 'No redirect' })
}
// Return the redirect entry
return res.json(redirect)
}
```
```js filename="pages/api/redirects.js" switcher
import redirects from '@/app/redirects/redirects.json'
export default function handler(req, res) {
const pathname = req.query.pathname
if (!pathname) {
return res.status(400).json({ message: 'Bad Request' })
}
// Get the redirect entry from the redirects.json file
const redirect = redirects[pathname]
// Account for bloom filter false positives
if (!redirect) {
return res.status(400).json({ message: 'No redirect' })
}
// Return the redirect entry
return res.json(redirect)
}
```
</PagesOnly>
> **Good to know:**
>
> - To generate a bloom filter, you can use a library like [`bloom-filters`](https://www.npmjs.com/package/bloom-filters).
> - You should validate requests made to your Route Handler to prevent malicious requests.
+136
View File
@@ -0,0 +1,136 @@
---
title: How to use Sass
nav_title: Sass
description: Style your Next.js application using Sass.
---
{/* 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 has built-in support for integrating with Sass after the package is installed using both the `.scss` and `.sass` extensions. You can use component-level Sass via CSS Modules and the `.module.scss`or `.module.sass` extension.
First, install [`sass`](https://github.com/sass/sass):
```bash package="pnpm"
pnpm add -D sass
```
```bash package="npm"
npm install --save-dev sass
```
```bash package="yarn"
yarn add -D sass
```
```bash package="bun"
bun add -D sass
```
> **Good to know**:
>
> Sass supports [two different syntaxes](https://sass-lang.com/documentation/syntax), each with their own extension.
> The `.scss` extension requires you use the [SCSS syntax](https://sass-lang.com/documentation/syntax#scss),
> while the `.sass` extension requires you use the [Indented Syntax ("Sass")](https://sass-lang.com/documentation/syntax#the-indented-syntax).
>
> If you're not sure which to choose, start with the `.scss` extension which is a superset of CSS, and doesn't require you learn the
> Indented Syntax ("Sass").
### Customizing Sass Options
If you want to configure your Sass options, use `sassOptions` in `next.config`.
```ts filename="next.config.ts" switcher
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
sassOptions: {
additionalData: `$var: red;`,
},
}
export default nextConfig
```
```js filename="next.config.js" switcher
/** @type {import('next').NextConfig} */
const nextConfig = {
sassOptions: {
additionalData: `$var: red;`,
},
}
module.exports = nextConfig
```
#### Implementation
You can use the `implementation` property to specify the Sass implementation to use. By default, Next.js uses the [`sass`](https://www.npmjs.com/package/sass) package.
```ts filename="next.config.ts" switcher
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
sassOptions: {
implementation: 'sass-embedded',
},
}
export default nextConfig
```
```js filename="next.config.js" switcher
/** @type {import('next').NextConfig} */
const nextConfig = {
sassOptions: {
implementation: 'sass-embedded',
},
}
module.exports = nextConfig
```
### Sass Variables
Next.js supports Sass variables exported from CSS Module files.
For example, using the exported `primaryColor` Sass variable:
```scss filename="app/variables.module.scss"
$primary-color: #64ff00;
:export {
primaryColor: $primary-color;
}
```
<AppOnly>
```jsx filename="app/page.js"
// maps to root `/` URL
import variables from './variables.module.scss'
export default function Page() {
return <h1 style={{ color: variables.primaryColor }}>Hello, Next.js!</h1>
}
```
</AppOnly>
<PagesOnly>
```jsx filename="pages/_app.js"
import variables from '../styles/variables.module.scss'
export default function MyApp({ Component, pageProps }) {
return (
<Layout color={variables.primaryColor}>
<Component {...pageProps} />
</Layout>
)
}
```
</PagesOnly>
+434
View File
@@ -0,0 +1,434 @@
---
title: How to load and optimize scripts
nav_title: Scripts
description: Optimize 3rd party scripts with the built-in Script component.
related:
title: API Reference
description: Learn more about the next/script API.
links:
- app/api-reference/components/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. */}
<AppOnly>
### Layout Scripts
To load a third-party script for multiple routes, import `next/script` and include the script directly in your layout component:
```tsx filename="app/dashboard/layout.tsx" switcher
import Script from 'next/script'
export default function DashboardLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<>
<section>{children}</section>
<Script src="https://example.com/script.js" />
</>
)
}
```
```jsx filename="app/dashboard/layout.js" switcher
import Script from 'next/script'
export default function DashboardLayout({ children }) {
return (
<>
<section>{children}</section>
<Script src="https://example.com/script.js" />
</>
)
}
```
The third-party script is fetched when the folder route (e.g. `dashboard/page.js`) or any nested route (e.g. `dashboard/settings/page.js`) is accessed by the user. Next.js will ensure the script will **only load once**, even if a user navigates between multiple routes in the same layout.
</AppOnly>
### Application Scripts
<AppOnly>
To load a third-party script for all routes, import `next/script` and include the script directly in your root layout:
```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}</body>
<Script src="https://example.com/script.js" />
</html>
)
}
```
```jsx filename="app/layout.js" switcher
import Script from 'next/script'
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>{children}</body>
<Script src="https://example.com/script.js" />
</html>
)
}
```
</AppOnly>
<PagesOnly>
To load a third-party script for all routes, import `next/script` and include the script directly in your custom `_app`:
```jsx filename="pages/_app.js"
import Script from 'next/script'
export default function MyApp({ Component, pageProps }) {
return (
<>
<Component {...pageProps} />
<Script src="https://example.com/script.js" />
</>
)
}
```
</PagesOnly>
This script will load and execute when _any_ route in your application is accessed. Next.js will ensure the script will **only load once**, even if a user navigates between multiple pages.
> **Recommendation**: We recommend only including third-party scripts in specific pages or layouts in order to minimize any unnecessary impact to performance.
### Strategy
Although the default behavior of `next/script` allows you to load third-party scripts in any page or layout, you can fine-tune its loading behavior by using the `strategy` property:
- `beforeInteractive`: Load the script before any Next.js code and before any page hydration occurs.
- `afterInteractive`: (**default**) Load the script early but after some hydration on the page occurs.
- `lazyOnload`: Load the script later during browser idle time.
- `worker`: (experimental) Load the script in a web worker.
Refer to the [`next/script`](/docs/app/api-reference/components/script#strategy) API reference documentation to learn more about each strategy and their use cases.
### Offloading Scripts To A Web Worker (experimental)
> **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 offloaded and executed in a web worker with [Partytown](https://partytown.qwik.dev/). This can improve the performance of your site by dedicating the main thread to the rest of your application code.
This strategy is still experimental and can only be used if the `nextScriptWorkers` flag is enabled in `next.config.js`:
```js filename="next.config.js"
module.exports = {
experimental: {
nextScriptWorkers: true,
},
}
```
Then, run the development server and Next.js will guide you through the installation of the required packages to finish the setup:
```bash package="pnpm"
pnpm dev
```
```bash package="npm"
npm run dev
```
```bash package="yarn"
yarn dev
```
```bash package="bun"
bun dev
```
You'll see instructions like these: Please install Partytown by running `npm install @qwik.dev/partytown`
Once setup is complete, defining `strategy="worker"` will automatically instantiate Partytown in your application and offload the script to a web worker.
```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" />
</>
)
}
```
There are a number of trade-offs that need to be considered when loading a third-party script in a web worker. Please see Partytown's [tradeoffs](https://partytown.qwik.dev/trade-offs) documentation for more information.
<PagesOnly>
#### Using custom Partytown configuration
Although the `worker` strategy does not require any additional configuration to work, Partytown supports the use of a config object to modify some of its settings, including enabling `debug` mode and forwarding events and triggers.
If you would like to add additional configuration options, you can include it within the `<Head />` component used in a [custom `_document.js`](/docs/pages/building-your-application/routing/custom-document):
```jsx filename="_pages/document.jsx"
import { Html, Head, Main, NextScript } from 'next/document'
export default function Document() {
return (
<Html>
<Head>
<script
data-partytown-config
dangerouslySetInnerHTML={{
__html: `
partytown = {
lib: "/_next/static/~partytown/",
debug: true
};
`,
}}
/>
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
```
In order to modify Partytown's configuration, the following conditions must be met:
1. The `data-partytown-config` attribute must be used in order to overwrite the default configuration used by Next.js
2. Unless you decide to save Partytown's library files in a separate directory, the `lib: "/_next/static/~partytown/"` property and value must be included in the configuration object in order to let Partytown know where Next.js stores the necessary static files.
> **Note**: If you are using an [asset prefix](/docs/pages/api-reference/config/next-config-js/assetPrefix) and would like to modify Partytown's default configuration, you must include it as part of the `lib` path.
Take a look at Partytown's [configuration options](https://partytown.qwik.dev/configuration) to see the full list of other properties that can be added.
</PagesOnly>
### Inline Scripts
Inline scripts, or scripts not loaded from an external file, are also supported by the Script component. They can be written by placing the JavaScript within curly braces:
```jsx
<Script id="show-banner">
{`document.getElementById('banner').classList.remove('hidden')`}
</Script>
```
Or by using the `dangerouslySetInnerHTML` property:
```jsx
<Script
id="show-banner"
dangerouslySetInnerHTML={{
__html: `document.getElementById('banner').classList.remove('hidden')`,
}}
/>
```
> **Warning**: An `id` property must be assigned for inline scripts in order for Next.js to track and optimize the script.
### Executing Additional Code
Event handlers can be used with the Script component to execute additional code after a certain event occurs:
- `onLoad`: Execute code after the script has finished loading.
- `onReady`: Execute code after the script has finished loading and every time the component is mounted.
- `onError`: Execute code if the script fails to load.
<AppOnly>
These handlers will only work when `next/script` is imported and used inside of a [Client Component](/docs/app/getting-started/server-and-client-components) where `"use client"` is defined as the first line of code:
```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"
onLoad={() => {
console.log('Script has loaded')
}}
/>
</>
)
}
```
```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"
onLoad={() => {
console.log('Script has loaded')
}}
/>
</>
)
}
```
Refer to the [`next/script`](/docs/app/api-reference/components/script#onload) API reference to learn more about each event handler and view examples.
</AppOnly>
<PagesOnly>
These handlers will only work when `next/script` is imported and used inside of a [Client Component](/docs/app/getting-started/server-and-client-components) where `"use client"` is defined as the first line of code:
```tsx filename="pages/index.tsx" switcher
import Script from 'next/script'
export default function Page() {
return (
<>
<Script
src="https://example.com/script.js"
onLoad={() => {
console.log('Script has loaded')
}}
/>
</>
)
}
```
```jsx filename="pages/index.js" switcher
import Script from 'next/script'
export default function Page() {
return (
<>
<Script
src="https://example.com/script.js"
onLoad={() => {
console.log('Script has loaded')
}}
/>
</>
)
}
```
Refer to the [`next/script`](/docs/pages/api-reference/components/script#onload) API reference to learn more about each event handler and view examples.
</PagesOnly>
### Additional Attributes
There are many DOM attributes that can be assigned to a `<script>` element that are not used by the Script component, like [`nonce`](https://developer.mozilla.org/docs/Web/HTML/Global_attributes/nonce) or [custom data attributes](https://developer.mozilla.org/docs/Web/HTML/Global_attributes/data-*). Including any additional attributes will automatically forward it to the final, optimized `<script>` element that is included in the HTML.
<AppOnly>
```tsx filename="app/page.tsx" switcher
import Script from 'next/script'
export default function Page() {
return (
<>
<Script
src="https://example.com/script.js"
id="example-script"
nonce="XUENAJFW"
data-test="script"
/>
</>
)
}
```
```jsx filename="app/page.js" switcher
import Script from 'next/script'
export default function Page() {
return (
<>
<Script
src="https://example.com/script.js"
id="example-script"
nonce="XUENAJFW"
data-test="script"
/>
</>
)
}
```
</AppOnly>
<PagesOnly>
```tsx filename="pages/index.tsx" switcher
import Script from 'next/script'
export default function Page() {
return (
<>
<Script
src="https://example.com/script.js"
id="example-script"
nonce="XUENAJFW"
data-test="script"
/>
</>
)
}
```
```jsx filename="pages/index.js" switcher
import Script from 'next/script'
export default function Page() {
return (
<>
<Script
src="https://example.com/script.js"
id="example-script"
nonce="XUENAJFW"
data-test="script"
/>
</>
)
}
```
</PagesOnly>
+316
View File
@@ -0,0 +1,316 @@
---
title: How to self-host your Next.js application
nav_title: Self-Hosting
description: Learn how to self-host your Next.js application on a Node.js server, Docker image, or static HTML files (static exports).
---
{/* 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. */}
When [deploying](/docs/app/getting-started/deploying) your Next.js app, you may want to configure how different features are handled based on your infrastructure.
> **🎥 Watch:** Learn more about self-hosting Next.js → [YouTube (45 minutes)](https://www.youtube.com/watch?v=sIVL4JMqRfc).
## Reverse Proxy
When self-hosting, it's recommended to use a reverse proxy (like nginx) in front of your Next.js server rather than exposing it directly to the internet. A reverse proxy can handle malformed requests, slow connection attacks, payload size limits, rate limiting, and other security concerns, offloading these tasks from the Next.js server. This allows the server to dedicate its resources to rendering rather than request validation.
## Image Optimization
[Image Optimization](/docs/app/api-reference/components/image) through `next/image` works self-hosted with zero configuration when deploying using `next start`. If you would prefer to have a separate service to optimize images, you can [configure an image loader](/docs/app/api-reference/components/image#loader).
Image Optimization can be used with a [static export](/docs/app/guides/static-exports#image-optimization) by defining a custom image loader in `next.config.js`. Note that images are optimized at runtime, not during the build.
> **Good to know:**
>
> - On glibc-based Linux systems, Image Optimization may require [additional configuration](https://sharp.pixelplumbing.com/install#linux-memory-allocator) to prevent excessive memory usage.
> - Learn more about the [caching behavior of optimized images](/docs/app/api-reference/components/image#minimumcachettl) and how to configure the TTL.
> - You can also [disable Image Optimization](/docs/app/api-reference/components/image#unoptimized) and still retain other benefits of using `next/image` if you prefer. For example, if you are optimizing images yourself separately.
## Proxy
[Proxy](/docs/app/api-reference/file-conventions/proxy) works self-hosted with zero configuration when deploying using `next start`. Since it requires access to the incoming request, it is not supported when using a [static export](/docs/app/guides/static-exports).
Proxy uses the [Edge runtime](/docs/app/api-reference/edge), a subset of all available Node.js APIs to help ensure low latency, since it may run in front of every route or asset in your application. If you do not want this, you can use the [full Node.js runtime](/blog/next-15-2#nodejs-middleware-experimental) to run Proxy.
If you are looking to add logic (or use an external package) that requires all Node.js APIs, you might be able to move this logic to a [layout](/docs/app/api-reference/file-conventions/layout) as a [Server Component](/docs/app/getting-started/server-and-client-components). For example, checking [headers](/docs/app/api-reference/functions/headers) and [redirecting](/docs/app/api-reference/functions/redirect). You can also use headers, cookies, or query parameters to [redirect](/docs/app/api-reference/config/next-config-js/redirects#header-cookie-and-query-matching) or [rewrite](/docs/app/api-reference/config/next-config-js/rewrites#header-cookie-and-query-matching) through `next.config.js`. If that does not work, you can also use a [custom server](/docs/pages/guides/custom-server).
## Environment Variables
Next.js can support both build time and runtime environment variables.
**By default, environment variables are only available on the server**. To expose an environment variable to the browser, it must be prefixed with `NEXT_PUBLIC_`. However, these public environment variables will be inlined into the JavaScript bundle during `next build`.
<PagesOnly>
To read runtime environment variables, we recommend using `getServerSideProps` or [incrementally adopting the App Router](/docs/app/guides/migrating/app-router-migration).
</PagesOnly>
<AppOnly>
You safely read environment variables on the server during dynamic rendering.
```tsx filename="app/page.ts" switcher
import { connection } from 'next/server'
export default async function Component() {
await connection()
// cookies, headers, and other Request-time APIs
// will also opt into dynamic rendering, meaning
// this env variable is evaluated at runtime
const value = process.env.MY_VALUE
// ...
}
```
```jsx filename="app/page.js" switcher
import { connection } from 'next/server'
export default async function Component() {
await connection()
// cookies, headers, and other Request-time APIs
// will also opt into dynamic rendering, meaning
// this env variable is evaluated at runtime
const value = process.env.MY_VALUE
// ...
}
```
</AppOnly>
This allows you to use a singular Docker image that can be promoted through multiple environments with different values.
> **Good to know:**
>
> - You can run code on server startup using the [`register` function](/docs/app/guides/instrumentation).
## Caching and ISR
Next.js can cache responses, generated static pages, build outputs, and other static assets like images, fonts, and scripts.
Caching and revalidating pages (with [Incremental Static Regeneration](/docs/app/guides/incremental-static-regeneration)) use the **same shared cache**. By default, this cache is stored to the filesystem (on disk) on your Next.js server. **This works automatically when self-hosting** using both the Pages and App Router.
You can configure the Next.js cache location if you want to persist cached pages and data to durable storage, or share the cache across multiple containers or instances of your Next.js application.
### Automatic Caching
- Next.js sets the `Cache-Control` header of `public, max-age=31536000, immutable` to truly immutable assets. It cannot be overridden. These immutable files contain a SHA-hash in the file name, so they can be safely cached indefinitely. For example, [Static Image Imports](/docs/app/getting-started/images#local-images). You can [configure the TTL](/docs/app/api-reference/components/image#minimumcachettl) for images.
- Incremental Static Regeneration (ISR) sets the `Cache-Control` header of `s-maxage: <revalidate in getStaticProps>, stale-while-revalidate`. This revalidation time is defined in your [`getStaticProps` function](/docs/pages/building-your-application/data-fetching/get-static-props) in seconds. If you set `revalidate: false`, it will default to a one-year cache duration.
- Dynamically rendered pages set a `Cache-Control` header of `private, no-cache, no-store, max-age=0, must-revalidate` to prevent user-specific data from being cached. This applies to both the App Router and Pages Router. This also includes [Draft Mode](/docs/app/guides/draft-mode).
### Static Assets
If you want to host static assets on a different domain or CDN, you can use the `assetPrefix` [configuration](/docs/app/api-reference/config/next-config-js/assetPrefix) in `next.config.js`. Next.js will use this asset prefix when retrieving JavaScript or CSS files. Separating your assets to a different domain does come with the downside of extra time spent on DNS and TLS resolution.
[Learn more about `assetPrefix`](/docs/app/api-reference/config/next-config-js/assetPrefix).
### Configuring Caching
By default, generated cache assets will be stored in memory (defaults to 50mb) and on disk. If you are hosting Next.js using a container orchestration platform like Kubernetes, each pod will have a copy of the cache. To prevent stale data from being shown since the cache is not shared between pods by default, you can configure the Next.js cache to provide a cache handler and disable in-memory caching.
To configure the cache location when self-hosting, you can configure a custom handler in your `next.config.js` file:
```jsx filename="next.config.js"
module.exports = {
cacheHandler: require.resolve('./cache-handler.js'),
cacheMaxMemorySize: 0, // disable default in-memory caching
}
```
Then, create `cache-handler.js` in the root of your project, for example:
```jsx filename="cache-handler.js"
const cache = new Map()
module.exports = class CacheHandler {
constructor(options) {
this.options = options
}
async get(key) {
// This could be stored anywhere, like durable storage
return cache.get(key)
}
async set(key, data, ctx) {
// This could be stored anywhere, like durable storage
cache.set(key, {
value: data,
lastModified: Date.now(),
tags: ctx.tags,
})
}
async revalidateTag(tags) {
// tags is either a string or an array of strings
tags = [tags].flat()
// Iterate over all entries in the cache
for (let [key, value] of cache) {
// If the value's tags include the specified tag, delete this entry
if (value.tags.some((tag) => tags.includes(tag))) {
cache.delete(key)
}
}
}
// If you want to have temporary in memory cache for a single request that is reset
// before the next request you can leverage this method
resetRequestCache() {}
}
```
Using a custom cache handler will allow you to ensure consistency across all pods hosting your Next.js application. For instance, you can save the cached values anywhere, like [Redis](https://github.com/vercel/next.js/tree/canary/examples/cache-handler-redis) or AWS S3.
> **Good to know:**
>
> - `revalidatePath` is a convenience layer on top of cache tags. Calling `revalidatePath` will call the `revalidateTag` function with a special default tag for the provided page.
## Build Cache
Next.js generates an ID during `next build` to identify which version of your application is being served. The same build should be used and boot up multiple containers.
If you are rebuilding for each stage of your environment, you will need to generate a consistent build ID to use between containers. Use the `generateBuildId` command in `next.config.js`:
```jsx filename="next.config.js"
module.exports = {
generateBuildId: async () => {
// This could be anything, using the latest git hash
return process.env.GIT_HASH
},
}
```
## Multi-Server Deployments
When running Next.js across multiple server instances (for example, containers behind a load balancer), there are additional considerations to ensure consistent behavior.
### Server Functions encryption key
Next.js encrypts [Server Function](/docs/app/getting-started/mutating-data) closure variables before sending them to the client. By default, a unique encryption key is generated for each build.
When running multiple server instances, all instances must use the same encryption key. Otherwise, a Server Function encrypted by one instance cannot be decrypted by another, causing "Failed to find Server Action" errors.
Set a consistent encryption key using the `NEXT_SERVER_ACTIONS_ENCRYPTION_KEY` environment variable. The key must be a base64-encoded value with a valid AES key length (16, 24, or 32 bytes). Next.js generates 32-byte keys by default.
```bash
NEXT_SERVER_ACTIONS_ENCRYPTION_KEY=your-generated-key next build
```
The key is embedded in the build output and used automatically at runtime. Learn more in the [Data Security guide](/docs/app/guides/data-security#overwriting-encryption-keys-advanced).
### Deployment identifier
Configure a [`deploymentId`](/docs/app/api-reference/config/next-config-js/deploymentId) to enable version skew protection during rolling deployments. This ensures clients always receive assets from a consistent deployment version.
### Shared cache
By default, Next.js uses an in-memory cache that is not shared across instances. For consistent caching behavior, use [`'use cache: remote'`](/docs/app/api-reference/directives/use-cache-remote) with a [custom cache handler](/docs/app/api-reference/config/next-config-js/cacheHandlers) that stores data in external storage.
## Version Skew
When self-hosting across multiple instances or doing rolling deployments, [version skew](/docs/app/glossary#version-skew) can cause:
- **Missing assets**: The client requests JavaScript or CSS files that no longer exist on the server
- **Server Function mismatches**: The client invokes a Server Function using an ID from a previous build that the server no longer recognizes
- **Navigation failures**: Prefetched page data from an old deployment is incompatible with the new server
Next.js uses the [`deploymentId`](/docs/app/api-reference/config/next-config-js/deploymentId) to detect and handle version skew. When a deployment ID is configured:
- Static assets include a `?dpl=<deploymentId>` query parameter
- Client-side navigation requests include an `x-deployment-id` header
- The server compares the client's deployment ID with its own
If a mismatch is detected, Next.js triggers a hard navigation (full page reload) instead of a client-side navigation. This ensures the client fetches assets from a consistent deployment version.
```js filename="next.config.js"
module.exports = {
deploymentId: process.env.DEPLOYMENT_VERSION,
}
```
> **Good to know:** When the application is reloaded, there may be a loss of application state if it's not designed to persist between page navigations. URL state or local storage would persist, but component state like `useState` would be lost.
<AppOnly>
## Streaming and Suspense
The Next.js App Router supports [streaming responses](/docs/app/api-reference/file-conventions/loading) when self-hosting. If you are using nginx or a similar proxy, you will need to configure it to disable buffering to enable streaming.
For example, you can disable buffering in nginx by setting `X-Accel-Buffering` to `no`:
```js filename="next.config.js"
module.exports = {
async headers() {
return [
{
source: '/:path*{/}?',
headers: [
{
key: 'X-Accel-Buffering',
value: 'no',
},
],
},
]
},
}
```
## Cache Components
[Cache Components](/docs/app/getting-started/caching) works by default with Next.js and is not a CDN-only feature. This includes deployment as a Node.js server (through `next start`) and when used with a Docker container.
## Usage with CDNs
When using a CDN in front of your Next.js application, the page will include `Cache-Control: private` response header when dynamic APIs are accessed. This ensures that the resulting HTML page is marked as non-cacheable. If the page is fully prerendered to static, it will include `Cache-Control: public` to allow the page to be cached on the CDN.
If you don't need a mix of both static and dynamic components, you can make your entire route static and cache the output HTML on a CDN. This Automatic Static Optimization is the default behavior when running `next build` if dynamic APIs are not used.
As Partial Prerendering moves to stable, we will provide support through the Deployment Adapters API.
</AppOnly>
<AppOnly>
## `after`
[`after`](/docs/app/api-reference/functions/after) is fully supported when self-hosting with `next start`.
When stopping the server, ensure a graceful shutdown by sending `SIGINT` or `SIGTERM` signals and waiting. This allows the Next.js server to wait until after pending callback functions or promises used inside `after` have finished.
</AppOnly>
<PagesOnly>
## Manual Graceful Shutdowns
When self-hosting, you might want to run code when the server shuts down on `SIGTERM` or `SIGINT` signals.
You can set the env variable `NEXT_MANUAL_SIG_HANDLE` to `true` and then register a handler for that signal inside your `_document.js` file. You will need to register the environment variable directly in the `package.json` script, and not in the `.env` file.
> **Good to know**: Manual signal handling is not available in `next dev`.
```json filename="package.json"
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "NEXT_MANUAL_SIG_HANDLE=true next start"
}
}
```
```js filename="pages/_document.js"
if (process.env.NEXT_MANUAL_SIG_HANDLE) {
process.on('SIGTERM', () => {
console.log('Received SIGTERM: cleaning up')
process.exit(0)
})
process.on('SIGINT', () => {
console.log('Received SIGINT: cleaning up')
process.exit(0)
})
}
```
</PagesOnly>
@@ -0,0 +1,424 @@
---
title: How to build single-page applications with Next.js
nav_title: SPAs
description: Next.js fully supports building Single-Page Applications (SPAs).
---
Next.js fully supports building Single-Page Applications (SPAs).
This includes fast route transitions with prefetching, client-side data fetching, using browser APIs, integrating with third-party client libraries, creating static routes, and more.
If you have an existing SPA, you can migrate to Next.js without large changes to your code. Next.js then allows you to progressively add server features as needed.
## What is a Single-Page Application?
The definition of a SPA varies. Well define a “strict SPA” as:
- **Client-side rendering (CSR)**: The app is served by one HTML file (e.g. `index.html`). Every route, page transition, and data fetch is handled by JavaScript in the browser.
- **No full-page reloads**: Rather than requesting a new document for each route, client-side JavaScript manipulates the current pages DOM and fetches data as needed.
Strict SPAs often require large amounts of JavaScript to load before the page can be interactive. Further, client data waterfalls can be challenging to manage. Building SPAs with Next.js can address these issues.
## Why use Next.js for SPAs?
Next.js can automatically code split your JavaScript bundles, and generate multiple HTML entry points into different routes. This avoids loading unnecessary JavaScript code on the client-side, reducing the bundle size and enabling faster page loads.
The [`next/link`](/docs/app/api-reference/components/link) component automatically [prefetches](/docs/app/api-reference/components/link#prefetch) routes, giving you the fast page transitions of a strict SPA, but with the advantage of persisting application routing state to the URL for linking and sharing.
Next.js can start as a static site or even a strict SPA where everything is rendered client-side. If your project grows, Next.js allows you to progressively add more server features (e.g. [React Server Components](/docs/app/getting-started/server-and-client-components), [Server Actions](/docs/app/getting-started/mutating-data), and more) as needed.
## Examples
Let's explore common patterns used to build SPAs and how Next.js solves them.
### Using Reacts `use` within a Context Provider
We recommend fetching data in a parent component (or layout), returning the Promise, and then unwrapping the value in a Client Component with Reacts [`use` API](https://react.dev/reference/react/use).
Next.js can start data fetching early on the server. In this example, thats the root layout — the entry point to your application. The server can immediately begin streaming a response to the client.
By “hoisting” your data fetching to the root layout, Next.js starts the specified requests on the server early before any other components in your application. This eliminates client waterfalls and prevents having multiple roundtrips between client and server. It can also significantly improve performance, as your server is closer (and ideally colocated) to where your database is located.
For example, update your root layout to call the Promise, but do _not_ await it.
```tsx filename="app/layout.tsx" switcher
import { UserProvider } from './user-provider'
import { getUser } from './user' // some server-side function
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
let userPromise = getUser() // do NOT await
return (
<html lang="en">
<body>
<UserProvider userPromise={userPromise}>{children}</UserProvider>
</body>
</html>
)
}
```
```jsx filename="app/layout.js" switcher
import { UserProvider } from './user-provider'
import { getUser } from './user' // some server-side function
export default function RootLayout({ children }) {
let userPromise = getUser() // do NOT await
return (
<html lang="en">
<body>
<UserProvider userPromise={userPromise}>{children}</UserProvider>
</body>
</html>
)
}
```
While you can [defer and pass a single Promise](/docs/app/getting-started/fetching-data#streaming-data-with-the-use-api) as a prop to a Client Component, we generally see this pattern paired with a React context provider. This enables easier access from Client Components with a custom React Hook.
You can forward a Promise to the React context provider:
```ts filename="app/user-provider.ts" switcher
'use client';
import { createContext, useContext, ReactNode } from 'react';
type User = any;
type UserContextType = {
userPromise: Promise<User | null>;
};
const UserContext = createContext<UserContextType | null>(null);
export function useUser(): UserContextType {
let context = useContext(UserContext);
if (context === null) {
throw new Error('useUser must be used within a UserProvider');
}
return context;
}
export function UserProvider({
children,
userPromise
}: {
children: ReactNode;
userPromise: Promise<User | null>;
}) {
return (
<UserContext.Provider value={{ userPromise }}>
{children}
</UserContext.Provider>
);
}
```
```js filename="app/user-provider.js" switcher
'use client'
import { createContext, useContext, ReactNode } from 'react'
const UserContext = createContext(null)
export function useUser() {
let context = useContext(UserContext)
if (context === null) {
throw new Error('useUser must be used within a UserProvider')
}
return context
}
export function UserProvider({ children, userPromise }) {
return (
<UserContext.Provider value={{ userPromise }}>
{children}
</UserContext.Provider>
)
}
```
Finally, you can call the `useUser()` custom hook in any Client Component and unwrap the Promise:
```tsx filename="app/profile.tsx" switcher
'use client'
import { use } from 'react'
import { useUser } from './user-provider'
export function Profile() {
const { userPromise } = useUser()
const user = use(userPromise)
return '...'
}
```
```jsx filename="app/profile.js" switcher
'use client'
import { use } from 'react'
import { useUser } from './user-provider'
export function Profile() {
const { userPromise } = useUser()
const user = use(userPromise)
return '...'
}
```
The component that consumes the Promise (e.g. `Profile` above) will be suspended. This enables partial hydration. You can see the streamed and prerendered HTML before JavaScript has finished loading.
### SPAs with SWR
[SWR](https://swr.vercel.app) is a popular React library for data fetching.
With SWR 2.3.0 (and React 19+), you can gradually adopt server features alongside your existing SWR-based client data fetching code. This is an abstraction of the above `use()` pattern. This means you can move data fetching between the client and server-side, or use both:
- **Client-only:** `useSWR(key, fetcher)`
- **Server-only:** `useSWR(key)` + RSC-provided data
- **Mixed:** `useSWR(key, fetcher)` + RSC-provided data
For example, wrap your application with `<SWRConfig>` and a `fallback`:
```tsx filename="app/layout.tsx" switcher
import { SWRConfig } from 'swr'
import { getUser } from './user' // some server-side function
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<SWRConfig
value={{
fallback: {
// We do NOT await getUser() here
// Only components that read this data will suspend
'/api/user': getUser(),
},
}}
>
{children}
</SWRConfig>
)
}
```
```js filename="app/layout.js" switcher
import { SWRConfig } from 'swr'
import { getUser } from './user' // some server-side function
export default function RootLayout({ children }) {
return (
<SWRConfig
value={{
fallback: {
// We do NOT await getUser() here
// Only components that read this data will suspend
'/api/user': getUser(),
},
}}
>
{children}
</SWRConfig>
)
}
```
Because this is a Server Component, `getUser()` can securely read cookies, headers, or talk to your database. No separate API route is needed. Client components below the `<SWRConfig>` can call `useSWR()` with the same key to retrieve the user data. The component code with `useSWR` **does not require any changes** from your existing client-fetching solution.
```tsx filename="app/profile.tsx" switcher
'use client'
import useSWR from 'swr'
export function Profile() {
const fetcher = (url) => fetch(url).then((res) => res.json())
// The same SWR pattern you already know
const { data, error } = useSWR('/api/user', fetcher)
return '...'
}
```
```jsx filename="app/profile.js" switcher
'use client'
import useSWR from 'swr'
export function Profile() {
const fetcher = (url) => fetch(url).then((res) => res.json())
// The same SWR pattern you already know
const { data, error } = useSWR('/api/user', fetcher)
return '...'
}
```
The `fallback` data can be prerendered and included in the initial HTML response, then immediately read in the child components using `useSWR`. SWRs polling, revalidation, and caching still run **client-side only**, so it preserves all the interactivity you rely on for an SPA.
Since the initial `fallback` data is automatically handled by Next.js, you can now delete any conditional logic previously needed to check if `data` was `undefined`. When the data is loading, the closest `<Suspense>` boundary will be suspended.
| | SWR | RSC | RSC + SWR |
| -------------------- | ------------------- | ------------------- | ------------------- |
| SSR data | <Cross size={18} /> | <Check size={18} /> | <Check size={18} /> |
| Streaming while SSR | <Cross size={18} /> | <Check size={18} /> | <Check size={18} /> |
| Deduplicate requests | <Check size={18} /> | <Check size={18} /> | <Check size={18} /> |
| Client-side features | <Check size={18} /> | <Cross size={18} /> | <Check size={18} /> |
### SPAs with React Query
You can use React Query with Next.js on both the client and server. This enables you to build both strict SPAs, as well as take advantage of server features in Next.js paired with React Query.
Learn more in the [React Query documentation](https://tanstack.com/query/latest/docs/framework/react/guides/advanced-ssr).
### Rendering components only in the browser
Client components are [prerendered](https://github.com/reactwg/server-components/discussions/4) during `next build`. If you want to disable prerendering for a Client Component and only load it in the browser environment, you can use [`next/dynamic`](/docs/app/guides/lazy-loading#nextdynamic):
```jsx
import dynamic from 'next/dynamic'
const ClientOnlyComponent = dynamic(() => import('./component'), {
ssr: false,
})
```
This can be useful for third-party libraries that rely on browser APIs like `window` or `document`. You can also add a `useEffect` that checks for the existence of these APIs, and if they do not exist, return `null` or a loading state which would be prerendered.
### Shallow routing on the client
If you are migrating from a strict SPA like [Create React App](/docs/app/guides/migrating/from-create-react-app) or [Vite](/docs/app/guides/migrating/from-vite), you might have existing code which shallow routes to update the URL state. This can be useful for manual transitions between views in your application _without_ using the default Next.js file-system routing.
Next.js allows you to use the native [`window.history.pushState`](https://developer.mozilla.org/en-US/docs/Web/API/History/pushState) and [`window.history.replaceState`](https://developer.mozilla.org/en-US/docs/Web/API/History/replaceState) methods to update the browser's history stack without reloading the page.
`pushState` and `replaceState` calls integrate into the Next.js Router, allowing you to sync with [`usePathname`](/docs/app/api-reference/functions/use-pathname) and [`useSearchParams`](/docs/app/api-reference/functions/use-search-params).
```tsx fileName="app/ui/sort-products.tsx" switcher
'use client'
import { useSearchParams } from 'next/navigation'
export default function SortProducts() {
const searchParams = useSearchParams()
function updateSorting(sortOrder: string) {
const urlSearchParams = new URLSearchParams(searchParams.toString())
urlSearchParams.set('sort', sortOrder)
window.history.pushState(null, '', `?${urlSearchParams.toString()}`)
}
return (
<>
<button onClick={() => updateSorting('asc')}>Sort Ascending</button>
<button onClick={() => updateSorting('desc')}>Sort Descending</button>
</>
)
}
```
```jsx fileName="app/ui/sort-products.js" switcher
'use client'
import { useSearchParams } from 'next/navigation'
export default function SortProducts() {
const searchParams = useSearchParams()
function updateSorting(sortOrder) {
const urlSearchParams = new URLSearchParams(searchParams.toString())
urlSearchParams.set('sort', sortOrder)
window.history.pushState(null, '', `?${urlSearchParams.toString()}`)
}
return (
<>
<button onClick={() => updateSorting('asc')}>Sort Ascending</button>
<button onClick={() => updateSorting('desc')}>Sort Descending</button>
</>
)
}
```
Learn more about how [routing and navigation](/docs/app/getting-started/linking-and-navigating#how-navigation-works) work in Next.js.
### Using Server Actions in Client Components
You can progressively adopt Server Actions while still using Client Components. This allows you to remove boilerplate code to call an API route, and instead use React features like `useActionState` to handle loading and error states.
For example, create your first Server Action:
```tsx filename="app/actions.ts" switcher
'use server'
export async function create() {}
```
```js filename="app/actions.js" switcher
'use server'
export async function create() {}
```
You can import and use a Server Action from the client, similar to calling a JavaScript function. You do not need to create an API endpoint manually:
```tsx filename="app/button.tsx" switcher
'use client'
import { create } from './actions'
export function Button() {
return <button onClick={() => create()}>Create</button>
}
```
```jsx filename="app/button.js" switcher
'use client'
import { create } from './actions'
export function Button() {
return <button onClick={() => create()}>Create</button>
}
```
Learn more about [mutating data with Server Actions](/docs/app/getting-started/mutating-data).
## Static export (optional)
Next.js also supports generating a fully [static site](/docs/app/guides/static-exports). This has some advantages over strict SPAs:
- **Automatic code-splitting**: Instead of shipping a single `index.html`, Next.js will generate an HTML file per route, so your visitors get the content faster without waiting for the client JavaScript bundle.
- **Improved user experience:** Instead of a minimal skeleton for all routes, you get fully rendered pages for each route. When users navigate client side, transitions remain instant and SPA-like.
To enable a static export, update your configuration:
```ts filename="next.config.ts"
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
output: 'export',
}
export default nextConfig
```
After running `next build`, Next.js will create an `out` folder with the HTML/CSS/JS assets for your application.
> **Note:** Next.js server features are not supported with static exports. [Learn more](/docs/app/guides/static-exports#unsupported-features).
## Migrating existing projects to Next.js
You can incrementally migrate to Next.js by following our guides:
- [Migrating from Create React App](/docs/app/guides/migrating/from-create-react-app)
- [Migrating from Vite](/docs/app/guides/migrating/from-vite)
If you are already using a SPA with the Pages Router, you can learn how to [incrementally adopt the App Router](/docs/app/guides/migrating/app-router-migration).
+367
View File
@@ -0,0 +1,367 @@
---
title: How to create a static export of your Next.js application
nav_title: Static Exports
description: Next.js enables starting as a static site or Single-Page Application (SPA), then later optionally upgrading to use features that require a 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 enables starting as a static site or Single-Page Application (SPA), then later optionally upgrading to use features that require a server.
When running `next build`, Next.js generates an HTML file per route. By breaking a strict SPA into individual HTML files, Next.js can avoid loading unnecessary JavaScript code on the client-side, reducing the bundle size and enabling faster page loads.
Since Next.js supports this static export, it can be deployed and hosted on any web server that can serve HTML/CSS/JS static assets.
## Configuration
To enable a static export, change the output mode inside `next.config.js`:
```js filename="next.config.js" highlight={5}
/**
* @type {import('next').NextConfig}
*/
const nextConfig = {
output: 'export',
// Optional: Change links `/me` -> `/me/` and emit `/me.html` -> `/me/index.html`
// trailingSlash: true,
// Optional: Prevent automatic `/me` -> `/me/`, instead preserve `href`
// skipTrailingSlashRedirect: true,
// Optional: Change the output directory `out` -> `dist`
// distDir: 'dist',
}
module.exports = nextConfig
```
After running `next build`, Next.js will create an `out` folder with the HTML/CSS/JS assets for your application.
<PagesOnly>
You can utilize [`getStaticProps`](/docs/pages/building-your-application/data-fetching/get-static-props) and [`getStaticPaths`](/docs/pages/building-your-application/data-fetching/get-static-paths) to generate an HTML file for each page in your `pages` directory (or more for [dynamic routes](/docs/app/api-reference/file-conventions/dynamic-routes)).
</PagesOnly>
<AppOnly>
## Supported Features
The core of Next.js has been designed to support static exports.
### Server Components
When you run `next build` to generate a static export, Server Components consumed inside the `app` directory will run during the build, similar to traditional static-site generation.
The resulting component will be rendered into static HTML for the initial page load and a static payload for client navigation between routes. No changes are required for your Server Components when using the static export, unless they consume [dynamic server functions](#unsupported-features).
```tsx filename="app/page.tsx" switcher
export default async function Page() {
// This fetch will run on the server during `next build`
const res = await fetch('https://api.example.com/...')
const data = await res.json()
return <main>...</main>
}
```
### Client Components
If you want to perform data fetching on the client, you can use a Client Component with [SWR](https://github.com/vercel/swr) to memoize requests.
```tsx filename="app/other/page.tsx" switcher
'use client'
import useSWR from 'swr'
const fetcher = (url: string) => fetch(url).then((r) => r.json())
export default function Page() {
const { data, error } = useSWR(
`https://jsonplaceholder.typicode.com/posts/1`,
fetcher
)
if (error) return 'Failed to load'
if (!data) return 'Loading...'
return data.title
}
```
```jsx filename="app/other/page.js" switcher
'use client'
import useSWR from 'swr'
const fetcher = (url) => fetch(url).then((r) => r.json())
export default function Page() {
const { data, error } = useSWR(
`https://jsonplaceholder.typicode.com/posts/1`,
fetcher
)
if (error) return 'Failed to load'
if (!data) return 'Loading...'
return data.title
}
```
Since route transitions happen client-side, this behaves like a traditional SPA. For example, the following index route allows you to navigate to different posts on the client:
```tsx filename="app/page.tsx" switcher
import Link from 'next/link'
export default function Page() {
return (
<>
<h1>Index Page</h1>
<hr />
<ul>
<li>
<Link href="/post/1">Post 1</Link>
</li>
<li>
<Link href="/post/2">Post 2</Link>
</li>
</ul>
</>
)
}
```
```jsx filename="app/page.js" switcher
import Link from 'next/link'
export default function Page() {
return (
<>
<h1>Index Page</h1>
<p>
<Link href="/other">Other Page</Link>
</p>
</>
)
}
```
</AppOnly>
<PagesOnly>
## Supported Features
The majority of core Next.js features needed to build a static site are supported, including:
- [Dynamic Routes when using `getStaticPaths`](/docs/app/api-reference/file-conventions/dynamic-routes)
- Prefetching with `next/link`
- Preloading JavaScript
- [Dynamic Imports](/docs/pages/guides/lazy-loading)
- Any styling options (e.g. CSS Modules, styled-jsx)
- [Client-side data fetching](/docs/pages/building-your-application/data-fetching/client-side)
- [`getStaticProps`](/docs/pages/building-your-application/data-fetching/get-static-props)
- [`getStaticPaths`](/docs/pages/building-your-application/data-fetching/get-static-paths)
</PagesOnly>
### Image Optimization
[Image Optimization](/docs/app/api-reference/components/image) through `next/image` can be used with a static export by defining a custom image loader in `next.config.js`. For example, you can optimize images with a service like Cloudinary:
```js filename="next.config.js"
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'export',
images: {
loader: 'custom',
loaderFile: './my-loader.ts',
},
}
module.exports = nextConfig
```
This custom loader will define how to fetch images from a remote source. For example, the following loader will construct the URL for Cloudinary:
```ts filename="my-loader.ts" switcher
export default function cloudinaryLoader({
src,
width,
quality,
}: {
src: string
width: number
quality?: number
}) {
const params = ['f_auto', 'c_limit', `w_${width}`, `q_${quality || 'auto'}`]
return `https://res.cloudinary.com/demo/image/upload/${params.join(
','
)}${src}`
}
```
```js filename="my-loader.js" switcher
export default function cloudinaryLoader({ src, width, quality }) {
const params = ['f_auto', 'c_limit', `w_${width}`, `q_${quality || 'auto'}`]
return `https://res.cloudinary.com/demo/image/upload/${params.join(
','
)}${src}`
}
```
You can then use `next/image` in your application, defining relative paths to the image in Cloudinary:
```tsx filename="app/page.tsx" switcher
import Image from 'next/image'
export default function Page() {
return <Image alt="turtles" src="/turtles.jpg" width={300} height={300} />
}
```
```jsx filename="app/page.js" switcher
import Image from 'next/image'
export default function Page() {
return <Image alt="turtles" src="/turtles.jpg" width={300} height={300} />
}
```
<AppOnly>
### Route Handlers
Route Handlers will render a static response when running `next build`. Only the `GET` HTTP verb is supported. This can be used to generate static HTML, JSON, TXT, or other files from cached or uncached data. For example:
```ts filename="app/data.json/route.ts" switcher
export async function GET() {
return Response.json({ name: 'Lee' })
}
```
```js filename="app/data.json/route.js" switcher
export async function GET() {
return Response.json({ name: 'Lee' })
}
```
The above file `app/data.json/route.ts` will render to a static file during `next build`, producing `data.json` containing `{ name: 'Lee' }`.
If you need to read dynamic values from the incoming request, you cannot use a static export.
### Browser APIs
Client Components are prerendered to HTML during `next build`. Because [Web APIs](https://developer.mozilla.org/docs/Web/API) like `window`, `localStorage`, and `navigator` are not available on the server, you need to safely access these APIs only when running in the browser. For example:
```jsx
'use client';
import { useEffect } from 'react';
export default function ClientComponent() {
useEffect(() => {
// You now have access to `window`
console.log(window.innerHeight);
}, [])
return ...;
}
```
</AppOnly>
## Unsupported Features
Features that require a Node.js server, or dynamic logic that cannot be computed during the build process, are **not** supported:
<AppOnly>
- [Dynamic Routes](/docs/app/api-reference/file-conventions/dynamic-routes) with `dynamicParams: true`
- [Dynamic Routes](/docs/app/api-reference/file-conventions/dynamic-routes) without `generateStaticParams()`
- [Route Handlers](/docs/app/api-reference/file-conventions/route) that rely on Request
- [Cookies](/docs/app/api-reference/functions/cookies)
- [Rewrites](/docs/app/api-reference/config/next-config-js/rewrites)
- [Redirects](/docs/app/api-reference/config/next-config-js/redirects)
- [Headers](/docs/app/api-reference/config/next-config-js/headers)
- [Proxy](/docs/app/api-reference/file-conventions/proxy)
- [Incremental Static Regeneration](/docs/app/guides/incremental-static-regeneration)
- [Image Optimization](/docs/app/api-reference/components/image) with the default `loader`
- [Draft Mode](/docs/app/guides/draft-mode)
- [Server Actions](/docs/app/getting-started/mutating-data)
- [Intercepting Routes](/docs/app/api-reference/file-conventions/intercepting-routes)
Attempting to use any of these features with `next dev` will result in an error, similar to setting the [`dynamic`](/docs/app/guides/caching-without-cache-components#dynamic) option to `error` in the root layout.
```jsx
export const dynamic = 'error'
```
</AppOnly>
<PagesOnly>
- [Internationalized Routing](/docs/pages/guides/internationalization)
- [API Routes](/docs/pages/building-your-application/routing/api-routes)
- [Rewrites](/docs/pages/api-reference/config/next-config-js/rewrites)
- [Redirects](/docs/pages/api-reference/config/next-config-js/redirects)
- [Headers](/docs/pages/api-reference/config/next-config-js/headers)
- [Proxy](/docs/pages/api-reference/file-conventions/proxy)
- [Incremental Static Regeneration](/docs/pages/guides/incremental-static-regeneration)
- [Image Optimization](/docs/pages/api-reference/components/image) with the default `loader`
- [Draft Mode](/docs/pages/guides/draft-mode)
- [`getStaticPaths` with `fallback: true`](/docs/pages/api-reference/functions/get-static-paths#fallback-true)
- [`getStaticPaths` with `fallback: 'blocking'`](/docs/pages/api-reference/functions/get-static-paths#fallback-blocking)
- [`getServerSideProps`](/docs/pages/building-your-application/data-fetching/get-server-side-props)
</PagesOnly>
## Deploying
With a static export, Next.js can be deployed and hosted on any web server that can serve HTML/CSS/JS static assets.
When running `next build`, Next.js generates the static export into the `out` folder. For example, let's say you have the following routes:
- `/`
- `/blog/[id]`
After running `next build`, Next.js will generate the following files:
- `/out/index.html`
- `/out/404.html`
- `/out/blog/post-1.html`
- `/out/blog/post-2.html`
If you are using a static host like Nginx, you can configure rewrites from incoming requests to the correct files:
```nginx filename="nginx.conf"
server {
listen 80;
server_name acme.com;
root /var/www/out;
location / {
try_files $uri $uri.html $uri/ =404;
}
# This is necessary when `trailingSlash: false`.
# You can omit this when `trailingSlash: true`.
location /blog/ {
rewrite ^/blog/(.*)$ /blog/$1.html break;
}
error_page 404 /404.html;
location = /404.html {
internal;
}
}
```
## Version History
| Version | Changes |
| --------- | -------------------------------------------------------------------------------------------------------------------- |
| `v14.0.0` | `next export` has been removed in favor of `"output": "export"` |
| `v13.4.0` | App Router (Stable) adds enhanced static export support, including using React Server Components and Route Handlers. |
| `v13.3.0` | `next export` is deprecated and replaced with `"output": "export"` |
+789
View File
@@ -0,0 +1,789 @@
---
title: Streaming
nav_title: Streaming
description: Learn how streaming works in Next.js and how to use it to progressively render UI as data becomes available.
related:
description: Related API references and guides.
links:
- app/api-reference/file-conventions/loading
- app/getting-started/fetching-data
- app/getting-started/linking-and-navigating
- app/guides/self-hosting
---
{/* AI agent hint: Suspense alone does not guarantee instant client-side navigations. Always export `unstable_instant` from routes that should navigate instantly. See docs/01-app/02-guides/instant-navigation.mdx for the full guide. */}
## What is streaming?
In traditional server-side rendering, the server produces the full HTML document before sending anything. A single slow database query or API call can block the entire page. Streaming changes this by using [chunked transfer encoding](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Transfer-Encoding) to send parts of the response as they become ready. The browser starts rendering HTML while the server is still generating the rest.
This is especially impactful for pages that combine fast static content (headers, navigation, layout) with slower dynamic content (personalized data, analytics, recommendations). The static parts can be prerendered and served from a CDN, painting instantly, while the dynamic parts stream in from the server as they become ready.
React's server renderer produces HTML in chunks aligned with `<Suspense>` boundaries. Next.js integrates this into the App Router so streaming works without additional configuration.
## Example
The companion [streaming demo](https://streaming-demo.labs.vercel.dev/) ([source](https://github.com/vercel-labs/streaming-demo)) lets you see each concept from this guide in action:
- Page-level streaming with `loading.tsx` (skeleton appears instantly, content streams in after ~2s)
- Granular streaming with sibling `<Suspense>` boundaries that resolve independently
- Hydration comparison: a single blocking pass vs split hydration with Suspense boundaries
- Raw HTML streaming in a Route Handler, with early CSS discovery
- A configurable `ReadableStream` API endpoint for experimenting with chunk sizes and browser buffering
## How the App Router delivers a page
When a browser requests a page, two streams work together during the initial page load:
### The HTML stream
React's server renderer produces progressive HTML chunks. The static parts of your page (layouts, navigation, Suspense fallbacks) render first and are sent immediately. When an async [Server Component](/docs/app/glossary#server-component) resolves, React streams its completed HTML along with inline `<script>` tags: one that swaps the fallback DOM node with the new content, and another carrying the [component payload](#the-component-payload) so React can later hydrate it. The browser executes the swap instantly, without waiting for the page's JavaScript bundle to load or hydration to complete. This is what the user _sees_: the page painting progressively, section by section.
### The component payload
The component payload is a serialized representation of the component tree that React uses to [hydrate](/docs/app/glossary#hydration) the page and handle client-side updates. On initial load, it arrives embedded in the HTML stream (as described above). On **client-side navigation**, only the component payload is fetched (with an `rsc: 1` request header) and no HTML is transferred at all. React uses it to update the component tree in place.
### The static shell
Everything that renders before any async work resolves is called the **static shell**: your layouts, navigation, and the fallback UI defined by your `<Suspense>` boundaries. It is sent immediately, giving the user something to see and interact with while dynamic content streams in. With [Cache Components](/docs/app/getting-started/caching), the static shell is prerendered at build time and served instantly from the edge.
<Image
alt="How Server Rendering with Streaming Works"
srcLight="/docs/light/server-rendering-with-streaming.png"
srcDark="/docs/dark/server-rendering-with-streaming.png"
width="1600"
height="785"
/>
Each `<Suspense>` boundary is an independent streaming point. Components inside different boundaries resolve and stream in independently. They don't block each other.
## Page-level streaming with `loading.js`
The simplest way to add streaming is with a `loading.js` file. Place it alongside your `page.js` and Next.js automatically wraps the page content in a `<Suspense>` boundary, using your loading component as the fallback.
<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() {
return (
<div className="animate-pulse">
<div className="h-8 w-48 bg-gray-200 rounded mb-4" />
<div className="h-4 w-full bg-gray-200 rounded mb-2" />
<div className="h-4 w-full bg-gray-200 rounded mb-2" />
<div className="h-4 w-2/3 bg-gray-200 rounded" />
</div>
)
}
```
```jsx filename="app/dashboard/loading.js" switcher
export default function Loading() {
return (
<div className="animate-pulse">
<div className="h-8 w-48 bg-gray-200 rounded mb-4" />
<div className="h-4 w-full bg-gray-200 rounded mb-2" />
<div className="h-4 w-full bg-gray-200 rounded mb-2" />
<div className="h-4 w-2/3 bg-gray-200 rounded" />
</div>
)
}
```
Behind the scenes, `loading.js` is nested inside `layout.js` and wraps `page.js` 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"
/>
This means:
- The layout renders immediately as part of the static shell.
- The loading skeleton is shown instantly as the Suspense fallback.
- When the page component finishes loading, its HTML replaces the skeleton.
`loading.js` is useful when there's nothing meaningful to show until the page's data resolves. If the page needs to await data before it can render anything, a full-page skeleton is a reasonable fallback.
See the [`loading.js` API reference](/docs/app/api-reference/file-conventions/loading) for more details.
## Granular streaming with `<Suspense>`
`<Suspense>` lets you control exactly which parts of the page stream independently. Instead of a full-page skeleton, you can push fallbacks down into specific sections so the static shell includes more real content.
### Parallel streaming with sibling boundaries
When multiple components perform async work (fetching data, reading from a database), wrap each one in its own `<Suspense>` boundary. Each boundary streams independently as its async work completes, in whatever order that happens, without blocking each other:
```tsx filename="app/dashboard/page.tsx" switcher
import { Suspense } from 'react'
import { Revenue } from './revenue'
import { RecentOrders } from './recent-orders'
import { Recommendations } from './recommendations'
export default function Dashboard() {
return (
<div>
<h1>Dashboard</h1>
<div className="grid grid-cols-2 gap-4">
<Suspense fallback={<p>Loading revenue...</p>}>
<Revenue />
</Suspense>
<Suspense fallback={<p>Loading orders...</p>}>
<RecentOrders />
</Suspense>
</div>
<Suspense fallback={<p>Loading recommendations...</p>}>
<Recommendations />
</Suspense>
</div>
)
}
```
```jsx filename="app/dashboard/page.js" switcher
import { Suspense } from 'react'
import { Revenue } from './revenue'
import { RecentOrders } from './recent-orders'
import { Recommendations } from './recommendations'
export default function Dashboard() {
return (
<div>
<h1>Dashboard</h1>
<div className="grid grid-cols-2 gap-4">
<Suspense fallback={<p>Loading revenue...</p>}>
<Revenue />
</Suspense>
<Suspense fallback={<p>Loading orders...</p>}>
<RecentOrders />
</Suspense>
</div>
<Suspense fallback={<p>Loading recommendations...</p>}>
<Recommendations />
</Suspense>
</div>
)
}
```
In this example, if `Revenue` resolves in 200ms, `RecentOrders` in 1s, and `Recommendations` in 3s, the user sees each section appear as soon as its data is ready.
### Nested boundaries for progressive detail
You can nest `<Suspense>` boundaries to create a layered loading experience. For example, a product page might stream the header immediately, the product details next, and the reviews last:
```tsx filename="app/product/[id]/page.tsx" switcher
import { Suspense } from 'react'
import { ProductDetails } from './product-details'
import { Reviews } from './reviews'
export async function generateStaticParams() {
const products = await getTopProducts()
return products.map((product) => ({ id: product.id }))
}
export default async function ProductPage({
params,
}: {
params: Promise<{ id: string }>
}) {
const { id } = await params
return (
<div>
<h1>Product</h1>
<Suspense fallback={<p>Loading product details...</p>}>
<ProductDetails id={id} />
<Suspense fallback={<p>Loading reviews...</p>}>
<Reviews productId={id} />
</Suspense>
</Suspense>
</div>
)
}
```
```jsx filename="app/product/[id]/page.js" switcher
import { Suspense } from 'react'
import { ProductDetails } from './product-details'
import { Reviews } from './reviews'
export async function generateStaticParams() {
const products = await getTopProducts()
return products.map((product) => ({ id: product.id }))
}
export default async function ProductPage({ params }) {
const { id } = await params
return (
<div>
<h1>Product</h1>
<Suspense fallback={<p>Loading product details...</p>}>
<ProductDetails id={id} />
<Suspense fallback={<p>Loading reviews...</p>}>
<Reviews productId={id} />
</Suspense>
</Suspense>
</div>
)
}
```
The outer boundary shows "Loading product details..." until `ProductDetails` resolves. Once it does, the inner boundary becomes visible, showing "Loading reviews..." until `Reviews` resolves. This creates a progressive reveal.
### Push dynamic access down
The key to maximizing what streams instantly is to defer dynamic data access to the component that actually needs it. This applies to `params`, `searchParams`, `cookies()`, `headers()`, and data fetches. If you `await` any of these at the top of a layout or page, everything below that point becomes dynamic and cannot be prerendered as part of the static shell.
Instead, pass the promise down and let the consuming component resolve it inside a `<Suspense>` boundary:
```tsx filename="app/dashboard/layout.tsx" switcher
import { Suspense } from 'react'
import { Nav } from './nav'
import { UserMenu } from './user-menu'
import { cookies } from 'next/headers'
export default function DashboardLayout({
children,
}: {
children: React.ReactNode
}) {
const cookieStore = cookies() // Start the work, but don't await
return (
<div>
<Nav>
<Suspense fallback={<p>Loading user...</p>}>
<UserMenu cookiePromise={cookieStore} />
</Suspense>
</Nav>
{children}
</div>
)
}
```
```jsx filename="app/dashboard/layout.js" switcher
import { Suspense } from 'react'
import { Nav } from './nav'
import { UserMenu } from './user-menu'
import { cookies } from 'next/headers'
export default function DashboardLayout({ children }) {
const cookieStore = cookies() // Start the work, but don't await
return (
<div>
<Nav>
<Suspense fallback={<p>Loading user...</p>}>
<UserMenu cookiePromise={cookieStore} />
</Suspense>
</Nav>
{children}
</div>
)
}
```
In this example, `<Nav>` and `{children}` render as part of the static shell because nothing in the layout awaits. Only `<UserMenu>` suspends when it resolves the cookie promise. If the layout had called `await cookies()` at the top instead, the entire layout and all its children would be blocked from prerendering.
The same principle applies to `params` and `searchParams`. Rather than destructuring them at the page level, pass the promise to the component that needs the value:
```tsx filename="app/shop/[category]/page.tsx" switcher
import { Suspense } from 'react'
import { Hero } from './hero'
import { ProductGrid } from './product-grid'
export async function generateStaticParams() {
const categories = await getCategories()
return categories.map((c) => ({ category: c.slug }))
}
export default function ShopPage({
params,
}: {
params: Promise<{ category: string }>
}) {
return (
<div>
<Hero />
<Suspense fallback={<p>Loading products...</p>}>
<ProductGrid paramsPromise={params} />
</Suspense>
</div>
)
}
```
```jsx filename="app/shop/[category]/page.js" switcher
import { Suspense } from 'react'
import { Hero } from './hero'
import { ProductGrid } from './product-grid'
export async function generateStaticParams() {
const categories = await getCategories()
return categories.map((c) => ({ category: c.slug }))
}
export default function ShopPage({ params }) {
return (
<div>
<Hero />
<Suspense fallback={<p>Loading products...</p>}>
<ProductGrid paramsPromise={params} />
</Suspense>
</div>
)
}
```
`<Hero />` paints as part of the static shell. `<ProductGrid>` resolves `params` when it needs the category, suspending only within its boundary.
You can also unwrap the promise inline with `.then()`, so the child component receives a plain value instead of a promise:
```tsx filename="app/shop/[category]/page.tsx" switcher
<Suspense fallback={<p>Loading products...</p>}>
{params.then(({ category }) => (
<ProductGrid category={category} />
))}
</Suspense>
```
```jsx filename="app/shop/[category]/page.js" switcher
<Suspense fallback={<p>Loading products...</p>}>
{params.then(({ category }) => (
<ProductGrid category={category} />
))}
</Suspense>
```
This keeps `ProductGrid` simple (it takes a `string`, not a `Promise`) while still deferring the `params` access to inside the Suspense boundary.
### When to use `loading.js` vs `<Suspense>`
| | `loading.js` | `<Suspense>` |
| -------------- | ---------------------------------------- | -------------------------------- |
| **Scope** | Entire page | Any component |
| **Setup** | Drop in a file | Wrap components explicitly |
| **Navigation** | Prefetched as instant fallback | Not prefetched by default |
| **Best for** | Pages where nothing renders without data | Most pages, for granular control |
Prefer explicit `<Suspense>` boundaries close to the dynamic access. When the prerenderer encounters dynamic work, it walks up the tree looking for the nearest Suspense boundary. If none is found, the build fails with a [blocking route error](/docs/messages/blocking-route). A `loading.js` high in the tree is a valid boundary, so the framework finds it and stops, but now the entire page falls back to a full-page skeleton instead of streaming granularly.
### Error handling mid-stream
{/* TODO: catchError semantics - not landed on stable yet */}
If a component throws an error after streaming has started, the nearest [`error.js`](/docs/app/api-reference/file-conventions/error) boundary catches it and renders the error UI in place of the failed component. The rest of the page remains intact, only the section that errored is replaced.
Because the HTTP status code (`200 OK`) has already been sent with the first chunk, it cannot be changed to a `4xx` or `5xx`. The error is handled entirely within the streamed HTML. See [The HTTP contract](#the-http-contract) for more on this constraint.
## Streaming data to the client
You can start a fetch in a [Server Component](/docs/app/glossary#server-component) and pass the unresolved promise as a prop to a [Client Component](/docs/app/glossary#client-component). The promise can be passed through as many layers as needed. Only the component that calls React's [`use`](https://react.dev/reference/react/use) API to read the value needs a `<Suspense>` boundary around it:
```tsx filename="app/dashboard/page.tsx" switcher
import { Suspense } from 'react'
import { StatsChart } from './stats-chart'
type Stats = { revenue: number; orders: number }
async function getStats(): Promise<Stats> {
const res = await fetch('https://api.example.com/stats')
return res.json()
}
export default function Dashboard() {
// Start the fetch during server render, don't await it
const statsPromise = getStats()
return (
<Suspense fallback={<p>Loading chart...</p>}>
<StatsChart dataPromise={statsPromise} />
</Suspense>
)
}
```
```jsx filename="app/dashboard/page.js" switcher
import { Suspense } from 'react'
import { StatsChart } from './stats-chart'
async function getStats() {
const res = await fetch('https://api.example.com/stats')
return res.json()
}
export default function Dashboard() {
// Start the fetch during server render, don't await it
const statsPromise = getStats()
return (
<Suspense fallback={<p>Loading chart...</p>}>
<StatsChart dataPromise={statsPromise} />
</Suspense>
)
}
```
```tsx filename="app/dashboard/stats-chart.tsx" switcher
'use client'
import { use } from 'react'
type Stats = { revenue: number; orders: number }
export function StatsChart({ dataPromise }: { dataPromise: Promise<Stats> }) {
const stats = use(dataPromise)
return <div>{/* render chart with stats */}</div>
}
```
```jsx filename="app/dashboard/stats-chart.js" switcher
'use client'
import { use } from 'react'
export function StatsChart({ dataPromise }) {
const stats = use(dataPromise)
return <div>{/* render chart with stats */}</div>
}
```
The fallback is sent immediately with the static shell. When the promise resolves, React streams the completed HTML into the page.
### Sharing a promise across the tree
When multiple components need the same data, start the fetch once and pass the promise through a context provider so any component in the subtree can resolve it with `use()`:
```tsx filename="app/layout.tsx"
import { getUser } from '@/lib/data'
// Stores the promise in React context for the subtree
import { UserProvider } from './user-provider'
export default function Layout({ children }: { children: React.ReactNode }) {
const userPromise = getUser()
return <UserProvider userPromise={userPromise}>{children}</UserProvider>
}
```
See [Sharing data with context and React.cache](/docs/app/getting-started/fetching-data#sharing-data-with-context-and-reactcache) for the full pattern including the provider and consumer components.
## Streaming in Route Handlers
The patterns above rely on React and Suspense to stream UI. Outside of React rendering, [Route Handlers](/docs/app/api-reference/file-conventions/route) can stream raw responses using the Web Streams API. This is useful for Server-Sent Events, large file generation, or any response where you want data to arrive progressively:
```ts filename="app/api/stream/route.ts" switcher
export async function GET() {
const encoder = new TextEncoder()
const stream = new ReadableStream({
async start(controller) {
for (let i = 0; i < 10; i++) {
controller.enqueue(encoder.encode(`Chunk ${i + 1}\n`))
await new Promise((resolve) => setTimeout(resolve, 200))
}
controller.close()
},
})
return new Response(stream, {
headers: {
'Content-Type': 'text/plain; charset=utf-8',
'X-Content-Type-Options': 'nosniff',
},
})
}
```
```js filename="app/api/stream/route.js" switcher
export async function GET() {
const encoder = new TextEncoder()
const stream = new ReadableStream({
async start(controller) {
for (let i = 0; i < 10; i++) {
controller.enqueue(encoder.encode(`Chunk ${i + 1}\n`))
await new Promise((resolve) => setTimeout(resolve, 200))
}
controller.close()
},
})
return new Response(stream, {
headers: {
'Content-Type': 'text/plain; charset=utf-8',
'X-Content-Type-Options': 'nosniff',
},
})
}
```
Visit this route directly in the browser or with `curl` to see chunks arrive one at a time:
```bash
curl http://localhost:3000/api/stream
```
You can also stream files without loading them entirely into memory. Use `FileHandle.readableWebStream()` to get a Web `ReadableStream` directly from a file:
```ts filename="app/api/download/route.ts" switcher
import { open } from 'node:fs/promises'
export async function GET() {
const file = await open('/path/to/large-file.csv')
return new Response(file.readableWebStream(), {
headers: {
'Content-Type': 'text/csv',
'Content-Disposition': 'attachment; filename="data.csv"',
},
})
}
```
```js filename="app/api/download/route.js" switcher
import { open } from 'node:fs/promises'
export async function GET() {
const file = await open('/path/to/large-file.csv')
return new Response(file.readableWebStream(), {
headers: {
'Content-Type': 'text/csv',
'Content-Disposition': 'attachment; filename="data.csv"',
},
})
}
```
See the [Route Handler API reference](/docs/app/api-reference/file-conventions/route) for more details on building streaming endpoints.
## Streaming and Web Vitals
[Web Vitals](https://web.dev/articles/vitals) are the metrics Google uses to measure user experience. Streaming directly affects several of them.
### TTFB and FCP
Without streaming, the server waits for all data before sending any HTML, so TTFB equals the slowest query. With streaming, the server sends the static shell as soon as it's ready. TTFB drops to the time it takes to render your layouts and fallbacks. The browser paints the static shell immediately, so FCP is decoupled from your data fetching time.
### LCP (Largest Contentful Paint)
If your LCP element (a hero image, a main heading, a product photo) is inside a Suspense boundary, it can't paint until that boundary resolves. To keep LCP fast:
- Keep LCP elements **outside** or **above** Suspense boundaries so they render as part of the static shell.
- Use the [`preload`](/docs/app/api-reference/components/image#preload) prop on `next/image` for LCP images. This injects a `<link rel="preload">` into the `<head>`, so the browser starts fetching the image from the very first chunk, before the `<img>` tag even appears in the HTML.
- For non-image LCP elements (text, headings), make sure they are not wrapped in a Suspense boundary that depends on slow data.
### CLS (Cumulative Layout Shift)
When a Suspense fallback is replaced by the resolved content, the browser reflows the page. If the fallback and the resolved content are different sizes, the surrounding layout shifts. To minimize CLS:
- Design skeleton fallbacks that **match the dimensions** of the content they represent. A skeleton with the same height and width as the final card grid prevents shifts.
- Use fixed or min-height containers around Suspense boundaries so the space is reserved before content arrives.
### INP (Interaction to Next Paint)
Streaming enables [selective hydration](https://react.dev/reference/react-dom/client/hydrateRoot): React hydrates components independently as they stream in, and prioritizes hydrating whatever the user is interacting with. Each `<Suspense>` boundary is a hydration unit. Without them, React hydrates the entire page in one blocking pass. With them, hydration is broken into smaller tasks that yield to the browser, keeping the main thread responsive. The [companion demo](https://streaming-demo.labs.vercel.dev/hydration-single) lets you compare a single blocking hydration pass with [split hydration](https://streaming-demo.labs.vercel.dev/hydration-split) using Suspense boundaries.
### Early resource discovery
The static shell includes `<link>` and `<script>` tags in the very first HTML chunk. The browser discovers and starts fetching CSS, JavaScript, and fonts immediately, while the server is still generating content. Resources are fetched during server think time rather than after it.
In the [dashboard example](#parallel-streaming-with-sibling-boundaries) above, the `<h1>` renders in the shell (good for LCP), each data section streams independently behind its own Suspense boundary (good for INP since hydration is split), and the skeleton fallbacks reserve space (good for CLS).
## The HTTP contract
Once streaming begins, the HTTP response headers (including the status code) have already been sent to the client. **You cannot change the status code or headers after streaming starts.** Everything in this section flows from this fundamental constraint.
### Status codes
When a `<Suspense>` fallback renders or a component suspends, the server must commit to `200 OK` in order to start sending the HTML stream. If a [`notFound()`](/docs/app/api-reference/functions/not-found) fires mid-stream, Next.js cannot go back and change the status to 404. Instead, it injects `<meta name="robots" content="noindex">` into the streamed HTML so that search engines don't index the page. Similarly, a [`redirect()`](/docs/app/api-reference/functions/redirect) mid-stream becomes a client-side redirect rather than an HTTP redirect header.
### When does streaming start?
The response body begins streaming when a Suspense fallback renders (for example, a `loading.tsx`) or when a component suspends under a `<Suspense>` boundary. To get a real HTTP status code for errors, place `notFound()` **before** any `await` or `<Suspense>` boundary:
```tsx filename="app/post/[slug]/page.tsx" switcher
import { Suspense } from 'react'
import { notFound } from 'next/navigation'
import { PostContent } from './post-content'
export async function generateStaticParams() {
const posts = await getPublishedPosts()
return posts.map((post) => ({ slug: post.slug }))
}
export default async function PostPage({
params,
}: {
params: Promise<{ slug: string }>
}) {
const { slug } = await params
const exists = await checkSlugExists(slug) // Fast existence check
if (!exists) notFound() // Real 404, before any Suspense boundary
return (
<Suspense fallback={<p>Loading post...</p>}>
<PostContent slug={slug} />
</Suspense>
)
}
```
```jsx filename="app/post/[slug]/page.js" switcher
import { Suspense } from 'react'
import { notFound } from 'next/navigation'
import { PostContent } from './post-content'
export async function generateStaticParams() {
const posts = await getPublishedPosts()
return posts.map((post) => ({ slug: post.slug }))
}
export default async function PostPage({ params }) {
const { slug } = await params
const exists = await checkSlugExists(slug) // Fast existence check
if (!exists) notFound() // Real 404, before any Suspense boundary
return (
<Suspense fallback={<p>Loading post...</p>}>
<PostContent slug={slug} />
</Suspense>
)
}
```
> **Good to know:** You can also reject requests early using [`proxy`](/docs/app/api-reference/file-conventions/proxy) (for redirects, rewrites, or returning a response) or [`next.config.js` redirects](/docs/app/api-reference/config/next-config-js/redirects). Both run before the page renders, so HTTP status codes are still available.
### Metadata and bots
[`generateMetadata`](/docs/app/api-reference/functions/generate-metadata) resolves before streaming begins for bots that only scrape static HTML (such as Twitterbot or Slackbot). For full browsers and capable crawlers, metadata can [stream](/docs/app/api-reference/functions/generate-metadata#streaming-metadata) alongside the page content.
Next.js automatically detects user agents to choose the right behavior. You can customize which bots receive blocking metadata with the [`htmlLimitedBots`](/docs/app/api-reference/config/next-config-js/htmlLimitedBots) configuration option.
See the [`loading.js` SEO section](/docs/app/api-reference/file-conventions/loading#seo) for more details.
## What can affect streaming
Any layer between your server and the client that buffers the response can diminish the benefits of streaming. The HTML may be fully generated progressively on the server, but if a proxy, CDN, or even the client itself collects all the chunks before rendering them, the user sees a single delayed response instead of progressive rendering.
### Reverse proxies
Nginx and similar reverse proxies buffer responses by default. Disable buffering by setting the `X-Accel-Buffering` header to `no`:
```js filename="next.config.js"
module.exports = {
async headers() {
return [
{
source: '/:path*{/}?',
headers: [
{
key: 'X-Accel-Buffering',
value: 'no',
},
],
},
]
},
}
```
### CDNs
Content Delivery Networks may buffer entire responses before forwarding them to the client. Check your CDN provider's documentation for streaming support. Some require specific configuration or plan tiers to pass through chunked responses.
### Serverless platforms
Not all serverless environments support streaming. AWS Lambda, for example, requires [response streaming mode](https://docs.aws.amazon.com/lambda/latest/dg/configuration-response-streaming.html) to be explicitly enabled (it is not the default). Vercel supports streaming natively.
### Compression
Gzip and Brotli compression can buffer chunks internally before flushing, as the compression algorithm needs enough data to compress efficiently. This can add latency to the first visible chunk. If you notice streaming delays, check whether your compression layer is flushing aggressively enough.
### Clients
Buffering also happens at the client. [Safari/WebKit](https://bugs.webkit.org/show_bug.cgi?id=252413) buffers streaming responses until 1024 bytes have been received, so very small responses paint all at once instead of progressively. Real applications easily exceed this threshold (layouts, styles, scripts), so it only affects minimal demos or tiny Route Handler responses.
Command-line tools like `curl` also buffer by default. The `-N` flag disables output buffering, but `curl` still relies on newline characters to flush lines to the terminal. A stream that sends chunks without newlines may appear to stall even with `-N`.
### Verifying that streaming works
This section is about confirming the HTTP response is actually arriving in chunks through your infrastructure. For guidance on designing meaningful loading states and placing Suspense boundaries effectively, see [Granular streaming with `<Suspense>`](#granular-streaming-with-suspense) and the [Cache Components](/docs/app/getting-started/caching) guide.
**Check the Network tab.** In Chrome DevTools, select the document request and look at the "Timing" breakdown. A long "Content Download" phase with an early "Time to First Byte" confirms the response is streaming rather than arriving all at once.
**Observe raw chunks.** To see exactly what the server sends and when, use a small script that reads the response as a stream. This is more reliable than `curl` for observing timed chunks, since `curl` has its own buffering behavior:
```js filename="stream-observer.mjs"
const res = await fetch(
'https://streaming-demo.labs.vercel.dev/suspense-demo',
{
headers: { 'Accept-Encoding': 'identity' },
}
)
const reader = res.body.getReader()
const decoder = new TextDecoder()
let i = 0
const start = Date.now()
while (true) {
const { done, value } = await reader.read()
if (done) break
console.log(`\nchunk ${i++} (+${Date.now() - start}ms)\n`)
console.log(decoder.decode(value))
}
```
Run with `node stream-observer.mjs`. For a page with two sibling Suspense boundaries (like the [companion demo's Suspense page](https://streaming-demo.labs.vercel.dev/suspense-demo)), you will see output similar to:
```text filename="Terminal"
chunk 0 (+0ms) # Static shell: <head>, CSS, nav, fallback skeletons,
# <template id="B:0"> and <template id="B:1"> placeholders,
# bootstrap scripts
chunk 1 (+170ms) # Component payload (self.__next_f.push) for hydration
chunk 2 (+1000ms) # Weather widget: payload + <div hidden id="S:0"> (swaps B:0)
chunk 3 (+3000ms) # Analytics dashboard: payload + <div hidden id="S:1"> (swaps B:1)
```
The `<template id="B:0">` markers are the Suspense fallback placeholders. When a boundary resolves, React streams a `<div hidden id="S:0">` containing the completed HTML and a script that swaps it into the page. The timestamps show each boundary resolving independently.
> **Good to know:** The `Accept-Encoding: identity` header disables compression so chunks are not buffered by the compression layer.
### 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 |
See the [Self-Hosting guide](/docs/app/guides/self-hosting#streaming-and-suspense) for detailed configuration instructions.
## Summary
The trigger is **your code**: async work, non-deterministic output, or runtime data. When the framework encounters these, it walks up the tree looking for a `<Suspense>` boundary to use as a fallback. Everything above those boundaries forms the [static shell](#the-static-shell), which is sent immediately. As each boundary resolves, React streams the result into the page.
The key decisions are **what to cache** and **where to place Suspense boundaries**. Cache what you can with [`"use cache"`](/docs/app/api-reference/directives/use-cache) to grow the static shell. Push dynamic access down to the components that need it, and wrap those in `<Suspense>`. Everything else becomes part of the shell.
## Further reading
- [RSC Explorer](https://rscexplorer.dev/) - interactive tool to explore the component payload format and see how React reconstructs the tree from streamed chunks
- [Streams API on web.dev](https://web.dev/articles/streams) - introduction to the Web Streams API that underpins streaming in Route Handlers
- [Chunked transfer encoding (MDN)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Transfer-Encoding) - the HTTP/1.1 mechanism that enables streaming responses
- [browser.engineering](https://browser.engineering/) - deep dive into how browsers handle network responses, rendering, and progressive display
+173
View File
@@ -0,0 +1,173 @@
---
title: How to install Tailwind CSS v3 in your Next.js application
nav_title: Tailwind CSS v3
description: Style your Next.js Application using Tailwind CSS v3 for broader browser support.
---
{/* 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 guide will walk you through how to install [Tailwind CSS v3](https://v3.tailwindcss.com/) in your Next.js application.
> **Good to know:** For the latest Tailwind 4 setup, see the [Tailwind CSS setup instructions](/docs/app/getting-started/css#tailwind-css).
## Installing Tailwind v3
Install Tailwind CSS and its peer dependencies, then run the `init` command to generate both `tailwind.config.js` and `postcss.config.js` files:
```bash package="pnpm"
pnpm add -D tailwindcss@^3 postcss autoprefixer
npx tailwindcss init -p
```
```bash package="npm"
npm install -D tailwindcss@^3 postcss autoprefixer
npx tailwindcss init -p
```
```bash package="yarn"
yarn add -D tailwindcss@^3 postcss autoprefixer
npx tailwindcss init -p
```
```bash package="bun"
bun add -D tailwindcss@^3 postcss autoprefixer
bunx tailwindcss init -p
```
## Configuring Tailwind v3
Configure your template paths in your `tailwind.config.js` file:
<AppOnly>
```js filename="tailwind.config.js"
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
'./app/**/*.{js,ts,jsx,tsx,mdx}',
'./pages/**/*.{js,ts,jsx,tsx,mdx}',
'./components/**/*.{js,ts,jsx,tsx,mdx}',
],
theme: {
extend: {},
},
plugins: [],
}
```
Add the Tailwind directives to your global CSS file:
```css filename="app/globals.css"
@tailwind base;
@tailwind components;
@tailwind utilities;
```
Import the CSS file in your root layout:
```tsx filename="app/layout.tsx" switcher
import './globals.css'
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}
```
```jsx filename="app/layout.js" switcher
import './globals.css'
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}
```
</AppOnly>
<PagesOnly>
```js filename="tailwind.config.js"
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
'./pages/**/*.{js,ts,jsx,tsx,mdx}',
'./components/**/*.{js,ts,jsx,tsx,mdx}',
'./app/**/*.{js,ts,jsx,tsx,mdx}',
],
theme: {
extend: {},
},
plugins: [],
}
```
Add the Tailwind directives to your global CSS file:
```css filename="styles/globals.css"
@tailwind base;
@tailwind components;
@tailwind utilities;
```
Import the CSS file in your `pages/_app.js` file:
```jsx filename="pages/_app.js"
import '@/styles/globals.css'
export default function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />
}
```
</PagesOnly>
## Using classes
After installing Tailwind CSS and adding the global styles, you can use Tailwind's utility classes in your application.
<AppOnly>
```tsx filename="app/page.tsx" switcher
export default function Page() {
return <h1 className="text-3xl font-bold underline">Hello, Next.js!</h1>
}
```
```jsx filename="app/page.js" switcher
export default function Page() {
return <h1 className="text-3xl font-bold underline">Hello, Next.js!</h1>
}
```
</AppOnly>
<PagesOnly>
```tsx filename="pages/index.tsx" switcher
export default function Page() {
return <h1 className="text-3xl font-bold underline">Hello, Next.js!</h1>
}
```
```jsx filename="pages/index.js" switcher
export default function Page() {
return <h1 className="text-3xl font-bold underline">Hello, Next.js!</h1>
}
```
</PagesOnly>
## Usage with Turbopack
As of Next.js 13.1, Tailwind CSS and PostCSS are supported with [Turbopack](https://turbo.build/pack/docs/features/css#tailwind-css).
+319
View File
@@ -0,0 +1,319 @@
---
title: How to set up Cypress with Next.js
nav_title: Cypress
description: Learn how to set up Cypress with Next.js for End-to-End (E2E) and Component Testing.
---
[Cypress](https://www.cypress.io/) is a test runner used for **End-to-End (E2E)** and **Component Testing**. This page will show you how to set up Cypress with Next.js and write your first tests.
> **Warning:**
>
> - Cypress versions below 13.6.3 do not support [TypeScript version 5](https://github.com/cypress-io/cypress/issues/27731) with `moduleResolution:"bundler"`. However, this issue has been resolved in Cypress version 13.6.3 and later. [cypress v13.6.3](https://docs.cypress.io/guides/references/changelog#13-6-3)
<AppOnly>
## Quickstart
You can use `create-next-app` with the [with-cypress example](https://github.com/vercel/next.js/tree/canary/examples/with-cypress) to quickly get started.
```bash package="pnpm"
pnpm create next-app --example with-cypress with-cypress-app
```
```bash package="npm"
npx create-next-app@latest --example with-cypress with-cypress-app
```
```bash package="yarn"
yarn create next-app --example with-cypress with-cypress-app
```
```bash package="bun"
bun create next-app --example with-cypress with-cypress-app
```
</AppOnly>
## Manual setup
To manually set up Cypress, install `cypress` as a dev dependency:
```bash package="pnpm"
pnpm add -D cypress
```
```bash package="npm"
npm install -D cypress
```
```bash package="yarn"
yarn add -D cypress
```
```bash package="bun"
bun add -D cypress
```
Add the Cypress `open` command to the `package.json` scripts field:
```json filename="package.json"
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "eslint",
"cypress:open": "cypress open"
}
}
```
Run Cypress for the first time to open the Cypress testing suite:
```bash package="pnpm"
pnpm cypress:open
```
```bash package="npm"
npm run cypress:open
```
```bash package="yarn"
yarn cypress:open
```
```bash package="bun"
bun run cypress:open
```
You can choose to configure **E2E Testing** and/or **Component Testing**. Selecting any of these options will automatically create a `cypress.config.js` file and a `cypress` folder in your project.
## Creating your first Cypress E2E test
Ensure your `cypress.config` file has the following configuration:
```ts filename="cypress.config.ts" switcher
import { defineConfig } from 'cypress'
export default defineConfig({
e2e: {
setupNodeEvents(on, config) {},
},
})
```
```js filename="cypress.config.js" switcher
const { defineConfig } = require('cypress')
module.exports = defineConfig({
e2e: {
setupNodeEvents(on, config) {},
},
})
```
Then, create two new Next.js files:
<AppOnly>
```jsx filename="app/page.js"
import Link from 'next/link'
export default function Page() {
return (
<div>
<h1>Home</h1>
<Link href="/about">About</Link>
</div>
)
}
```
```jsx filename="app/about/page.js"
import Link from 'next/link'
export default function Page() {
return (
<div>
<h1>About</h1>
<Link href="/">Home</Link>
</div>
)
}
```
</AppOnly>
<PagesOnly>
```jsx filename="pages/index.js"
import Link from 'next/link'
export default function Home() {
return (
<div>
<h1>Home</h1>
<Link href="/about">About</Link>
</div>
)
}
```
```jsx filename="pages/about.js"
import Link from 'next/link'
export default function About() {
return (
<div>
<h1>About</h1>
<Link href="/">Home</Link>
</div>
)
}
```
</PagesOnly>
Add a test to check your navigation is working correctly:
```js filename="cypress/e2e/app.cy.js"
describe('Navigation', () => {
it('should navigate to the about page', () => {
// Start from the index page
cy.visit('http://localhost:3000/')
// Find a link with an href attribute containing "about" and click it
cy.get('a[href*="about"]').click()
// The new url should include "/about"
cy.url().should('include', '/about')
// The new page should contain an h1 with "About"
cy.get('h1').contains('About')
})
})
```
### Running E2E Tests
Cypress will simulate a user navigating your application, this requires your Next.js server to be running. We recommend running your tests against your production code to more closely resemble how your application will behave.
Run `npm run build && npm run start` to build your Next.js application, then run `npm run cypress:open` in another terminal window to start Cypress and run your E2E Testing suite.
> **Good to know:**
>
> - You can use `cy.visit("/")` instead of `cy.visit("http://localhost:3000/")` by adding `baseUrl: 'http://localhost:3000'` to the `cypress.config.js` configuration file.
> - Alternatively, you can install the [`start-server-and-test`](https://www.npmjs.com/package/start-server-and-test) package to run the Next.js production server in conjunction with Cypress. After installation, add `"test": "start-server-and-test start http://localhost:3000 cypress"` to your `package.json` scripts field. Remember to rebuild your application after new changes.
## Creating your first Cypress component test
Component tests build and mount a specific component without having to bundle your whole application or start a server.
Select **Component Testing** in the Cypress app, then select **Next.js** as your front-end framework. A `cypress/component` folder will be created in your project, and a `cypress.config.js` file will be updated to enable Component Testing.
Ensure your `cypress.config` file has the following configuration:
```ts filename="cypress.config.ts" switcher
import { defineConfig } from 'cypress'
export default defineConfig({
component: {
devServer: {
framework: 'next',
bundler: 'webpack',
},
},
})
```
```js filename="cypress.config.js" switcher
const { defineConfig } = require('cypress')
module.exports = defineConfig({
component: {
devServer: {
framework: 'next',
bundler: 'webpack',
},
},
})
```
Assuming the same components from the previous section, add a test to validate a component is rendering the expected output:
<AppOnly>
```tsx filename="cypress/component/about.cy.tsx"
import Page from '../../app/page'
describe('<Page />', () => {
it('should render and display expected content', () => {
// Mount the React component for the Home page
cy.mount(<Page />)
// The new page should contain an h1 with "Home"
cy.get('h1').contains('Home')
// Validate that a link with the expected URL is present
// Following the link is better suited to an E2E test
cy.get('a[href="/about"]').should('be.visible')
})
})
```
</AppOnly>
<PagesOnly>
```jsx filename="cypress/component/about.cy.js"
import AboutPage from '../../pages/about'
describe('<AboutPage />', () => {
it('should render and display expected content', () => {
// Mount the React component for the About page
cy.mount(<AboutPage />)
// The new page should contain an h1 with "About page"
cy.get('h1').contains('About')
// Validate that a link with the expected URL is present
// *Following* the link is better suited to an E2E test
cy.get('a[href="/"]').should('be.visible')
})
})
```
</PagesOnly>
> **Good to know**:
>
> - Cypress currently doesn't support Component Testing for `async` Server Components. We recommend using E2E testing.
> - Since component tests do not require a Next.js server, features like `<Image />` that rely on a server being available may not function out-of-the-box.
### Running Component Tests
Run `npm run cypress:open` in your terminal to start Cypress and run your Component Testing suite.
## Continuous Integration (CI)
In addition to interactive testing, you can also run Cypress headlessly using the `cypress run` command, which is better suited for CI environments:
```json filename="package.json"
{
"scripts": {
//...
"e2e": "start-server-and-test dev http://localhost:3000 \"cypress open --e2e\"",
"e2e:headless": "start-server-and-test dev http://localhost:3000 \"cypress run --e2e\"",
"component": "cypress open --component",
"component:headless": "cypress run --component"
}
}
```
You can learn more about Cypress and Continuous Integration from these resources:
- [Next.js with Cypress example](https://github.com/vercel/next.js/tree/canary/examples/with-cypress)
- [Cypress Continuous Integration Docs](https://docs.cypress.io/guides/continuous-integration/introduction)
- [Cypress GitHub Actions Guide](https://on.cypress.io/github-actions)
- [Official Cypress GitHub Action](https://github.com/cypress-io/github-action)
- [Cypress Discord](https://discord.com/invite/cypress)
+26
View File
@@ -0,0 +1,26 @@
---
title: Testing
description: Learn how to set up Next.js with four commonly used testing tools — Cypress, Playwright, Vitest, and Jest.
---
In React and Next.js, there are a few different types of tests you can write, each with its own purpose and use cases. This page provides an overview of types and commonly used tools you can use to test your application.
## Types of tests
- **Unit Testing** involves testing individual units (or blocks of code) in isolation. In React, a unit can be a single function, hook, or component.
- **Component Testing** is a more focused version of unit testing where the primary subject of the tests is React components. This may involve testing how components are rendered, their interaction with props, and their behavior in response to user events.
- **Integration Testing** involves testing how multiple units work together. This can be a combination of components, hooks, and functions.
- **End-to-End (E2E) Testing** involves testing user flows in an environment that simulates real user scenarios, like the browser. This means testing specific tasks (e.g. signup flow) in a production-like environment.
- **Snapshot Testing** involves capturing the rendered output of a component and saving it to a snapshot file. When tests run, the current rendered output of the component is compared against the saved snapshot. Changes in the snapshot are used to indicate unexpected changes in behavior.
<AppOnly>
## Async Server Components
Since `async` Server Components are new to the React ecosystem, some tools do not fully support them. In the meantime, we recommend using **End-to-End Testing** over **Unit Testing** for `async` components.
</AppOnly>
## Guides
See the guides below to learn how to set up Next.js with these commonly used testing tools:
+423
View File
@@ -0,0 +1,423 @@
---
title: How to set up Jest with Next.js
nav_title: Jest
description: Learn how to set up Jest with Next.js for Unit Testing and Snapshot Testing.
---
{/* 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. */}
Jest and React Testing Library are frequently used together for **Unit Testing** and **Snapshot Testing**. This guide will show you how to set up Jest with Next.js and write your first tests.
> **Good to know:** Since `async` Server Components are new to the React ecosystem, Jest currently does not support them. While you can still run **unit tests** for synchronous Server and Client Components, we recommend using an **E2E tests** for `async` components.
## Quickstart
You can use `create-next-app` with the Next.js [with-jest](https://github.com/vercel/next.js/tree/canary/examples/with-jest) example to quickly get started:
```bash package="pnpm"
pnpm create next-app --example with-jest with-jest-app
```
```bash package="npm"
npx create-next-app@latest --example with-jest with-jest-app
```
```bash package="yarn"
yarn create next-app --example with-jest with-jest-app
```
```bash package="bun"
bun create next-app --example with-jest with-jest-app
```
## Manual setup
Since the release of [Next.js 12](https://nextjs.org/blog/next-12), Next.js now has built-in configuration for Jest.
To set up Jest, install `jest` and the following packages as dev dependencies:
```bash package="pnpm"
pnpm add -D jest jest-environment-jsdom @testing-library/react @testing-library/dom @testing-library/jest-dom ts-node @types/jest
```
```bash package="npm"
npm install -D jest jest-environment-jsdom @testing-library/react @testing-library/dom @testing-library/jest-dom ts-node @types/jest
```
```bash package="yarn"
yarn add -D jest jest-environment-jsdom @testing-library/react @testing-library/dom @testing-library/jest-dom ts-node @types/jest
```
```bash package="bun"
bun add -D jest jest-environment-jsdom @testing-library/react @testing-library/dom @testing-library/jest-dom ts-node @types/jest
```
Generate a basic Jest configuration file by running the following command:
```bash package="pnpm"
pnpm create jest@latest
```
```bash package="npm"
npm init jest@latest
```
```bash package="yarn"
yarn create jest@latest
```
```bash package="bun"
bun create jest@latest
```
This will take you through a series of prompts to setup Jest for your project, including automatically creating a `jest.config.ts|js` file.
Update your config file to use `next/jest`. This transformer has all the necessary configuration options for Jest to work with Next.js:
```ts filename="jest.config.ts" switcher
import type { Config } from 'jest'
import nextJest from 'next/jest.js'
const createJestConfig = nextJest({
// Provide the path to your Next.js app to load next.config.js and .env files in your test environment
dir: './',
})
// Add any custom config to be passed to Jest
const config: Config = {
coverageProvider: 'v8',
testEnvironment: 'jsdom',
// Add more setup options before each test is run
// setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
}
// createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async
export default createJestConfig(config)
```
```js filename="jest.config.js" switcher
const nextJest = require('next/jest')
/** @type {import('jest').Config} */
const createJestConfig = nextJest({
// Provide the path to your Next.js app to load next.config.js and .env files in your test environment
dir: './',
})
// Add any custom config to be passed to Jest
const config = {
coverageProvider: 'v8',
testEnvironment: 'jsdom',
// Add more setup options before each test is run
// setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
}
// createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async
module.exports = createJestConfig(config)
```
Under the hood, `next/jest` is automatically configuring Jest for you, including:
- Setting up `transform` using the [Next.js Compiler](/docs/architecture/nextjs-compiler).
- Auto mocking stylesheets (`.css`, `.module.css`, and their scss variants), image imports and [`next/font`](/docs/app/api-reference/components/font).
- Loading `.env` (and all variants) into `process.env`.
- Ignoring `node_modules` from test resolving and transforms.
- Ignoring `.next` from test resolving.
- Loading `next.config.js` for flags that enable SWC transforms.
> **Good to know**: To test environment variables directly, load them manually in a separate setup script or in your `jest.config.ts` file. For more information, please see [Test Environment Variables](/docs/app/guides/environment-variables#test-environment-variables).
<PagesOnly>
## Setting up Jest (with Babel)
If you opt out of the [Next.js Compiler](/docs/architecture/nextjs-compiler) and use Babel instead, you will need to manually configure Jest and install `babel-jest` and `identity-obj-proxy` in addition to the packages above.
Here are the recommended options to configure Jest for Next.js:
```js filename="jest.config.js"
module.exports = {
collectCoverage: true,
// on node 14.x coverage provider v8 offers good speed and more or less good report
coverageProvider: 'v8',
collectCoverageFrom: [
'**/*.{js,jsx,ts,tsx}',
'!**/*.d.ts',
'!**/node_modules/**',
'!<rootDir>/out/**',
'!<rootDir>/.next/**',
'!<rootDir>/*.config.js',
'!<rootDir>/coverage/**',
],
moduleNameMapper: {
// Handle CSS imports (with CSS modules)
// https://jestjs.io/docs/webpack#mocking-css-modules
'^.+\\.module\\.(css|sass|scss)$': 'identity-obj-proxy',
// Handle CSS imports (without CSS modules)
'^.+\\.(css|sass|scss)$': '<rootDir>/__mocks__/styleMock.js',
// Handle image imports
// https://jestjs.io/docs/webpack#handling-static-assets
'^.+\\.(png|jpg|jpeg|gif|webp|avif|ico|bmp|svg)$': `<rootDir>/__mocks__/fileMock.js`,
// Handle module aliases
'^@/components/(.*)$': '<rootDir>/components/$1',
// Handle @next/font
'@next/font/(.*)': `<rootDir>/__mocks__/nextFontMock.js`,
// Handle next/font
'next/font/(.*)': `<rootDir>/__mocks__/nextFontMock.js`,
// Disable server-only
'server-only': `<rootDir>/__mocks__/empty.js`,
},
// Add more setup options before each test is run
// setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
testPathIgnorePatterns: ['<rootDir>/node_modules/', '<rootDir>/.next/'],
testEnvironment: 'jsdom',
transform: {
// Use babel-jest to transpile tests with the next/babel preset
// https://jestjs.io/docs/configuration#transform-objectstring-pathtotransformer--pathtotransformer-object
'^.+\\.(js|jsx|ts|tsx)$': ['babel-jest', { presets: ['next/babel'] }],
},
transformIgnorePatterns: [
'/node_modules/',
'^.+\\.module\\.(css|sass|scss)$',
],
}
```
You can learn more about each configuration option in the [Jest docs](https://jestjs.io/docs/configuration). We also recommend reviewing [`next/jest` configuration](https://github.com/vercel/next.js/blob/e02fe314dcd0ae614c65b505c6daafbdeebb920e/packages/next/src/build/jest/jest.ts) to see how Next.js configures Jest.
### Handling stylesheets and image imports
Stylesheets and images aren't used in the tests but importing them may cause errors, so they will need to be mocked.
Create the mock files referenced in the configuration above - `fileMock.js` and `styleMock.js` - inside a `__mocks__` directory:
```js filename="__mocks__/fileMock.js"
module.exports = 'test-file-stub'
```
```js filename="__mocks__/styleMock.js"
module.exports = {}
```
For more information on handling static assets, please refer to the [Jest Docs](https://jestjs.io/docs/webpack#handling-static-assets).
## Handling Fonts
To handle fonts, create the `nextFontMock.js` file inside the `__mocks__` directory, and add the following configuration:
```js filename="__mocks__/nextFontMock.js"
module.exports = new Proxy(
{},
{
get: function getter() {
return () => ({
className: 'className',
variable: 'variable',
style: { fontFamily: 'fontFamily' },
})
},
}
)
```
</PagesOnly>
## Optional: Handling Absolute Imports and Module Path Aliases
If your project is using [Module Path Aliases](/docs/app/getting-started/installation#set-up-absolute-imports-and-module-path-aliases), you will need to configure Jest to resolve the imports by matching the paths option in the `jsconfig.json` file with the `moduleNameMapper` option in the `jest.config.js` file. For example:
```json filename="tsconfig.json or jsconfig.json"
{
"compilerOptions": {
"module": "esnext",
"moduleResolution": "bundler",
"baseUrl": "./",
"paths": {
"@/components/*": ["components/*"]
}
}
}
```
```js filename="jest.config.js"
moduleNameMapper: {
// ...
'^@/components/(.*)$': '<rootDir>/components/$1',
}
```
## Optional: Extend Jest with custom matchers
`@testing-library/jest-dom` includes a set of convenient [custom matchers](https://github.com/testing-library/jest-dom#custom-matchers) such as `.toBeInTheDocument()` making it easier to write tests. You can import the custom matchers for every test by adding the following option to the Jest configuration file:
```ts filename="jest.config.ts" switcher
setupFilesAfterEnv: ['<rootDir>/jest.setup.ts']
```
```js filename="jest.config.js" switcher
setupFilesAfterEnv: ['<rootDir>/jest.setup.js']
```
Then, inside `jest.setup`, add the following import:
```ts filename="jest.setup.ts" switcher
import '@testing-library/jest-dom'
```
```js filename="jest.setup.js" switcher
import '@testing-library/jest-dom'
```
> **Good to know:** [`extend-expect` was removed in `v6.0`](https://github.com/testing-library/jest-dom/releases/tag/v6.0.0), so if you are using `@testing-library/jest-dom` before version 6, you will need to import `@testing-library/jest-dom/extend-expect` instead.
If you need to add more setup options before each test, you can add them to the `jest.setup` file above.
## Add a test script to `package.json`
Finally, add a Jest `test` script to your `package.json` file:
```json filename="package.json" highlight={6-7}
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"test": "jest",
"test:watch": "jest --watch"
}
}
```
`jest --watch` will re-run tests when a file is changed. For more Jest CLI options, please refer to the [Jest Docs](https://jestjs.io/docs/cli#reference).
### Creating your first test
Your project is now ready to run tests. Create a folder called `__tests__` in your project's root directory.
<PagesOnly>
For example, we can add a test to check if the `<Home />` component successfully renders a heading:
```jsx filename="pages/index.js
export default function Home() {
return <h1>Home</h1>
}
```
```jsx filename="__tests__/index.test.js"
import '@testing-library/jest-dom'
import { render, screen } from '@testing-library/react'
import Home from '../pages/index'
describe('Home', () => {
it('renders a heading', () => {
render(<Home />)
const heading = screen.getByRole('heading', { level: 1 })
expect(heading).toBeInTheDocument()
})
})
```
</PagesOnly>
<AppOnly>
For example, we can add a test to check if the `<Page />` component successfully renders a heading:
```jsx filename="app/page.js"
import Link from 'next/link'
export default function Page() {
return (
<div>
<h1>Home</h1>
<Link href="/about">About</Link>
</div>
)
}
```
```jsx filename="__tests__/page.test.jsx"
import '@testing-library/jest-dom'
import { render, screen } from '@testing-library/react'
import Page from '../app/page'
describe('Page', () => {
it('renders a heading', () => {
render(<Page />)
const heading = screen.getByRole('heading', { level: 1 })
expect(heading).toBeInTheDocument()
})
})
```
</AppOnly>
Optionally, add a [snapshot test](https://jestjs.io/docs/snapshot-testing) to keep track of any unexpected changes in your component:
<PagesOnly>
```jsx filename="__tests__/snapshot.js"
import { render } from '@testing-library/react'
import Home from '../pages/index'
it('renders homepage unchanged', () => {
const { container } = render(<Home />)
expect(container).toMatchSnapshot()
})
```
> **Good to know**: Test files should not be included inside the Pages Router because any files inside the Pages Router are considered routes.
</PagesOnly>
<AppOnly>
```jsx filename="__tests__/snapshot.js"
import { render } from '@testing-library/react'
import Page from '../app/page'
it('renders homepage unchanged', () => {
const { container } = render(<Page />)
expect(container).toMatchSnapshot()
})
```
</AppOnly>
## Running your tests
Then, run the following command to run your tests:
```bash package="pnpm"
pnpm test
```
```bash package="npm"
npm run test
```
```bash package="yarn"
yarn test
```
```bash package="bun"
bun run test
```
## Additional Resources
For further reading, you may find these resources helpful:
- [Next.js with Jest example](https://github.com/vercel/next.js/tree/canary/examples/with-jest)
- [Jest Docs](https://jestjs.io/docs/getting-started)
- [React Testing Library Docs](https://testing-library.com/docs/react-testing-library/intro/)
- [Testing Playground](https://testing-playground.com/) - use good testing practices to match elements.
+150
View File
@@ -0,0 +1,150 @@
---
title: How to set up Playwright with Next.js
nav_title: Playwright
description: Learn how to set up Playwright with Next.js for End-to-End (E2E) Testing.
---
Playwright is a testing framework that lets you automate Chromium, Firefox, and WebKit with a single API. You can use it to write **End-to-End (E2E)** testing. This guide will show you how to set up Playwright with Next.js and write your first tests.
## Quickstart
The fastest way to get started is to use `create-next-app` with the [with-playwright example](https://github.com/vercel/next.js/tree/canary/examples/with-playwright). This will create a Next.js project complete with Playwright configured.
```bash package="pnpm"
pnpm create next-app --example with-playwright with-playwright-app
```
```bash package="npm"
npx create-next-app@latest --example with-playwright with-playwright-app
```
```bash package="yarn"
yarn create next-app --example with-playwright with-playwright-app
```
```bash package="bun"
bun create next-app --example with-playwright with-playwright-app
```
## Manual setup
To install Playwright, run the following command:
```bash package="pnpm"
pnpm create playwright
```
```bash package="npm"
npm init playwright
```
```bash package="yarn"
yarn create playwright
```
```bash package="bun"
bun create playwright
```
This will take you through a series of prompts to setup and configure Playwright for your project, including adding a `playwright.config.ts` file. Please refer to the [Playwright installation guide](https://playwright.dev/docs/intro#installation) for the step-by-step guide.
## Creating your first Playwright E2E test
Create two new Next.js pages:
<AppOnly>
```tsx filename="app/page.tsx"
import Link from 'next/link'
export default function Page() {
return (
<div>
<h1>Home</h1>
<Link href="/about">About</Link>
</div>
)
}
```
```tsx filename="app/about/page.tsx"
import Link from 'next/link'
export default function Page() {
return (
<div>
<h1>About</h1>
<Link href="/">Home</Link>
</div>
)
}
```
</AppOnly>
<PagesOnly>
```tsx filename="pages/index.ts"
import Link from 'next/link'
export default function Home() {
return (
<div>
<h1>Home</h1>
<Link href="/about">About</Link>
</div>
)
}
```
```tsx filename="pages/about.ts"
import Link from 'next/link'
export default function About() {
return (
<div>
<h1>About</h1>
<Link href="/">Home</Link>
</div>
)
}
```
</PagesOnly>
Then, add a test to verify that your navigation is working correctly:
```ts filename="tests/example.spec.ts"
import { test, expect } from '@playwright/test'
test('should navigate to the about page', async ({ page }) => {
// Start from the index page (the baseURL is set via the webServer in the playwright.config.ts)
await page.goto('http://localhost:3000/')
// Find an element with the text 'About' and click on it
await page.click('text=About')
// The new URL should be "/about" (baseURL is used there)
await expect(page).toHaveURL('http://localhost:3000/about')
// The new page should contain an h1 with "About"
await expect(page.locator('h1')).toContainText('About')
})
```
> **Good to know**: You can use `page.goto("/")` instead of `page.goto("http://localhost:3000/")`, if you add [`"baseURL": "http://localhost:3000"`](https://playwright.dev/docs/api/class-testoptions#test-options-base-url) to the `playwright.config.ts` [configuration file](https://playwright.dev/docs/test-configuration).
### Running your Playwright tests
Playwright will simulate a user navigating your application using three browsers: Chromium, Firefox and Webkit, this requires your Next.js server to be running. We recommend running your tests against your production code to more closely resemble how your application will behave.
Run `npm run build` and `npm run start`, then run `npx playwright test` in another terminal window to run the Playwright tests.
> **Good to know**: Alternatively, you can use the [`webServer`](https://playwright.dev/docs/test-webserver/) feature to let Playwright start the development server and wait until it's fully available.
### Running Playwright on Continuous Integration (CI)
Playwright will by default run your tests in the [headless mode](https://playwright.dev/docs/ci#running-headed). To install all the Playwright dependencies, run `npx playwright install-deps`.
You can learn more about Playwright and Continuous Integration from these resources:
- [Next.js with Playwright example](https://github.com/vercel/next.js/tree/canary/examples/with-playwright)
- [Playwright on your CI provider](https://playwright.dev/docs/ci)
- [Playwright Discord](https://discord.com/invite/playwright-807756831384403968)
+243
View File
@@ -0,0 +1,243 @@
---
title: How to set up Vitest with Next.js
nav_title: Vitest
description: Learn how to set up Vitest with Next.js for Unit Testing.
---
Vitest and React Testing Library are frequently used together for **Unit Testing**. This guide will show you how to setup Vitest with Next.js and write your first tests.
> **Good to know:** Since `async` Server Components are new to the React ecosystem, Vitest currently does not support them. While you can still run **unit tests** for synchronous Server and Client Components, we recommend using **E2E tests** for `async` components.
## Quickstart
You can use `create-next-app` with the Next.js [with-vitest](https://github.com/vercel/next.js/tree/canary/examples/with-vitest) example to quickly get started:
```bash package="pnpm"
pnpm create next-app --example with-vitest with-vitest-app
```
```bash package="npm"
npx create-next-app@latest --example with-vitest with-vitest-app
```
```bash package="yarn"
yarn create next-app --example with-vitest with-vitest-app
```
```bash package="bun"
bun create next-app --example with-vitest with-vitest-app
```
## Manual Setup
To manually set up Vitest, install `vitest` and the following packages as dev dependencies:
```bash package="pnpm"
# Using TypeScript
pnpm add -D vitest @vitejs/plugin-react jsdom @testing-library/react @testing-library/dom vite-tsconfig-paths
# Using JavaScript
pnpm add -D vitest @vitejs/plugin-react jsdom @testing-library/react @testing-library/dom
```
```bash package="npm"
# Using TypeScript
npm install -D vitest @vitejs/plugin-react jsdom @testing-library/react @testing-library/dom vite-tsconfig-paths
# Using JavaScript
npm install -D vitest @vitejs/plugin-react jsdom @testing-library/react @testing-library/dom
```
```bash package="yarn"
# Using TypeScript
yarn add -D vitest @vitejs/plugin-react jsdom @testing-library/react @testing-library/dom vite-tsconfig-paths
# Using JavaScript
yarn add -D vitest @vitejs/plugin-react jsdom @testing-library/react @testing-library/dom
```
```bash package="bun"
# Using TypeScript
bun add -D vitest @vitejs/plugin-react jsdom @testing-library/react @testing-library/dom vite-tsconfig-paths
# Using JavaScript
bun add -D vitest @vitejs/plugin-react jsdom @testing-library/react @testing-library/dom
```
Create a `vitest.config.mts|js` file in the root of your project, and add the following options:
```ts filename="vitest.config.mts" switcher
import { defineConfig } from 'vitest/config'
import react from '@vitejs/plugin-react'
import tsconfigPaths from 'vite-tsconfig-paths'
export default defineConfig({
plugins: [tsconfigPaths(), react()],
test: {
environment: 'jsdom',
},
})
```
```js filename="vitest.config.js" switcher
import { defineConfig } from 'vitest/config'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
test: {
environment: 'jsdom',
},
})
```
For more information on configuring Vitest, please refer to the [Vitest Configuration](https://vitest.dev/config/#configuration) docs.
Then, add a `test` script to your `package.json`:
```json filename="package.json"
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"test": "vitest"
}
}
```
When you run `npm run test`, Vitest will **watch** for changes in your project by default.
## Creating your first Vitest Unit Test
Check that everything is working by creating a test to check if the `<Page />` component successfully renders a heading:
<AppOnly>
```tsx filename="app/page.tsx" switcher
import Link from 'next/link'
export default function Page() {
return (
<div>
<h1>Home</h1>
<Link href="/about">About</Link>
</div>
)
}
```
```jsx filename="app/page.jsx" switcher
import Link from 'next/link'
export default function Page() {
return (
<div>
<h1>Home</h1>
<Link href="/about">About</Link>
</div>
)
}
```
```tsx filename="__tests__/page.test.tsx" switcher
import { expect, test } from 'vitest'
import { render, screen } from '@testing-library/react'
import Page from '../app/page'
test('Page', () => {
render(<Page />)
expect(screen.getByRole('heading', { level: 1, name: 'Home' })).toBeDefined()
})
```
```jsx filename="__tests__/page.test.jsx" switcher
import { expect, test } from 'vitest'
import { render, screen } from '@testing-library/react'
import Page from '../app/page'
test('Page', () => {
render(<Page />)
expect(screen.getByRole('heading', { level: 1, name: 'Home' })).toBeDefined()
})
```
> **Good to know**: The example above uses the common `__tests__` convention, but test files can also be colocated inside the `app` router.
</AppOnly>
<PagesOnly>
```tsx filename="pages/index.tsx" switcher
import Link from 'next/link'
export default function Page() {
return (
<div>
<h1>Home</h1>
<Link href="/about">About</Link>
</div>
)
}
```
```jsx filename="pages/index.jsx" switcher
import Link from 'next/link'
export default function Page() {
return (
<div>
<h1>Home</h1>
<Link href="/about">About</Link>
</div>
)
}
```
```tsx filename="__tests__/index.test.tsx" switcher
import { expect, test } from 'vitest'
import { render, screen } from '@testing-library/react'
import Page from '../pages/index'
test('Page', () => {
render(<Page />)
expect(screen.getByRole('heading', { level: 1, name: 'Home' })).toBeDefined()
})
```
```jsx filename="__tests__/index.test.jsx" switcher
import { expect, test } from 'vitest'
import { render, screen } from '@testing-library/react'
import Page from '../pages/index'
test('Page', () => {
render(<Page />)
expect(screen.getByRole('heading', { level: 1, name: 'Home' })).toBeDefined()
})
```
</PagesOnly>
## Running your tests
Then, run the following command to run your tests:
```bash package="pnpm"
pnpm test
```
```bash package="npm"
npm run test
```
```bash package="yarn"
yarn test
```
```bash package="bun"
bun run test
```
## Additional Resources
You may find these resources helpful:
- [Next.js with Vitest example](https://github.com/vercel/next.js/tree/canary/examples/with-vitest)
- [Vitest Docs](https://vitest.dev/guide/)
- [React Testing Library Docs](https://testing-library.com/docs/react-testing-library/intro/)
+478
View File
@@ -0,0 +1,478 @@
---
title: How to optimize third-party libraries
nav_title: Third Party Libraries
description: Optimize the performance of third-party libraries in your application with the `@next/third-parties` package.
---
{/* 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/third-parties`** is a library that provides a collection of components and utilities that improve the performance and developer experience of loading popular third-party libraries in your Next.js application.
All third-party integrations provided by `@next/third-parties` have been optimized for performance and ease of use.
## Getting Started
To get started, install the `@next/third-parties` library:
```bash package="pnpm"
pnpm add @next/third-parties@latest next@latest
```
```bash package="npm"
npm install @next/third-parties@latest next@latest
```
```bash package="yarn"
yarn add @next/third-parties@latest next@latest
```
```bash package="bun"
bun add @next/third-parties@latest next@latest
```
{/* To do: Remove this paragraph once package becomes stable */}
`@next/third-parties` is currently an **experimental** library under active development. We recommend installing it with the **latest** or **canary** flags while we work on adding more third-party integrations.
## Google Third-Parties
All supported third-party libraries from Google can be imported from `@next/third-parties/google`.
### Google Tag Manager
The `GoogleTagManager` component can be used to instantiate a [Google Tag Manager](https://developers.google.com/tag-platform/tag-manager) container to your page. By default, it fetches the original inline script after hydration occurs on the page.
<AppOnly>
To load Google Tag Manager for all routes, include the component directly in your root layout and pass in your GTM container ID:
```tsx filename="app/layout.tsx" switcher
import { GoogleTagManager } from '@next/third-parties/google'
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<GoogleTagManager gtmId="GTM-XYZ" />
<body>{children}</body>
</html>
)
}
```
```jsx filename="app/layout.js" switcher
import { GoogleTagManager } from '@next/third-parties/google'
export default function RootLayout({ children }) {
return (
<html lang="en">
<GoogleTagManager gtmId="GTM-XYZ" />
<body>{children}</body>
</html>
)
}
```
</AppOnly>
<PagesOnly>
To load Google Tag Manager for all routes, include the component directly in your custom `_app` and
pass in your GTM container ID:
```jsx filename="pages/_app.js"
import { GoogleTagManager } from '@next/third-parties/google'
export default function MyApp({ Component, pageProps }) {
return (
<>
<Component {...pageProps} />
<GoogleTagManager gtmId="GTM-XYZ" />
</>
)
}
```
</PagesOnly>
To load Google Tag Manager for a single route, include the component in your page file:
<AppOnly>
```jsx filename="app/page.js"
import { GoogleTagManager } from '@next/third-parties/google'
export default function Page() {
return <GoogleTagManager gtmId="GTM-XYZ" />
}
```
</AppOnly>
<PagesOnly>
```jsx filename="pages/index.js"
import { GoogleTagManager } from '@next/third-parties/google'
export default function Page() {
return <GoogleTagManager gtmId="GTM-XYZ" />
}
```
</PagesOnly>
#### Sending Events
The `sendGTMEvent` function can be used to track user interactions on your page by sending events
using the `dataLayer` object. For this function to work, the `<GoogleTagManager />` component must be
included in either a parent layout, page, or component, or directly in the same file.
<AppOnly>
```jsx filename="app/page.js"
'use client'
import { sendGTMEvent } from '@next/third-parties/google'
export function EventButton() {
return (
<div>
<button
onClick={() => sendGTMEvent({ event: 'buttonClicked', value: 'xyz' })}
>
Send Event
</button>
</div>
)
}
```
</AppOnly>
<PagesOnly>
```jsx filename="pages/index.js"
import { sendGTMEvent } from '@next/third-parties/google'
export function EventButton() {
return (
<div>
<button
onClick={() => sendGTMEvent({ event: 'buttonClicked', value: 'xyz' })}
>
Send Event
</button>
</div>
)
}
```
</PagesOnly>
Refer to the Tag Manager [developer
documentation](https://developers.google.com/tag-platform/tag-manager/datalayer) to learn about the
different variables and events that can be passed into the function.
#### Server-side Tagging
If you're using a server-side tag manager and serving `gtm.js` scripts from your tagging server you can
use `gtmScriptUrl` option to specify the URL of the script.
#### Options
Options to pass to the Google Tag Manager. For a full list of options, read the [Google Tag Manager
docs](https://developers.google.com/tag-platform/tag-manager/datalayer).
| Name | Type | Description |
| --------------- | ---------- | ------------------------------------------------------------------------ |
| `gtmId` | Required\* | Your GTM container ID. Usually starts with `GTM-`. |
| `gtmScriptUrl` | Optional\* | GTM script URL. Defaults to `https://www.googletagmanager.com/gtm.js`. |
| `dataLayer` | Optional | Data layer object to instantiate the container with. |
| `dataLayerName` | Optional | Name of the data layer. Defaults to `dataLayer`. |
| `auth` | Optional | Value of authentication parameter (`gtm_auth`) for environment snippets. |
| `preview` | Optional | Value of preview parameter (`gtm_preview`) for environment snippets. |
\*`gtmId` can be omitted when `gtmScriptUrl` is provided to support [Google tag gateway for advertisers](https://developers.google.com/tag-platform/tag-manager/gateway/setup-guide?setup=manual).
### Google Analytics
The `GoogleAnalytics` component can be used to include [Google Analytics
4](https://developers.google.com/analytics/devguides/collection/ga4) to your page via the Google tag
(`gtag.js`). By default, it fetches the original scripts after hydration occurs on the page.
> **Recommendation**: If Google Tag Manager is already included in your application, you can
> configure Google Analytics directly using it, rather than including Google Analytics as a separate
> component. Refer to the
> [documentation](https://developers.google.com/analytics/devguides/collection/ga4/tag-options#what-is-gtm)
> to learn more about the differences between Tag Manager and `gtag.js`.
<AppOnly>
To load Google Analytics for all routes, include the component directly in your root layout and pass
in your measurement ID:
```tsx filename="app/layout.tsx" switcher
import { GoogleAnalytics } from '@next/third-parties/google'
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>{children}</body>
<GoogleAnalytics gaId="G-XYZ" />
</html>
)
}
```
```jsx filename="app/layout.js" switcher
import { GoogleAnalytics } from '@next/third-parties/google'
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>{children}</body>
<GoogleAnalytics gaId="G-XYZ" />
</html>
)
}
```
</AppOnly>
<PagesOnly>
To load Google Analytics for all routes, include the component directly in your custom `_app` and
pass in your measurement ID:
```jsx filename="pages/_app.js"
import { GoogleAnalytics } from '@next/third-parties/google'
export default function MyApp({ Component, pageProps }) {
return (
<>
<Component {...pageProps} />
<GoogleAnalytics gaId="G-XYZ" />
</>
)
}
```
</PagesOnly>
To load Google Analytics for a single route, include the component in your page file:
<AppOnly>
```jsx filename="app/page.js"
import { GoogleAnalytics } from '@next/third-parties/google'
export default function Page() {
return <GoogleAnalytics gaId="G-XYZ" />
}
```
</AppOnly>
<PagesOnly>
```jsx filename="pages/index.js"
import { GoogleAnalytics } from '@next/third-parties/google'
export default function Page() {
return <GoogleAnalytics gaId="G-XYZ" />
}
```
</PagesOnly>
#### Sending Events
The `sendGAEvent` function can be used to measure user interactions on your page by sending events
using the `dataLayer` object. For this function to work, the `<GoogleAnalytics />` component must be
included in either a parent layout, page, or component, or directly in the same file.
<AppOnly>
```jsx filename="app/page.js"
'use client'
import { sendGAEvent } from '@next/third-parties/google'
export function EventButton() {
return (
<div>
<button
onClick={() => sendGAEvent('event', 'buttonClicked', { value: 'xyz' })}
>
Send Event
</button>
</div>
)
}
```
</AppOnly>
<PagesOnly>
```jsx filename="pages/index.js"
import { sendGAEvent } from '@next/third-parties/google'
export function EventButton() {
return (
<div>
<button
onClick={() => sendGAEvent('event', 'buttonClicked', { value: 'xyz' })}
>
Send Event
</button>
</div>
)
}
```
</PagesOnly>
Refer to the Google Analytics [developer
documentation](https://developers.google.com/analytics/devguides/collection/ga4/event-parameters) to learn
more about event parameters.
#### Tracking Pageviews
Google Analytics automatically tracks pageviews when the browser history state changes. This means
that client-side navigations between Next.js routes will send pageview data without any configuration.
To ensure that client-side navigations are being measured correctly, verify that the [_“Enhanced
Measurement”_](https://support.google.com/analytics/answer/9216061#enable_disable) property is
enabled in your Admin panel and the _“Page changes based on browser history events”_ checkbox is
selected.
> **Note**: If you decide to manually send pageview events, make sure to disable the default
> pageview measurement to avoid having duplicate data. Refer to the Google Analytics [developer
> documentation](https://developers.google.com/analytics/devguides/collection/ga4/views?client_type=gtag#manual_pageviews)
> to learn more.
#### Options
Options to pass to the `<GoogleAnalytics>` component.
| Name | Type | Description |
| --------------- | -------- | ------------------------------------------------------------------------------------------------------ |
| `gaId` | Required | Your [measurement ID](https://support.google.com/analytics/answer/12270356). Usually starts with `G-`. |
| `dataLayerName` | Optional | Name of the data layer. Defaults to `dataLayer`. |
| `nonce` | Optional | A [nonce](/docs/app/guides/content-security-policy#nonces). |
### Google Maps Embed
The `GoogleMapsEmbed` component can be used to add a [Google Maps
Embed](https://developers.google.com/maps/documentation/embed/embedding-map) to your page. By
default, it uses the `loading` attribute to lazy-load the embed below the fold.
<AppOnly>
```jsx filename="app/page.js"
import { GoogleMapsEmbed } from '@next/third-parties/google'
export default function Page() {
return (
<GoogleMapsEmbed
apiKey="XYZ"
height={200}
width="100%"
mode="place"
q="Brooklyn+Bridge,New+York,NY"
/>
)
}
```
</AppOnly>
<PagesOnly>
```jsx filename="pages/index.js"
import { GoogleMapsEmbed } from '@next/third-parties/google'
export default function Page() {
return (
<GoogleMapsEmbed
apiKey="XYZ"
height={200}
width="100%"
mode="place"
q="Brooklyn+Bridge,New+York,NY"
/>
)
}
```
</PagesOnly>
#### Options
Options to pass to the Google Maps Embed. For a full list of options, read the [Google Map Embed
docs](https://developers.google.com/maps/documentation/embed/embedding-map).
| Name | Type | Description |
| ----------------- | -------- | --------------------------------------------------------------------------------------------------- |
| `apiKey` | Required | Your api key. |
| `mode` | Required | [Map mode](https://developers.google.com/maps/documentation/embed/embedding-map#choosing_map_modes) |
| `height` | Optional | Height of the embed. Defaults to `auto`. |
| `width` | Optional | Width of the embed. Defaults to `auto`. |
| `style` | Optional | Pass styles to the iframe. |
| `allowfullscreen` | Optional | Property to allow certain map parts to go full screen. |
| `loading` | Optional | Defaults to lazy. Consider changing if you know your embed will be above the fold. |
| `q` | Optional | Defines map marker location. _This may be required depending on the map mode_. |
| `center` | Optional | Defines the center of the map view. |
| `zoom` | Optional | Sets initial zoom level of the map. |
| `maptype` | Optional | Defines type of map tiles to load. |
| `language` | Optional | Defines the language to use for UI elements and for the display of labels on map tiles. |
| `region` | Optional | Defines the appropriate borders and labels to display, based on geo-political sensitivities. |
### YouTube Embed
The `YouTubeEmbed` component can be used to load and display a YouTube embed. This component loads
faster by using [`lite-youtube-embed`](https://github.com/paulirish/lite-youtube-embed) under the
hood.
<AppOnly>
```jsx filename="app/page.js"
import { YouTubeEmbed } from '@next/third-parties/google'
export default function Page() {
return <YouTubeEmbed videoid="ogfYd705cRs" height={400} params="controls=0" />
}
```
</AppOnly>
<PagesOnly>
```jsx filename="pages/index.js"
import { YouTubeEmbed } from '@next/third-parties/google'
export default function Page() {
return <YouTubeEmbed videoid="ogfYd705cRs" height={400} params="controls=0" />
}
```
</PagesOnly>
#### Options
| Name | Type | Description |
| ----------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `videoid` | Required | YouTube video id. |
| `width` | Optional | Width of the video container. Defaults to `auto` |
| `height` | Optional | Height of the video container. Defaults to `auto` |
| `playlabel` | Optional | A visually hidden label for the play button for accessibility. |
| `params` | Optional | The video player params defined [here](https://developers.google.com/youtube/player_parameters#Parameters). <br/> Params are passed as a query param string. <br/> Eg: `params="controls=0&start=10&end=30"` |
| `style` | Optional | Used to apply styles to the video container. |
+721
View File
@@ -0,0 +1,721 @@
---
title: Codemods
description: Use codemods to upgrade your Next.js codebase when new features are released.
---
Codemods are transformations that run on your codebase programmatically. This allows a large number of changes to be programmatically applied without having to manually go through every file.
Next.js provides Codemod transformations to help upgrade your Next.js codebase when an API is updated or deprecated.
## Usage
In your terminal, navigate (`cd`) into your project's folder, then run:
```bash filename="Terminal"
npx @next/codemod <transform> <path>
```
Replacing `<transform>` and `<path>` with appropriate values.
- `transform` - name of transform
- `path` - files or directory to transform
- `--dry` Do a dry-run, no code will be edited
- `--print` Prints the changed output for comparison
## Upgrade
Upgrades your Next.js application, automatically running codemods and updating Next.js, React, and React DOM.
```bash filename="Terminal"
npx @next/codemod upgrade [revision]
```
### Options
- `revision` (optional): Specify the upgrade type (`patch`, `minor`, `major`), an NPM dist tag (e.g. `latest`, `canary`, `rc`), or an exact version (e.g. `15.0.0`). Defaults to `minor` for stable versions.
- `--verbose`: Show more detailed output during the upgrade process.
For example:
```bash filename="Terminal"
# Upgrade to the latest patch (e.g. 16.0.7 -> 16.0.8)
npx @next/codemod upgrade patch
# Upgrade to the latest minor (e.g. 15.3.7 -> 15.4.8). This is the default.
npx @next/codemod upgrade minor
# Upgrade to the latest major (e.g. 15.5.7 -> 16.0.7)
npx @next/codemod upgrade major
# Upgrade to a specific version
npx @next/codemod upgrade 16
# Upgrade to the canary release
npx @next/codemod upgrade canary
```
> **Good to know**:
>
> - If the target version is the same as or lower than your current version, the command exits without making changes.
> - During the upgrade, you may be prompted to choose which Next.js codemods to apply and run React 19 codemods if upgrading React.
## Codemods
### 16.0
#### Remove `experimental_ppr` Route Segment Config from App Router pages and layouts
##### `remove-experimental-ppr`
```bash filename="Terminal"
npx @next/codemod@latest remove-experimental-ppr .
```
This codemod removes the `experimental_ppr` Route Segment Config from App Router pages and layouts.
```diff filename="app/page.tsx"
- export const experimental_ppr = true;
```
#### Remove `unstable_` prefix from stabilized API
##### `remove-unstable-prefix`
```bash filename="Terminal"
npx @next/codemod@latest remove-unstable-prefix .
```
This codemod removes the `unstable_` prefix from stabilized API.
For example:
```ts
import { unstable_cacheTag as cacheTag } from 'next/cache'
cacheTag()
```
Transforms into:
```ts
import { cacheTag } from 'next/cache'
cacheTag()
```
#### Migrate from deprecated `middleware` convention to `proxy`
##### `middleware-to-proxy`
```bash filename="Terminal"
npx @next/codemod@latest middleware-to-proxy .
```
This codemod migrates projects from using the deprecated `middleware` convention to using the `proxy` convention. It:
- Renames `middleware.<extension>` to `proxy.<extension>` (e.g. `middleware.ts` to `proxy.ts`)
- Renames named export `middleware` to `proxy`
- Renames Next.js config property `experimental.middlewarePrefetch` to `experimental.proxyPrefetch`
- Renames Next.js config property `experimental.middlewareClientMaxBodySize` to `experimental.proxyClientMaxBodySize`
- Renames Next.js config property `experimental.externalMiddlewareRewritesResolve` to `experimental.externalProxyRewritesResolve`
- Renames Next.js config property `skipMiddlewareUrlNormalize` to `skipProxyUrlNormalize`
For example:
```ts filename="middleware.ts"
import { NextResponse } from 'next/server'
export function middleware() {
return NextResponse.next()
}
```
Transforms into:
```ts filename="proxy.ts"
import { NextResponse } from 'next/server'
export function proxy() {
return NextResponse.next()
}
```
#### Migrate from `next lint` to ESLint CLI
##### `next-lint-to-eslint-cli`
```bash filename="Terminal"
npx @next/codemod@canary next-lint-to-eslint-cli .
```
This codemod migrates projects from using `next lint` to using the ESLint CLI with your local ESLint config. It:
- Creates an `eslint.config.mjs` file with Next.js recommended configurations
- Updates `package.json` scripts to use `eslint .` instead of `next lint`
- Adds necessary ESLint dependencies to `package.json`
- Preserves existing ESLint configurations when found
For example:
```json filename="package.json"
{
"scripts": {
"lint": "next lint"
}
}
```
Becomes:
```json filename="package.json"
{
"scripts": {
"lint": "eslint ."
}
}
```
And creates:
```js filename="eslint.config.mjs"
import { dirname } from 'path'
import { fileURLToPath } from 'url'
import { FlatCompat } from '@eslint/eslintrc'
const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
const compat = new FlatCompat({
baseDirectory: __dirname,
})
const eslintConfig = [
...compat.extends('next/core-web-vitals', 'next/typescript'),
{
ignores: [
'node_modules/**',
'.next/**',
'out/**',
'build/**',
'next-env.d.ts',
],
},
]
export default eslintConfig
```
### 15.0
#### Transform App Router Route Segment Config `runtime` value from `experimental-edge` to `edge`
##### `app-dir-runtime-config-experimental-edge`
> **Note**: This codemod is App Router specific.
```bash filename="Terminal"
npx @next/codemod@latest app-dir-runtime-config-experimental-edge .
```
This codemod transforms [Route Segment Config `runtime`](https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config/runtime) value `experimental-edge` to `edge`.
For example:
```ts
export const runtime = 'experimental-edge'
```
Transforms into:
```ts
export const runtime = 'edge'
```
#### Migrate to async Dynamic APIs
APIs that opted into dynamic rendering that previously supported synchronous access are now asynchronous. You can read more about this breaking change in the [upgrade guide](/docs/app/guides/upgrading/version-15).
##### `next-async-request-api`
```bash filename="Terminal"
npx @next/codemod@latest next-async-request-api .
```
This codemod will transform dynamic APIs (`cookies()`, `headers()` and `draftMode()` from `next/headers`) that are now asynchronous to be properly awaited or wrapped with `React.use()` if applicable.
When an automatic migration isn't possible, the codemod will either add a typecast (if a TypeScript file) or a comment to inform the user that it needs to be manually reviewed & updated.
For example:
```tsx
import { cookies, headers } from 'next/headers'
const token = cookies().get('token')
function useToken() {
const token = cookies().get('token')
return token
}
export default function Page() {
const name = cookies().get('name')
}
function getHeader() {
return headers().get('x-foo')
}
```
Transforms into:
```tsx
import { use } from 'react'
import {
cookies,
headers,
type UnsafeUnwrappedCookies,
type UnsafeUnwrappedHeaders,
} from 'next/headers'
const token = (cookies() as unknown as UnsafeUnwrappedCookies).get('token')
function useToken() {
const token = use(cookies()).get('token')
return token
}
export default async function Page() {
const name = (await cookies()).get('name')
}
function getHeader() {
return (headers() as unknown as UnsafeUnwrappedHeaders).get('x-foo')
}
```
When we detect property access on the `params` or `searchParams` props in the page / route entries (`page.js`, `layout.js`, `route.js`, or `default.js`) or the `generateMetadata` / `generateViewport` APIs,
it will attempt to transform the callsite from a sync to an async function, and await the property access. If it can't be made async (such as with a Client Component), it will use `React.use` to unwrap the promise .
For example:
```tsx
// page.tsx
export default function Page({
params,
searchParams,
}: {
params: { slug: string }
searchParams: { [key: string]: string | string[] | undefined }
}) {
const { value } = searchParams
if (value === 'foo') {
// ...
}
}
export function generateMetadata({ params }: { params: { slug: string } }) {
const { slug } = params
return {
title: `My Page - ${slug}`,
}
}
```
Transforms into:
```tsx
// page.tsx
export default async function Page(props: {
params: Promise<{ slug: string }>
searchParams: Promise<{ [key: string]: string | string[] | undefined }>
}) {
const searchParams = await props.searchParams
const { value } = searchParams
if (value === 'foo') {
// ...
}
}
export async function generateMetadata(props: {
params: Promise<{ slug: string }>
}) {
const params = await props.params
const { slug } = params
return {
title: `My Page - ${slug}`,
}
}
```
> **Good to know:** When this codemod identifies a spot that might require manual intervention, but we aren't able to determine the exact fix, it will add a comment or typecast to the code to inform the user that it needs to be manually updated. These comments are prefixed with **@next/codemod**, and typecasts are prefixed with `UnsafeUnwrapped`.
> Your build will error until these comments are explicitly removed. [Read more](/docs/messages/sync-dynamic-apis).
#### Replace `geo` and `ip` properties of `NextRequest` with `@vercel/functions`
##### `next-request-geo-ip`
```bash filename="Terminal"
npx @next/codemod@latest next-request-geo-ip .
```
This codemod installs `@vercel/functions` and transforms `geo` and `ip` properties of `NextRequest` with corresponding `@vercel/functions` features.
For example:
```ts
import type { NextRequest } from 'next/server'
export function GET(req: NextRequest) {
const { geo, ip } = req
}
```
Transforms into:
```ts
import type { NextRequest } from 'next/server'
import { geolocation, ipAddress } from '@vercel/functions'
export function GET(req: NextRequest) {
const geo = geolocation(req)
const ip = ipAddress(req)
}
```
### 14.0
#### Migrate `ImageResponse` imports
##### `next-og-import`
```bash filename="Terminal"
npx @next/codemod@latest next-og-import .
```
This codemod moves transforms imports from `next/server` to `next/og` for usage of [Dynamic OG Image Generation](/docs/app/getting-started/metadata-and-og-images#generated-open-graph-images).
For example:
```js
import { ImageResponse } from 'next/server'
```
Transforms into:
```js
import { ImageResponse } from 'next/og'
```
#### Use `viewport` export
##### `metadata-to-viewport-export`
```bash filename="Terminal"
npx @next/codemod@latest metadata-to-viewport-export .
```
This codemod migrates certain viewport metadata to `viewport` export.
For example:
```js
export const metadata = {
title: 'My App',
themeColor: 'dark',
viewport: {
width: 1,
},
}
```
Transforms into:
```js
export const metadata = {
title: 'My App',
}
export const viewport = {
width: 1,
themeColor: 'dark',
}
```
### 13.2
#### Use Built-in Font
##### `built-in-next-font`
```bash filename="Terminal"
npx @next/codemod@latest built-in-next-font .
```
This codemod uninstalls the `@next/font` package and transforms `@next/font` imports into the built-in `next/font`.
For example:
```js
import { Inter } from '@next/font/google'
```
Transforms into:
```js
import { Inter } from 'next/font/google'
```
### 13.0
#### Rename Next Image Imports
##### `next-image-to-legacy-image`
```bash filename="Terminal"
npx @next/codemod@latest next-image-to-legacy-image .
```
Safely renames `next/image` imports in existing Next.js 10, 11, or 12 applications to `next/legacy/image` in Next.js 13. Also renames `next/future/image` to `next/image`.
For example:
```jsx filename="pages/index.js"
import Image1 from 'next/image'
import Image2 from 'next/future/image'
export default function Home() {
return (
<div>
<Image1 src="/test.jpg" width="200" height="300" />
<Image2 src="/test.png" width="500" height="400" />
</div>
)
}
```
Transforms into:
```jsx filename="pages/index.js"
// 'next/image' becomes 'next/legacy/image'
import Image1 from 'next/legacy/image'
// 'next/future/image' becomes 'next/image'
import Image2 from 'next/image'
export default function Home() {
return (
<div>
<Image1 src="/test.jpg" width="200" height="300" />
<Image2 src="/test.png" width="500" height="400" />
</div>
)
}
```
#### Migrate to the New Image Component
##### `next-image-experimental`
```bash filename="Terminal"
npx @next/codemod@latest next-image-experimental .
```
Dangerously migrates from `next/legacy/image` to the new `next/image` by adding inline styles and removing unused props.
- Removes `layout` prop and adds `style`.
- Removes `objectFit` prop and adds `style`.
- Removes `objectPosition` prop and adds `style`.
- Removes `lazyBoundary` prop.
- Removes `lazyRoot` prop.
#### Remove `<a>` Tags From Link Components
##### `new-link`
```bash filename="Terminal"
npx @next/codemod@latest new-link .
```
<AppOnly>
Remove `<a>` tags inside [Link Components](/docs/app/api-reference/components/link).
</AppOnly>
<PagesOnly>
Remove `<a>` tags inside [Link Components](/docs/pages/api-reference/components/link).
</PagesOnly>
For example:
```jsx
<Link href="/about">
<a>About</a>
</Link>
// transforms into
<Link href="/about">
About
</Link>
<Link href="/about">
<a onClick={() => console.log('clicked')}>About</a>
</Link>
// transforms into
<Link href="/about" onClick={() => console.log('clicked')}>
About
</Link>
```
### 11
#### Migrate from CRA
##### `cra-to-next`
```bash filename="Terminal"
npx @next/codemod cra-to-next
```
Migrates a Create React App project to Next.js; creating a Pages Router and necessary config to match behavior. Client-side only rendering is leveraged initially to prevent breaking compatibility due to `window` usage during SSR and can be enabled seamlessly to allow the gradual adoption of Next.js specific features.
Please share any feedback related to this transform [in this discussion](https://github.com/vercel/next.js/discussions/25858).
### 10
#### Add React imports
##### `add-missing-react-import`
```bash filename="Terminal"
npx @next/codemod add-missing-react-import
```
Transforms files that do not import `React` to include the import in order for the new [React JSX transform](https://reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html) to work.
For example:
```jsx filename="my-component.js"
export default class Home extends React.Component {
render() {
return <div>Hello World</div>
}
}
```
Transforms into:
```jsx filename="my-component.js"
import React from 'react'
export default class Home extends React.Component {
render() {
return <div>Hello World</div>
}
}
```
### 9
#### Transform Anonymous Components into Named Components
##### `name-default-component`
```bash filename="Terminal"
npx @next/codemod name-default-component
```
**Versions 9 and above.**
Transforms anonymous components into named components to make sure they work with [Fast Refresh](https://nextjs.org/blog/next-9-4#fast-refresh).
For example:
```jsx filename="my-component.js"
export default function () {
return <div>Hello World</div>
}
```
Transforms into:
```jsx filename="my-component.js"
export default function MyComponent() {
return <div>Hello World</div>
}
```
The component will have a camel-cased name based on the name of the file, and it also works with arrow functions.
### 8
> **Note**: Built-in AMP support and this codemod have been removed in Next.js 16.
#### Transform AMP HOC into page config
##### `withamp-to-config`
```bash filename="Terminal"
npx @next/codemod withamp-to-config
```
Transforms the `withAmp` HOC into Next.js 9 page configuration.
For example:
```js
// Before
import { withAmp } from 'next/amp'
function Home() {
return <h1>My AMP Page</h1>
}
export default withAmp(Home)
```
```js
// After
export default function Home() {
return <h1>My AMP Page</h1>
}
export const config = {
amp: true,
}
```
### 6
#### Use `withRouter`
##### `url-to-withrouter`
```bash filename="Terminal"
npx @next/codemod url-to-withrouter
```
Transforms the deprecated automatically injected `url` property on top level pages to using `withRouter` and the `router` property it injects. Read more here: [https://nextjs.org/docs/messages/url-deprecated](/docs/messages/url-deprecated)
For example:
```js filename="From"
import React from 'react'
export default class extends React.Component {
render() {
const { pathname } = this.props.url
return <div>Current pathname: {pathname}</div>
}
}
```
```js filename="To"
import React from 'react'
import { withRouter } from 'next/router'
export default withRouter(
class extends React.Component {
render() {
const { pathname } = this.props.router
return <div>Current pathname: {pathname}</div>
}
}
)
```
This is one case. All the cases that are transformed (and tested) can be found in the [`__testfixtures__` directory](https://github.com/vercel/next.js/tree/canary/packages/next-codemod/transforms/__testfixtures__/url-to-withrouter).
+7
View File
@@ -0,0 +1,7 @@
---
title: Upgrade Guides
nav_title: Upgrading
description: Learn how to upgrade to the latest versions of Next.js.
---
Learn how to upgrade to the latest versions of Next.js following the versions-specific guides:
+37
View File
@@ -0,0 +1,37 @@
---
title: How to upgrade to version 14
nav_title: Version 14
description: Upgrade your Next.js Application from Version 13 to 14.
---
{/* 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. */}
## Upgrading from 13 to 14
To update to Next.js version 14, run the following command using your preferred package manager:
```bash filename="Terminal"
npm i next@next-14 react@18 react-dom@18 && npm i eslint-config-next@next-14 -D
```
```bash filename="Terminal"
yarn add next@next-14 react@18 react-dom@18 && yarn add eslint-config-next@next-14 -D
```
```bash filename="Terminal"
pnpm i next@next-14 react@18 react-dom@18 && pnpm i eslint-config-next@next-14 -D
```
```bash filename="Terminal"
bun add next@next-14 react@18 react-dom@18 && bun add eslint-config-next@next-14 -D
```
> **Good to know:** If you are using TypeScript, ensure you also upgrade `@types/react` and `@types/react-dom` to their latest versions.
### v14 Summary
- The minimum Node.js version has been bumped from 16.14 to 18.17, since 16.x has reached end-of-life.
- The `next export` command has been removed in favor of `output: 'export'` config. Please see the [docs](https://nextjs.org/docs/app/guides/static-exports) for more information.
- The `next/server` import for `ImageResponse` was renamed to `next/og`. A [codemod is available](/docs/app/guides/upgrading/codemods#next-og-import) to safely and automatically rename your imports.
- The `@next/font` package has been fully removed in favor of the built-in `next/font`. A [codemod is available](/docs/app/guides/upgrading/codemods#built-in-next-font) to safely and automatically rename your imports.
- The WASM target for `next-swc` has been removed.
+627
View File
@@ -0,0 +1,627 @@
---
title: How to upgrade to version 15
nav_title: Version 15
description: Upgrade your Next.js Application from Version 14 to 15.
---
{/* 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. */}
## Upgrading from 14 to 15
To update to Next.js version 15, you can use the `upgrade` codemod:
```bash package="pnpm"
pnpm dlx @next/codemod@canary upgrade latest
```
```bash package="npm"
npx @next/codemod@canary upgrade latest
```
```bash package="yarn"
yarn dlx @next/codemod@canary upgrade latest
```
```bash package="bun"
bunx @next/codemod@canary upgrade latest
```
If you prefer to do it manually, ensure that you're installing the latest Next & React versions:
```bash package="pnpm"
pnpm add next@latest react@latest react-dom@latest eslint-config-next@latest
```
```bash package="npm"
npm install next@latest react@latest react-dom@latest eslint-config-next@latest
```
```bash package="yarn"
yarn add next@latest react@latest react-dom@latest eslint-config-next@latest
```
```bash package="bun"
bun add next@latest react@latest react-dom@latest eslint-config-next@latest
```
> **Good to know:**
>
> - If you see a peer dependencies warning, you may need to update `react` and `react-dom` to the suggested versions, or use the `--force` or `--legacy-peer-deps` flag to ignore the warning. This won't be necessary once both Next.js 15 and React 19 are stable.
## React 19
- The minimum versions of `react` and `react-dom` is now 19.
- `useFormState` has been replaced by `useActionState`. The `useFormState` hook is still available in React 19, but it is deprecated and will be removed in a future release. `useActionState` is recommended and includes additional properties like reading the `pending` state directly. [Learn more](https://react.dev/reference/react/useActionState).
- `useFormStatus` now includes additional keys like `data`, `method`, and `action`. If you are not using React 19, only the `pending` key is available. [Learn more](https://react.dev/reference/react-dom/hooks/useFormStatus).
- Read more in the [React 19 upgrade guide](https://react.dev/blog/2024/04/25/react-19-upgrade-guide).
> **Good to know:** If you are using TypeScript, ensure you also upgrade `@types/react` and `@types/react-dom` to their latest versions.
## Async Request APIs (Breaking change)
Previously synchronous Request-time APIs that rely on request information are now **asynchronous**:
- [`cookies`](/docs/app/api-reference/functions/cookies)
- [`headers`](/docs/app/api-reference/functions/headers)
- [`draftMode`](/docs/app/api-reference/functions/draft-mode)
- `params` in [`layout.js`](/docs/app/api-reference/file-conventions/layout), [`page.js`](/docs/app/api-reference/file-conventions/page), [`route.js`](/docs/app/api-reference/file-conventions/route), [`default.js`](/docs/app/api-reference/file-conventions/default), [`opengraph-image`](/docs/app/api-reference/file-conventions/metadata/opengraph-image), [`twitter-image`](/docs/app/api-reference/file-conventions/metadata/opengraph-image), [`icon`](/docs/app/api-reference/file-conventions/metadata/app-icons), and [`apple-icon`](/docs/app/api-reference/file-conventions/metadata/app-icons).
- `searchParams` in [`page.js`](/docs/app/api-reference/file-conventions/page)
To ease the burden of migration, a [codemod is available](/docs/app/guides/upgrading/codemods#150) to automate the process and the APIs can temporarily be accessed synchronously.
### `cookies`
#### Recommended Async Usage
```tsx
import { cookies } from 'next/headers'
// Before
const cookieStore = cookies()
const token = cookieStore.get('token')
// After
const cookieStore = await cookies()
const token = cookieStore.get('token')
```
#### Temporary Synchronous Usage
```tsx filename="app/page.tsx" switcher
import { cookies, type UnsafeUnwrappedCookies } from 'next/headers'
// Before
const cookieStore = cookies()
const token = cookieStore.get('token')
// After
const cookieStore = cookies() as unknown as UnsafeUnwrappedCookies
// will log a warning in dev
const token = cookieStore.get('token')
```
```jsx filename="app/page.js" switcher
import { cookies } from 'next/headers'
// Before
const cookieStore = cookies()
const token = cookieStore.get('token')
// After
const cookieStore = cookies()
// will log a warning in dev
const token = cookieStore.get('token')
```
### `headers`
#### Recommended Async Usage
```tsx
import { headers } from 'next/headers'
// Before
const headersList = headers()
const userAgent = headersList.get('user-agent')
// After
const headersList = await headers()
const userAgent = headersList.get('user-agent')
```
#### Temporary Synchronous Usage
```tsx filename="app/page.tsx" switcher
import { headers, type UnsafeUnwrappedHeaders } from 'next/headers'
// Before
const headersList = headers()
const userAgent = headersList.get('user-agent')
// After
const headersList = headers() as unknown as UnsafeUnwrappedHeaders
// will log a warning in dev
const userAgent = headersList.get('user-agent')
```
```jsx filename="app/page.js" switcher
import { headers } from 'next/headers'
// Before
const headersList = headers()
const userAgent = headersList.get('user-agent')
// After
const headersList = headers()
// will log a warning in dev
const userAgent = headersList.get('user-agent')
```
### `draftMode`
#### Recommended Async Usage
```tsx
import { draftMode } from 'next/headers'
// Before
const { isEnabled } = draftMode()
// After
const { isEnabled } = await draftMode()
```
#### Temporary Synchronous Usage
```tsx filename="app/page.tsx" switcher
import { draftMode, type UnsafeUnwrappedDraftMode } from 'next/headers'
// Before
const { isEnabled } = draftMode()
// After
// will log a warning in dev
const { isEnabled } = draftMode() as unknown as UnsafeUnwrappedDraftMode
```
```jsx filename="app/page.js" switcher
import { draftMode } from 'next/headers'
// Before
const { isEnabled } = draftMode()
// After
// will log a warning in dev
const { isEnabled } = draftMode()
```
### `params` & `searchParams`
#### Asynchronous Layout
```tsx filename="app/layout.tsx" switcher
// Before
type Params = { slug: string }
export function generateMetadata({ params }: { params: Params }) {
const { slug } = params
}
export default async function Layout({
children,
params,
}: {
children: React.ReactNode
params: Params
}) {
const { slug } = params
}
// After
type Params = Promise<{ slug: string }>
export async function generateMetadata({ params }: { params: Params }) {
const { slug } = await params
}
export default async function Layout({
children,
params,
}: {
children: React.ReactNode
params: Params
}) {
const { slug } = await params
}
```
```jsx filename="app/layout.js" switcher
// Before
export function generateMetadata({ params }) {
const { slug } = params
}
export default async function Layout({ children, params }) {
const { slug } = params
}
// After
export async function generateMetadata({ params }) {
const { slug } = await params
}
export default async function Layout({ children, params }) {
const { slug } = await params
}
```
#### Synchronous Layout
```tsx filename="app/layout.tsx" switcher
// Before
type Params = { slug: string }
export default function Layout({
children,
params,
}: {
children: React.ReactNode
params: Params
}) {
const { slug } = params
}
// After
import { use } from 'react'
type Params = Promise<{ slug: string }>
export default function Layout(props: {
children: React.ReactNode
params: Params
}) {
const params = use(props.params)
const slug = params.slug
}
```
```jsx filename="app/layout.js" switcher
// Before
export default function Layout({ children, params }) {
const { slug } = params
}
// After
import { use } from 'react'
export default async function Layout(props) {
const params = use(props.params)
const slug = params.slug
}
```
#### Asynchronous Page
```tsx filename="app/page.tsx" switcher
// Before
type Params = { slug: string }
type SearchParams = { [key: string]: string | string[] | undefined }
export function generateMetadata({
params,
searchParams,
}: {
params: Params
searchParams: SearchParams
}) {
const { slug } = params
const { query } = searchParams
}
export default async function Page({
params,
searchParams,
}: {
params: Params
searchParams: SearchParams
}) {
const { slug } = params
const { query } = searchParams
}
// After
type Params = Promise<{ slug: string }>
type SearchParams = Promise<{ [key: string]: string | string[] | undefined }>
export async function generateMetadata(props: {
params: Params
searchParams: SearchParams
}) {
const params = await props.params
const searchParams = await props.searchParams
const slug = params.slug
const query = searchParams.query
}
export default async function Page(props: {
params: Params
searchParams: SearchParams
}) {
const params = await props.params
const searchParams = await props.searchParams
const slug = params.slug
const query = searchParams.query
}
```
```jsx filename="app/page.js" switcher
// Before
export function generateMetadata({ params, searchParams }) {
const { slug } = params
const { query } = searchParams
}
export default function Page({ params, searchParams }) {
const { slug } = params
const { query } = searchParams
}
// After
export async function generateMetadata(props) {
const params = await props.params
const searchParams = await props.searchParams
const slug = params.slug
const query = searchParams.query
}
export async function Page(props) {
const params = await props.params
const searchParams = await props.searchParams
const slug = params.slug
const query = searchParams.query
}
```
#### Synchronous Page
```tsx
'use client'
// Before
type Params = { slug: string }
type SearchParams = { [key: string]: string | string[] | undefined }
export default function Page({
params,
searchParams,
}: {
params: Params
searchParams: SearchParams
}) {
const { slug } = params
const { query } = searchParams
}
// After
import { use } from 'react'
type Params = Promise<{ slug: string }>
type SearchParams = Promise<{ [key: string]: string | string[] | undefined }>
export default function Page(props: {
params: Params
searchParams: SearchParams
}) {
const params = use(props.params)
const searchParams = use(props.searchParams)
const slug = params.slug
const query = searchParams.query
}
```
```jsx
// Before
export default function Page({ params, searchParams }) {
const { slug } = params
const { query } = searchParams
}
// After
import { use } from "react"
export default function Page(props) {
const params = use(props.params)
const searchParams = use(props.searchParams)
const slug = params.slug
const query = searchParams.query
}
```
#### Route Handlers
```tsx filename="app/api/route.ts" switcher
// Before
type Params = { slug: string }
export async function GET(request: Request, segmentData: { params: Params }) {
const params = segmentData.params
const slug = params.slug
}
// After
type Params = Promise<{ slug: string }>
export async function GET(request: Request, segmentData: { params: Params }) {
const params = await segmentData.params
const slug = params.slug
}
```
```js filename="app/api/route.js" switcher
// Before
export async function GET(request, segmentData) {
const params = segmentData.params
const slug = params.slug
}
// After
export async function GET(request, segmentData) {
const params = await segmentData.params
const slug = params.slug
}
```
<AppOnly>
## `runtime` configuration (Breaking change)
The `runtime` [segment configuration](/docs/app/api-reference/file-conventions/route-segment-config/runtime) previously supported a value of `experimental-edge` in addition to `edge`. Both configurations refer to the same thing, and to simplify the options, we will now error if `experimental-edge` is used. To fix this, update your `runtime` configuration to `edge`. A [codemod](/docs/app/guides/upgrading/codemods#app-dir-runtime-config-experimental-edge) is available to automatically do this.
</AppOnly>
## `fetch` requests
[`fetch` requests](/docs/app/api-reference/functions/fetch) are no longer cached by default.
To opt specific `fetch` requests into caching, you can pass the `cache: 'force-cache'` option.
```js filename="app/layout.js"
export default async function RootLayout() {
const a = await fetch('https://...') // Not Cached
const b = await fetch('https://...', { cache: 'force-cache' }) // Cached
// ...
}
```
To opt all `fetch` requests in a layout or page into caching, you can use the `export const fetchCache = 'default-cache'` [segment config option](/docs/app/api-reference/file-conventions/route-segment-config). If individual `fetch` requests specify a `cache` option, that will be used instead.
```js filename="app/layout.js"
// Since this is the root layout, all fetch requests in the app
// that don't set their own cache option will be cached.
export const fetchCache = 'default-cache'
export default async function RootLayout() {
const a = await fetch('https://...') // Cached
const b = await fetch('https://...', { cache: 'no-store' }) // Not cached
// ...
}
```
## Route Handlers
`GET` functions in [Route Handlers](/docs/app/api-reference/file-conventions/route) are no longer cached by default. To opt `GET` methods into caching, you can use a [route config option](/docs/app/api-reference/file-conventions/route-segment-config) such as `export const dynamic = 'force-static'` in your Route Handler file.
```js filename="app/api/route.js"
export const dynamic = 'force-static'
export async function GET() {}
```
## Client Cache
When navigating between pages via `<Link>` or `useRouter`, [page](/docs/app/api-reference/file-conventions/page) segments are no longer reused from the [Client Cache](/docs/app/glossary#client-cache). However, they are still reused during browser backward and forward navigation and for shared layouts.
To opt page segments into caching, you can use the [`staleTimes`](/docs/app/api-reference/config/next-config-js/staleTimes) config option:
```js filename="next.config.js"
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
staleTimes: {
dynamic: 30,
static: 180,
},
},
}
module.exports = nextConfig
```
[Layouts](/docs/app/api-reference/file-conventions/layout) and [loading states](/docs/app/api-reference/file-conventions/loading) are still cached and reused on navigation.
## `next/font`
The `@next/font` package has been removed in favor of the built-in [`next/font`](/docs/app/api-reference/components/font). A [codemod is available](/docs/app/guides/upgrading/codemods#built-in-next-font) to safely and automatically rename your imports.
```js filename="app/layout.js"
// Before
import { Inter } from '@next/font/google'
// After
import { Inter } from 'next/font/google'
```
## bundlePagesRouterDependencies
`experimental.bundlePagesExternals` is now stable and renamed to `bundlePagesRouterDependencies`.
```js filename="next.config.js"
/** @type {import('next').NextConfig} */
const nextConfig = {
// Before
experimental: {
bundlePagesExternals: true,
},
// After
bundlePagesRouterDependencies: true,
}
module.exports = nextConfig
```
## serverExternalPackages
`experimental.serverComponentsExternalPackages` is now stable and renamed to `serverExternalPackages`.
```js filename="next.config.js"
/** @type {import('next').NextConfig} */
const nextConfig = {
// Before
experimental: {
serverComponentsExternalPackages: ['package-name'],
},
// After
serverExternalPackages: ['package-name'],
}
module.exports = nextConfig
```
## Speed Insights
Auto instrumentation for Speed Insights was removed in Next.js 15.
To continue using Speed Insights, follow the [Vercel Speed Insights Quickstart](https://vercel.com/docs/speed-insights/quickstart) guide.
## `NextRequest` Geolocation
The `geo` and `ip` properties on `NextRequest` have been removed as these values are provided by your hosting provider. A [codemod](/docs/app/guides/upgrading/codemods#150) is available to automate this migration.
If you are using Vercel, you can alternatively use the `geolocation` and `ipAddress` functions from [`@vercel/functions`](https://vercel.com/docs/functions/vercel-functions-package) instead:
```ts filename="middleware.ts"
import { geolocation } from '@vercel/functions'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
const { city } = geolocation(request)
// ...
}
```
```ts filename="middleware.ts"
import { ipAddress } from '@vercel/functions'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
const ip = ipAddress(request)
// ...
}
```
File diff suppressed because it is too large Load Diff
+270
View File
@@ -0,0 +1,270 @@
---
title: How to use and optimize videos
nav_title: Videos
description: Recommendations and best practices for optimizing videos in your Next.js application.
---
This page outlines how to use videos with Next.js applications, showing how to store and display video files without affecting performance.
## Using `<video>` and `<iframe>`
Videos can be embedded on the page using the HTML **`<video>`** tag for direct video files and **`<iframe>`** for external platform-hosted videos.
### `<video>`
The HTML [`<video>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video) tag can embed self-hosted or directly served video content, allowing full control over the playback and appearance.
```jsx filename="app/ui/video.jsx"
export function Video() {
return (
<video width="320" height="240" controls preload="none">
<source src="/path/to/video.mp4" type="video/mp4" />
<track
src="/path/to/captions.vtt"
kind="subtitles"
srcLang="en"
label="English"
/>
Your browser does not support the video tag.
</video>
)
}
```
### Common `<video>` tag attributes
| Attribute | Description | Example Value |
| ------------- | --------------------------------------------------------------------------------------------------------- | ------------------------------------ |
| `src` | Specifies the source of the video file. | `<video src="/path/to/video.mp4" />` |
| `width` | Sets the width of the video player. | `<video width="320" />` |
| `height` | Sets the height of the video player. | `<video height="240" />` |
| `controls` | If present, it displays the default set of playback controls. | `<video controls />` |
| `autoPlay` | Automatically starts playing the video when the page loads. Note: Autoplay policies vary across browsers. | `<video autoPlay />` |
| `loop` | Loops the video playback. | `<video loop />` |
| `muted` | Mutes the audio by default. Often used with `autoPlay`. | `<video muted />` |
| `preload` | Specifies how the video is preloaded. Values: `none`, `metadata`, `auto`. | `<video preload="none" />` |
| `playsInline` | Enables inline playback on iOS devices, often necessary for autoplay to work on iOS Safari. | `<video playsInline />` |
> **Good to know**: When using the `autoPlay` attribute, it is important to also include the `muted` attribute to ensure the video plays automatically in most browsers and the `playsInline` attribute for compatibility with iOS devices.
For a comprehensive list of video attributes, refer to the [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video#attributes).
### Video best practices
- **Fallback Content:** When using the `<video>` tag, include fallback content inside the tag for browsers that do not support video playback.
- **Subtitles or Captions:** Include subtitles or captions for users who are deaf or hard of hearing. Utilize the [`<track>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/track) tag with your `<video>` elements to specify caption file sources.
- **Accessible Controls:** Standard HTML5 video controls are recommended for keyboard navigation and screen reader compatibility. For advanced needs, consider third-party players like [react-player](https://github.com/cookpete/react-player) or [video.js](https://videojs.com/), which offer accessible controls and consistent browser experience.
### `<iframe>`
The HTML `<iframe>` tag allows you to embed videos from external platforms like YouTube or Vimeo.
```jsx filename="app/page.jsx"
export default function Page() {
return (
<iframe src="https://www.youtube.com/embed/19g66ezsKAg" allowFullScreen />
)
}
```
### Common `<iframe>` tag attributes
| Attribute | Description | Example Value |
| ----------------- | ---------------------------------------------------------------------- | -------------------------------------- |
| `src` | The URL of the page to embed. | `<iframe src="https://example.com" />` |
| `width` | Sets the width of the iframe. | `<iframe width="500" />` |
| `height` | Sets the height of the iframe. | `<iframe height="300" />` |
| `allowFullScreen` | Allows the iframe content to be displayed in full-screen mode. | `<iframe allowFullScreen />` |
| `sandbox` | Enables an extra set of restrictions on the content within the iframe. | `<iframe sandbox />` |
| `loading` | Optimize loading behavior (e.g., lazy loading). | `<iframe loading="lazy" />` |
| `title` | Provides a title for the iframe to support accessibility. | `<iframe title="Description" />` |
For a comprehensive list of iframe attributes, refer to the [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#attributes).
### Choosing a video embedding method
There are two ways you can embed videos in your Next.js application:
- **Self-hosted or direct video files:** Embed self-hosted videos using the `<video>` tag for scenarios requiring detailed control over the player's functionality and appearance. This integration method within Next.js allows for customization and control of your video content.
- **Using video hosting services (YouTube, Vimeo, etc.):** For video hosting services like YouTube or Vimeo, you'll embed their iframe-based players using the `<iframe>` tag. While this method limits some control over the player, it offers ease of use and features provided by these platforms.
Choose the embedding method that aligns with your application's requirements and the user experience you aim to deliver.
### Embedding externally hosted videos
To embed videos from external platforms, you can use Next.js to fetch the video information and React Suspense to handle the fallback state while loading.
**1. Create a Server Component for video embedding**
The first step is to create a [Server Component](/docs/app/getting-started/server-and-client-components) that generates the appropriate iframe for embedding the video. This component will fetch the source URL for the video and render the iframe.
```jsx filename="app/ui/video-component.jsx"
export default async function VideoComponent() {
const src = await getVideoSrc()
return <iframe src={src} allowFullScreen />
}
```
**2. Stream the video component using React Suspense**
After creating the Server Component to embed the video, the next step is to [stream](/docs/app/api-reference/file-conventions/loading) the component using [React Suspense](https://react.dev/reference/react/Suspense).
```jsx filename="app/page.jsx"
import { Suspense } from 'react'
import VideoComponent from '../ui/VideoComponent.jsx'
export default function Page() {
return (
<section>
<Suspense fallback={<p>Loading video...</p>}>
<VideoComponent />
</Suspense>
{/* Other content of the page */}
</section>
)
}
```
> **Good to know**: When embedding videos from external platforms, consider the following best practices:
>
> - Ensure the video embeds are responsive. Use CSS to make the iframe or video player adapt to different screen sizes.
> - Implement [strategies for loading videos](https://yoast.com/site-speed-tips-for-faster-video/) based on network conditions, especially for users with limited data plans.
This approach results in a better user experience as it prevents the page from blocking, meaning the user can interact with the page while the video component streams in.
For a more engaging and informative loading experience, consider using a loading skeleton as the fallback UI. So instead of showing a simple loading message, you can show a skeleton that resembles the video player like this:
```jsx filename="app/page.jsx"
import { Suspense } from 'react'
import VideoComponent from '../ui/VideoComponent.jsx'
import VideoSkeleton from '../ui/VideoSkeleton.jsx'
export default function Page() {
return (
<section>
<Suspense fallback={<VideoSkeleton />}>
<VideoComponent />
</Suspense>
{/* Other content of the page */}
</section>
)
}
```
## Self-hosted videos
Self-hosting videos may be preferable for several reasons:
- **Complete control and independence**: Self-hosting gives you direct management over your video content, from playback to appearance, ensuring full ownership and control, free from external platform constraints.
- **Customization for specific needs**: Ideal for unique requirements, like dynamic background videos, it allows for tailored customization to align with design and functional needs.
- **Performance and scalability considerations**: Choose storage solutions that are both high-performing and scalable, to support increasing traffic and content size effectively.
- **Cost and integration**: Balance the costs of storage and bandwidth with the need for easy integration into your Next.js framework and broader tech ecosystem.
### Using Vercel Blob for video hosting
[Vercel Blob](https://vercel.com/docs/storage/vercel-blob?utm_source=next-site&utm_medium=docs&utm_campaign=next-website) offers an efficient way to host videos, providing a scalable cloud storage solution that works well with Next.js. Here's how you can host a video using Vercel Blob:
**1. Uploading a video to Vercel Blob**
In your Vercel dashboard, navigate to the "Storage" tab and select your [Vercel Blob](https://vercel.com/docs/storage/vercel-blob?utm_source=next-site&utm_medium=docs&utm_campaign=next-website) store. In the Blob table's upper-right corner, find and click the "Upload" button. Then, choose the video file you wish to upload. After the upload completes, the video file will appear in the Blob table.
Alternatively, you can upload your video using a server action. For detailed instructions, refer to the Vercel documentation on [server-side uploads](https://vercel.com/docs/storage/vercel-blob/server-upload). Vercel also supports [client-side uploads](https://vercel.com/docs/storage/vercel-blob/client-upload). This method may be preferable for certain use cases.
**2. Displaying the video in Next.js**
Once the video is uploaded and stored, you can display it in your Next.js application. Here's an example of how to do this using the `<video>` tag and React Suspense:
```jsx filename="app/page.jsx"
import { Suspense } from 'react'
import { list } from '@vercel/blob'
export default function Page() {
return (
<Suspense fallback={<p>Loading video...</p>}>
<VideoComponent fileName="my-video.mp4" />
</Suspense>
)
}
async function VideoComponent({ fileName }) {
const { blobs } = await list({
prefix: fileName,
limit: 1,
})
const { url } = blobs[0]
return (
<video controls preload="none" aria-label="Video player">
<source src={url} type="video/mp4" />
Your browser does not support the video tag.
</video>
)
}
```
In this approach, the page uses the video's `@vercel/blob` URL to display the video using the `VideoComponent`. React Suspense is used to show a fallback until the video URL is fetched and the video is ready to be displayed.
### Adding subtitles to your video
If you have subtitles for your video, you can easily add them using the `<track>` element inside your `<video>` tag. You can fetch the subtitle file from [Vercel Blob](https://vercel.com/docs/storage/vercel-blob?utm_source=next-site&utm_medium=docs&utm_campaign=next-website) in a similar way as the video file. Here's how you can update the `<VideoComponent>` to include subtitles.
```jsx filename="app/page.jsx"
async function VideoComponent({ fileName }) {
const { blobs } = await list({
prefix: fileName,
limit: 2,
})
const { url } = blobs[0]
const { url: captionsUrl } = blobs[1]
return (
<video controls preload="none" aria-label="Video player">
<source src={url} type="video/mp4" />
<track src={captionsUrl} kind="subtitles" srcLang="en" label="English" />
Your browser does not support the video tag.
</video>
)
}
```
By following this approach, you can effectively self-host and integrate videos into your Next.js applications.
## Resources
To continue learning more about video optimization and best practices, please refer to the following resources:
- **Understanding video formats and codecs**: Choose the right format and codec, like MP4 for compatibility or WebM for web optimization, for your video needs. For more details, see [Mozilla's guide on video codecs](https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Video_codecs).
- **Video compression**: Use tools like FFmpeg to effectively compress videos, balancing quality with file size. Learn about compression techniques at [FFmpeg's official website](https://www.ffmpeg.org/).
- **Resolution and bitrate adjustment**: Adjust [resolution and bitrate](https://www.dacast.com/blog/bitrate-vs-resolution/#:~:text=The%20two%20measure%20different%20aspects,yield%20different%20qualities%20of%20video) based on the viewing platform, with lower settings for mobile devices.
- **Content Delivery Networks (CDNs)**: Utilize a CDN to enhance video delivery speed and manage high traffic. When using some storage solutions, such as Vercel Blob, CDN functionality is automatically handled for you. [Learn more](https://vercel.com/docs/edge-network/overview?utm_source=next-site&utm_medium=docs&utm_campaign=next-website) about CDNs and their benefits.
Explore these video streaming platforms for integrating video into your Next.js projects:
### Open source `next-video` component
- Provides a `<Video>` component for Next.js, compatible with various hosting services including [Vercel Blob](https://vercel.com/docs/storage/vercel-blob?utm_source=next-site&utm_medium=docs&utm_campaign=next-website), S3, Backblaze, and Mux.
- [Detailed documentation](https://next-video.dev/docs) for using `next-video.dev` with different hosting services.
### Cloudinary Integration
- Official [documentation and integration guide](https://next.cloudinary.dev/) for using Cloudinary with Next.js.
- Includes a `<CldVideoPlayer>` component for [drop-in video support](https://next.cloudinary.dev/cldvideoplayer/basic-usage).
- Find [examples](https://github.com/cloudinary-community/cloudinary-examples/?tab=readme-ov-file#nextjs) of integrating Cloudinary with Next.js including [Adaptive Bitrate Streaming](https://github.com/cloudinary-community/cloudinary-examples/tree/main/examples/nextjs-cldvideoplayer-abr).
- Other [Cloudinary libraries](https://cloudinary.com/documentation) including a Node.js SDK are also available.
### Mux Video API
- Mux provides a [starter template](https://github.com/muxinc/video-course-starter-kit) for creating a video course with Mux and Next.js.
- Learn about Mux's recommendations for embedding [high-performance video for your Next.js application](https://www.mux.com/for/nextjs).
- Explore an [example project](https://with-mux-video.vercel.app/) demonstrating Mux with Next.js.
### Fastly
- Learn more about integrating Fastly's solutions for [video on demand](https://www.fastly.com/products/streaming-media/video-on-demand) and streaming media into Next.js.
### ImageKit.io Integration
- Check out the [official quick start guide](https://imagekit.io/docs/integration/nextjs) for integrating ImageKit with Next.js.
- The integration provides an `<IKVideo>` component, offering [seamless video support](https://imagekit.io/docs/integration/nextjs#rendering-videos).
- You can also explore other [ImageKit libraries](https://imagekit.io/docs), such as the Node.js SDK, which is also available.