Auto-commit 2026-04-29 16:31
This commit is contained in:
429
node_modules/next/dist/docs/01-app/01-getting-started/01-installation.md
generated
vendored
Normal file
429
node_modules/next/dist/docs/01-app/01-getting-started/01-installation.md
generated
vendored
Normal file
@@ -0,0 +1,429 @@
|
||||
---
|
||||
title: Installation
|
||||
description: Learn how to create a new Next.js application with the `create-next-app` CLI, and set up TypeScript, ESLint, and Module Path Aliases.
|
||||
---
|
||||
|
||||
{/* 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>
|
||||
|
||||
Create a new Next.js app and run it locally.
|
||||
|
||||
## Quick start
|
||||
|
||||
1. Create a new Next.js app named `my-app`
|
||||
2. `cd my-app` and start the dev server.
|
||||
3. Visit `http://localhost:3000`.
|
||||
|
||||
```bash package="pnpm"
|
||||
pnpm create next-app@latest my-app --yes
|
||||
cd my-app
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
```bash package="npm"
|
||||
npx create-next-app@latest my-app --yes
|
||||
cd my-app
|
||||
npm run dev
|
||||
```
|
||||
|
||||
```bash package="yarn"
|
||||
yarn create next-app@latest my-app --yes
|
||||
cd my-app
|
||||
yarn dev
|
||||
```
|
||||
|
||||
```bash package="bun"
|
||||
bun create next-app@latest my-app --yes
|
||||
cd my-app
|
||||
bun dev
|
||||
```
|
||||
|
||||
- `--yes` skips prompts using saved preferences or defaults. The default setup enables TypeScript, Tailwind CSS, ESLint, App Router, and Turbopack, with import alias `@/*`, and includes `AGENTS.md` (with a `CLAUDE.md` that references it) to guide coding agents to write up-to-date Next.js code.
|
||||
|
||||
</AppOnly>
|
||||
|
||||
## System requirements
|
||||
|
||||
Before you begin, make sure your development environment meets the following requirements:
|
||||
|
||||
- Minimum Node.js version: [20.9](https://nodejs.org/)
|
||||
- Operating systems: macOS, Windows (including WSL), and Linux.
|
||||
|
||||
## Supported browsers
|
||||
|
||||
Next.js supports modern browsers with zero configuration.
|
||||
|
||||
- Chrome 111+
|
||||
- Edge 111+
|
||||
- Firefox 111+
|
||||
- Safari 16.4+
|
||||
|
||||
Learn more about [browser support](/docs/architecture/supported-browsers), including how to configure polyfills and target specific browsers.
|
||||
|
||||
## Create with the CLI
|
||||
|
||||
The quickest way to create a new Next.js app is using [`create-next-app`](/docs/app/api-reference/cli/create-next-app), which sets up everything automatically for you. To create a project, run:
|
||||
|
||||
```bash package="pnpm"
|
||||
pnpm create next-app
|
||||
```
|
||||
|
||||
```bash package="npm"
|
||||
npx create-next-app@latest
|
||||
```
|
||||
|
||||
```bash package="yarn"
|
||||
yarn create next-app
|
||||
```
|
||||
|
||||
```bash package="bun"
|
||||
bun create next-app
|
||||
```
|
||||
|
||||
On installation, you'll see the following prompts:
|
||||
|
||||
```txt filename="Terminal"
|
||||
What is your project named? my-app
|
||||
Would you like to use the recommended Next.js defaults?
|
||||
Yes, use recommended defaults - TypeScript, ESLint, Tailwind CSS, App Router, AGENTS.md
|
||||
No, reuse previous settings
|
||||
No, customize settings - Choose your own preferences
|
||||
```
|
||||
|
||||
If you choose to `customize settings`, you'll see the following prompts:
|
||||
|
||||
```txt filename="Terminal"
|
||||
Would you like to use TypeScript? No / Yes
|
||||
Which linter would you like to use? ESLint / Biome / None
|
||||
Would you like to use React Compiler? No / Yes
|
||||
Would you like to use Tailwind CSS? No / Yes
|
||||
Would you like your code inside a `src/` directory? No / Yes
|
||||
Would you like to use App Router? (recommended) No / Yes
|
||||
Would you like to customize the import alias (`@/*` by default)? No / Yes
|
||||
What import alias would you like configured? @/*
|
||||
Would you like to include AGENTS.md to guide coding agents to write up-to-date Next.js code? No / Yes
|
||||
```
|
||||
|
||||
After the prompts, [`create-next-app`](/docs/app/api-reference/cli/create-next-app) will create a folder with your project name and install the required dependencies.
|
||||
|
||||
## Manual installation
|
||||
|
||||
To manually create a new Next.js app, install the required packages:
|
||||
|
||||
```bash package="pnpm"
|
||||
pnpm i next@latest react@latest react-dom@latest
|
||||
```
|
||||
|
||||
```bash package="npm"
|
||||
npm i 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
|
||||
```
|
||||
|
||||
> **Good to know**:
|
||||
>
|
||||
> - The `App Router` uses [React canary releases](https://react.dev/blog/2023/05/03/react-canaries) built-in, which include all the stable React 19 changes, as well as newer features being validated in frameworks, but you should still declare react and react-dom in package.json for tooling and ecosystem compatibility.
|
||||
> - The `Pages Router` uses the React version from your `package.json`.
|
||||
|
||||
Then, add the following scripts to your `package.json` file:
|
||||
|
||||
```json filename="package.json"
|
||||
{
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "eslint",
|
||||
"lint:fix": "eslint --fix"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
These scripts refer to the different stages of developing an application:
|
||||
|
||||
- `next dev`: Starts the development server using Turbopack (default bundler).
|
||||
- `next build`: Builds the application for production.
|
||||
- `next start`: Starts the production server.
|
||||
- `eslint`: Runs ESLint.
|
||||
|
||||
Turbopack is now the default bundler. To use Webpack run `next dev --webpack` or `next build --webpack`. See the [Turbopack docs](/docs/app/api-reference/turbopack) for configuration details.
|
||||
|
||||
<AppOnly>
|
||||
|
||||
### Create the `app` directory
|
||||
|
||||
Next.js uses file-system routing, which means the routes in your application are determined by how you structure your files.
|
||||
|
||||
Create an `app` folder. Then, inside `app`, create a `layout.tsx` file. This file is the [root layout](/docs/app/api-reference/file-conventions/layout#root-layout). It's required and must contain the `<html>` and `<body>` tags.
|
||||
|
||||
```tsx filename="app/layout.tsx" switcher
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/layout.js" switcher
|
||||
export default function RootLayout({ children }) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
Create a home page `app/page.tsx` with some initial content:
|
||||
|
||||
```tsx filename="app/page.tsx" switcher
|
||||
export default function Page() {
|
||||
return <h1>Hello, Next.js!</h1>
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/page.js" switcher
|
||||
export default function Page() {
|
||||
return <h1>Hello, Next.js!</h1>
|
||||
}
|
||||
```
|
||||
|
||||
Both `layout.tsx` and `page.tsx` will be rendered when the user visits the root of your application (`/`).
|
||||
|
||||
<Image
|
||||
alt="App Folder Structure"
|
||||
srcLight="/docs/light/app-getting-started.png"
|
||||
srcDark="/docs/dark/app-getting-started.png"
|
||||
width="1600"
|
||||
height="363"
|
||||
/>
|
||||
|
||||
> **Good to know**:
|
||||
>
|
||||
> - If you forget to create the root layout, Next.js will automatically create this file when running the development server with `next dev`.
|
||||
> - You can optionally use a [`src` folder](/docs/app/api-reference/file-conventions/src-folder) in the root of your project to separate your application's code from configuration files.
|
||||
|
||||
</AppOnly>
|
||||
|
||||
<PagesOnly>
|
||||
|
||||
### Create the `pages` directory
|
||||
|
||||
Next.js uses file-system routing, which means the routes in your application are determined by how you structure your files.
|
||||
|
||||
Create a `pages` directory at the root of your project. Then, add an `index.tsx` file inside your `pages` folder. This will be your home page (`/`):
|
||||
|
||||
```tsx filename="pages/index.tsx" switcher
|
||||
export default function Page() {
|
||||
return <h1>Hello, Next.js!</h1>
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="pages/index.js" switcher
|
||||
export default function Page() {
|
||||
return <h1>Hello, Next.js!</h1>
|
||||
}
|
||||
```
|
||||
|
||||
Next, add an `_app.tsx` file inside `pages/` to define the global layout. Learn more about the [custom App file](/docs/pages/building-your-application/routing/custom-app).
|
||||
|
||||
```tsx filename="pages/_app.tsx" switcher
|
||||
import type { AppProps } from 'next/app'
|
||||
|
||||
export default function App({ Component, pageProps }: AppProps) {
|
||||
return <Component {...pageProps} />
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="pages/_app.js" switcher
|
||||
export default function App({ Component, pageProps }) {
|
||||
return <Component {...pageProps} />
|
||||
}
|
||||
```
|
||||
|
||||
Finally, add a `_document.tsx` file inside `pages/` to control the initial response from the server. Learn more about the [custom Document file](/docs/pages/building-your-application/routing/custom-document).
|
||||
|
||||
```tsx filename="pages/_document.tsx" switcher
|
||||
import { Html, Head, Main, NextScript } from 'next/document'
|
||||
|
||||
export default function Document() {
|
||||
return (
|
||||
<Html>
|
||||
<Head />
|
||||
<body>
|
||||
<Main />
|
||||
<NextScript />
|
||||
</body>
|
||||
</Html>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="pages/_document.js" switcher
|
||||
import { Html, Head, Main, NextScript } from 'next/document'
|
||||
|
||||
export default function Document() {
|
||||
return (
|
||||
<Html>
|
||||
<Head />
|
||||
<body>
|
||||
<Main />
|
||||
<NextScript />
|
||||
</body>
|
||||
</Html>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
</PagesOnly>
|
||||
|
||||
### Create the `public` folder (optional)
|
||||
|
||||
Create a [`public` folder](/docs/app/api-reference/file-conventions/public-folder) at the root of your project to store static assets such as images, fonts, etc. Files inside `public` can then be referenced by your code starting from the base URL (`/`).
|
||||
|
||||
You can then reference these assets using the root path (`/`). For example, `public/profile.png` can be referenced as `/profile.png`:
|
||||
|
||||
```tsx filename="app/page.tsx" highlight={4} switcher
|
||||
import Image from 'next/image'
|
||||
|
||||
export default function Page() {
|
||||
return <Image src="/profile.png" alt="Profile" width={100} height={100} />
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/page.js" highlight={4} switcher
|
||||
import Image from 'next/image'
|
||||
|
||||
export default function Page() {
|
||||
return <Image src="/profile.png" alt="Profile" width={100} height={100} />
|
||||
}
|
||||
```
|
||||
|
||||
## Run the development server
|
||||
|
||||
1. Run `npm run dev` to start the development server.
|
||||
2. Visit `http://localhost:3000` to view your application.
|
||||
3. Edit the <AppOnly>`app/page.tsx`</AppOnly><PagesOnly>`pages/index.tsx`</PagesOnly> file and save it to see the updated result in your browser.
|
||||
|
||||
## Set up TypeScript
|
||||
|
||||
> Minimum TypeScript version: `v5.1.0`
|
||||
|
||||
Next.js comes with built-in TypeScript support. To add TypeScript to your project, rename a file to `.ts` / `.tsx` and run `next dev`. Next.js will automatically install the necessary dependencies and add a `tsconfig.json` file with the recommended config options.
|
||||
|
||||
<AppOnly>
|
||||
|
||||
### IDE Plugin
|
||||
|
||||
Next.js includes a custom TypeScript plugin and type checker, which VSCode and other code editors can use for advanced type-checking and auto-completion.
|
||||
|
||||
You can enable the plugin in VS Code by:
|
||||
|
||||
1. Opening the command palette (`Ctrl/⌘` + `Shift` + `P`)
|
||||
2. Searching for "TypeScript: Select TypeScript Version"
|
||||
3. Selecting "Use Workspace Version"
|
||||
|
||||
<Image
|
||||
alt="TypeScript Command Palette"
|
||||
srcLight="/docs/light/typescript-command-palette.png"
|
||||
srcDark="/docs/dark/typescript-command-palette.png"
|
||||
width="1600"
|
||||
height="637"
|
||||
/>
|
||||
|
||||
</AppOnly>
|
||||
|
||||
See the [TypeScript reference](/docs/app/api-reference/config/typescript) page for more information.
|
||||
|
||||
## Set up linting
|
||||
|
||||
Next.js supports linting with either ESLint or Biome. Choose a linter and run it directly via `package.json` scripts.
|
||||
|
||||
- Use **ESLint** (comprehensive rules):
|
||||
|
||||
```json filename="package.json"
|
||||
{
|
||||
"scripts": {
|
||||
"lint": "eslint",
|
||||
"lint:fix": "eslint --fix"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- Or use **Biome** (fast linter + formatter):
|
||||
|
||||
```json filename="package.json"
|
||||
{
|
||||
"scripts": {
|
||||
"lint": "biome check",
|
||||
"format": "biome format --write"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If your project previously used `next lint`, migrate your scripts to the ESLint CLI with the codemod:
|
||||
|
||||
```bash filename="Terminal"
|
||||
npx @next/codemod@canary next-lint-to-eslint-cli .
|
||||
```
|
||||
|
||||
If you use ESLint, create an explicit config (recommended `eslint.config.mjs`). ESLint supports both [the legacy `.eslintrc.*` and the newer `eslint.config.mjs` formats](https://eslint.org/docs/latest/use/configure/configuration-files#configuring-eslint). See the [ESLint API reference](/docs/app/api-reference/config/eslint#with-core-web-vitals) for a recommended setup.
|
||||
|
||||
> **Good to know**: Starting with Next.js 16, `next build` no longer runs the linter automatically. Instead, you can run your linter through NPM scripts.
|
||||
|
||||
See the [ESLint Plugin](/docs/app/api-reference/config/eslint) page for more information.
|
||||
|
||||
## Set up Absolute Imports and Module Path Aliases
|
||||
|
||||
Next.js has in-built support for the `"paths"` and `"baseUrl"` options of `tsconfig.json` and `jsconfig.json` files.
|
||||
|
||||
These options allow you to alias project directories to absolute paths, making it easier and cleaner to import modules. For example:
|
||||
|
||||
```jsx
|
||||
// Before
|
||||
import { Button } from '../../../components/button'
|
||||
|
||||
// After
|
||||
import { Button } from '@/components/button'
|
||||
```
|
||||
|
||||
To configure absolute imports, add the `baseUrl` configuration option to your `tsconfig.json` or `jsconfig.json` file. For example:
|
||||
|
||||
```json filename="tsconfig.json or jsconfig.json"
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": "src/"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In addition to configuring the `baseUrl` path, you can use the `"paths"` option to `"alias"` module paths.
|
||||
|
||||
For example, the following configuration maps `@/components/*` to `components/*`:
|
||||
|
||||
```json filename="tsconfig.json or jsconfig.json"
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": "src/",
|
||||
"paths": {
|
||||
"@/styles/*": ["styles/*"],
|
||||
"@/components/*": ["components/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Each of the `"paths"` are relative to the `baseUrl` location.
|
||||
421
node_modules/next/dist/docs/01-app/01-getting-started/02-project-structure.md
generated
vendored
Normal file
421
node_modules/next/dist/docs/01-app/01-getting-started/02-project-structure.md
generated
vendored
Normal file
@@ -0,0 +1,421 @@
|
||||
---
|
||||
title: Project structure and organization
|
||||
nav_title: Project Structure
|
||||
description: Learn the folder and file conventions in Next.js, and how to organize your project.
|
||||
---
|
||||
|
||||
This page provides an overview of **all** the folder and file conventions in Next.js, and recommendations for organizing your project.
|
||||
|
||||
## Folder and file conventions
|
||||
|
||||
### Top-level folders
|
||||
|
||||
Top-level folders are used to organize your application's code and static assets.
|
||||
|
||||
<Image
|
||||
alt="Route segments to path segments"
|
||||
srcLight="/docs/light/top-level-folders.png"
|
||||
srcDark="/docs/dark/top-level-folders.png"
|
||||
width="1600"
|
||||
height="525"
|
||||
/>
|
||||
|
||||
| | |
|
||||
| ------------------------------------------------------------------ | ---------------------------------- |
|
||||
| [`app`](/docs/app) | App Router |
|
||||
| [`pages`](/docs/pages/building-your-application/routing) | Pages Router |
|
||||
| [`public`](/docs/app/api-reference/file-conventions/public-folder) | Static assets to be served |
|
||||
| [`src`](/docs/app/api-reference/file-conventions/src-folder) | Optional application source folder |
|
||||
|
||||
### Top-level files
|
||||
|
||||
Top-level files are used to configure your application, manage dependencies, run proxy, integrate monitoring tools, and define environment variables.
|
||||
|
||||
| | |
|
||||
| ---------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
|
||||
| **Next.js** | |
|
||||
| [`next.config.js`](/docs/app/api-reference/config/next-config-js) | Configuration file for Next.js |
|
||||
| [`package.json`](/docs/app/getting-started/installation#manual-installation) | Project dependencies and scripts |
|
||||
| [`instrumentation.ts`](/docs/app/guides/instrumentation) | OpenTelemetry and Instrumentation file |
|
||||
| [`proxy.ts`](/docs/app/api-reference/file-conventions/proxy) | Next.js request proxy |
|
||||
| [`.env`](/docs/app/guides/environment-variables) | Environment variables (should not be tracked by version control) |
|
||||
| [`.env.local`](/docs/app/guides/environment-variables) | Local environment variables (should not be tracked by version control) |
|
||||
| [`.env.production`](/docs/app/guides/environment-variables) | Production environment variables (should not be tracked by version control) |
|
||||
| [`.env.development`](/docs/app/guides/environment-variables) | Development environment variables (should not be tracked by version control) |
|
||||
| [`eslint.config.mjs`](/docs/app/api-reference/config/eslint) | Configuration file for ESLint |
|
||||
| `.gitignore` | Git files and folders to ignore |
|
||||
| [`next-env.d.ts`](/docs/app/api-reference/config/typescript#next-envdts) | TypeScript declaration file for Next.js (should not be tracked by version control) |
|
||||
| `tsconfig.json` | Configuration file for TypeScript |
|
||||
| `jsconfig.json` | Configuration file for JavaScript |
|
||||
|
||||
<AppOnly>
|
||||
|
||||
### Routing Files
|
||||
|
||||
Add `page` to expose a route, `layout` for shared UI such as header, nav, or footer, `loading` for skeletons, `error` for error boundaries, and `route` for APIs.
|
||||
|
||||
| | | |
|
||||
| ----------------------------------------------------------------------------- | ------------------- | ---------------------------- |
|
||||
| [`layout`](/docs/app/api-reference/file-conventions/layout) | `.js` `.jsx` `.tsx` | Layout |
|
||||
| [`page`](/docs/app/api-reference/file-conventions/page) | `.js` `.jsx` `.tsx` | Page |
|
||||
| [`loading`](/docs/app/api-reference/file-conventions/loading) | `.js` `.jsx` `.tsx` | Loading UI |
|
||||
| [`not-found`](/docs/app/api-reference/file-conventions/not-found) | `.js` `.jsx` `.tsx` | Not found UI |
|
||||
| [`error`](/docs/app/api-reference/file-conventions/error) | `.js` `.jsx` `.tsx` | Error UI |
|
||||
| [`global-error`](/docs/app/api-reference/file-conventions/error#global-error) | `.js` `.jsx` `.tsx` | Global error UI |
|
||||
| [`route`](/docs/app/api-reference/file-conventions/route) | `.js` `.ts` | API endpoint |
|
||||
| [`template`](/docs/app/api-reference/file-conventions/template) | `.js` `.jsx` `.tsx` | Re-rendered layout |
|
||||
| [`default`](/docs/app/api-reference/file-conventions/default) | `.js` `.jsx` `.tsx` | Parallel route fallback page |
|
||||
|
||||
### Nested routes
|
||||
|
||||
Folders define URL segments. Nesting folders nests segments. Layouts at any level wrap their child segments. A route becomes public when a `page` or `route` file exists.
|
||||
|
||||
| Path | URL pattern | Notes |
|
||||
| --------------------------- | --------------- | ----------------------------- |
|
||||
| `app/layout.tsx` | — | Root layout wraps all routes |
|
||||
| `app/blog/layout.tsx` | — | Wraps `/blog` and descendants |
|
||||
| `app/page.tsx` | `/` | Public route |
|
||||
| `app/blog/page.tsx` | `/blog` | Public route |
|
||||
| `app/blog/authors/page.tsx` | `/blog/authors` | Public route |
|
||||
|
||||
### Dynamic routes
|
||||
|
||||
Parameterize segments with square brackets. Use `[segment]` for a single param, `[...segment]` for catch‑all, and `[[...segment]]` for optional catch‑all. Access values via the [`params`](/docs/app/api-reference/file-conventions/page#params-optional) prop.
|
||||
|
||||
| Path | URL pattern |
|
||||
| ------------------------------- | -------------------------------------------------------------------- |
|
||||
| `app/blog/[slug]/page.tsx` | `/blog/my-first-post` |
|
||||
| `app/shop/[...slug]/page.tsx` | `/shop/clothing`, `/shop/clothing/shirts` |
|
||||
| `app/docs/[[...slug]]/page.tsx` | `/docs`, `/docs/layouts-and-pages`, `/docs/api-reference/use-router` |
|
||||
|
||||
### Route groups and private folders
|
||||
|
||||
Organize code without changing URLs with route groups [`(group)`](/docs/app/api-reference/file-conventions/route-groups#convention), and colocate non-routable files with private folders [`_folder`](#private-folders).
|
||||
|
||||
| Path | URL pattern | Notes |
|
||||
| ------------------------------- | ----------- | ----------------------------------------- |
|
||||
| `app/(marketing)/page.tsx` | `/` | Group omitted from URL |
|
||||
| `app/(shop)/cart/page.tsx` | `/cart` | Share layouts within `(shop)` |
|
||||
| `app/blog/_components/Post.tsx` | — | Not routable; safe place for UI utilities |
|
||||
| `app/blog/_lib/data.ts` | — | Not routable; safe place for utils |
|
||||
|
||||
### Parallel and Intercepted Routes
|
||||
|
||||
These features fit specific UI patterns, such as slot-based layouts or modal routing.
|
||||
|
||||
Use `@slot` for named slots rendered by a parent layout. Use intercept patterns to render another route inside the current layout without changing the URL, for example, to show a details view as a modal over a list.
|
||||
|
||||
| Pattern (docs) | Meaning | Typical use case |
|
||||
| ------------------------------------------------------------------------------------------- | -------------------- | ---------------------------------------- |
|
||||
| [`@folder`](/docs/app/api-reference/file-conventions/parallel-routes#slots) | Named slot | Sidebar + main content |
|
||||
| [`(.)folder`](/docs/app/api-reference/file-conventions/intercepting-routes#convention) | Intercept same level | Preview sibling route in a modal |
|
||||
| [`(..)folder`](/docs/app/api-reference/file-conventions/intercepting-routes#convention) | Intercept parent | Open a child of the parent as an overlay |
|
||||
| [`(..)(..)folder`](/docs/app/api-reference/file-conventions/intercepting-routes#convention) | Intercept two levels | Deeply nested overlay |
|
||||
| [`(...)folder`](/docs/app/api-reference/file-conventions/intercepting-routes#convention) | Intercept from root | Show arbitrary route in current view |
|
||||
|
||||
### Metadata file conventions
|
||||
|
||||
#### App icons
|
||||
|
||||
| | | |
|
||||
| --------------------------------------------------------------------------------------------------------------- | ----------------------------------- | ------------------------ |
|
||||
| [`favicon`](/docs/app/api-reference/file-conventions/metadata/app-icons#favicon) | `.ico` | Favicon file |
|
||||
| [`icon`](/docs/app/api-reference/file-conventions/metadata/app-icons#icon) | `.ico` `.jpg` `.jpeg` `.png` `.svg` | App Icon file |
|
||||
| [`icon`](/docs/app/api-reference/file-conventions/metadata/app-icons#generate-icons-using-code-js-ts-tsx) | `.js` `.ts` `.tsx` | Generated App Icon |
|
||||
| [`apple-icon`](/docs/app/api-reference/file-conventions/metadata/app-icons#apple-icon) | `.jpg` `.jpeg`, `.png` | Apple App Icon file |
|
||||
| [`apple-icon`](/docs/app/api-reference/file-conventions/metadata/app-icons#generate-icons-using-code-js-ts-tsx) | `.js` `.ts` `.tsx` | Generated Apple App Icon |
|
||||
|
||||
#### Open Graph and Twitter images
|
||||
|
||||
| | | |
|
||||
| --------------------------------------------------------------------------------------------------------------------------- | ---------------------------- | -------------------------- |
|
||||
| [`opengraph-image`](/docs/app/api-reference/file-conventions/metadata/opengraph-image#opengraph-image) | `.jpg` `.jpeg` `.png` `.gif` | Open Graph image file |
|
||||
| [`opengraph-image`](/docs/app/api-reference/file-conventions/metadata/opengraph-image#generate-images-using-code-js-ts-tsx) | `.js` `.ts` `.tsx` | Generated Open Graph image |
|
||||
| [`twitter-image`](/docs/app/api-reference/file-conventions/metadata/opengraph-image#twitter-image) | `.jpg` `.jpeg` `.png` `.gif` | Twitter image file |
|
||||
| [`twitter-image`](/docs/app/api-reference/file-conventions/metadata/opengraph-image#generate-images-using-code-js-ts-tsx) | `.js` `.ts` `.tsx` | Generated Twitter image |
|
||||
|
||||
#### SEO
|
||||
|
||||
| | | |
|
||||
| ------------------------------------------------------------------------------------------------------------ | ----------- | --------------------- |
|
||||
| [`sitemap`](/docs/app/api-reference/file-conventions/metadata/sitemap#sitemap-files-xml) | `.xml` | Sitemap file |
|
||||
| [`sitemap`](/docs/app/api-reference/file-conventions/metadata/sitemap#generating-a-sitemap-using-code-js-ts) | `.js` `.ts` | Generated Sitemap |
|
||||
| [`robots`](/docs/app/api-reference/file-conventions/metadata/robots#static-robotstxt) | `.txt` | Robots file |
|
||||
| [`robots`](/docs/app/api-reference/file-conventions/metadata/robots#generate-a-robots-file) | `.js` `.ts` | Generated Robots file |
|
||||
|
||||
</AppOnly>
|
||||
|
||||
<PagesOnly>
|
||||
|
||||
### File conventions
|
||||
|
||||
| | | |
|
||||
| ----------------------------------------------------------------------------------------------------------- | ------------------- | ----------------- |
|
||||
| [`_app`](/docs/pages/building-your-application/routing/custom-app) | `.js` `.jsx` `.tsx` | Custom App |
|
||||
| [`_document`](/docs/pages/building-your-application/routing/custom-document) | `.js` `.jsx` `.tsx` | Custom Document |
|
||||
| [`_error`](/docs/pages/building-your-application/routing/custom-error#more-advanced-error-page-customizing) | `.js` `.jsx` `.tsx` | Custom Error Page |
|
||||
| [`404`](/docs/pages/building-your-application/routing/custom-error#404-page) | `.js` `.jsx` `.tsx` | 404 Error Page |
|
||||
| [`500`](/docs/pages/building-your-application/routing/custom-error#500-page) | `.js` `.jsx` `.tsx` | 500 Error Page |
|
||||
|
||||
### Routes
|
||||
|
||||
| | | |
|
||||
| ---------------------------------------------------------------------------------------------- | ------------------- | ----------- |
|
||||
| **Folder convention** | | |
|
||||
| [`index`](/docs/pages/building-your-application/routing/pages-and-layouts#index-routes) | `.js` `.jsx` `.tsx` | Home page |
|
||||
| [`folder/index`](/docs/pages/building-your-application/routing/pages-and-layouts#index-routes) | `.js` `.jsx` `.tsx` | Nested page |
|
||||
| **File convention** | | |
|
||||
| [`index`](/docs/pages/building-your-application/routing/pages-and-layouts#index-routes) | `.js` `.jsx` `.tsx` | Home page |
|
||||
| [`file`](/docs/pages/building-your-application/routing/pages-and-layouts) | `.js` `.jsx` `.tsx` | Nested page |
|
||||
|
||||
### Dynamic routes
|
||||
|
||||
| | | |
|
||||
| ----------------------------------------------------------------------------------------------------------------- | ------------------- | -------------------------------- |
|
||||
| **Folder convention** | | |
|
||||
| [`[folder]/index`](/docs/pages/building-your-application/routing/dynamic-routes) | `.js` `.jsx` `.tsx` | Dynamic route segment |
|
||||
| [`[...folder]/index`](/docs/pages/building-your-application/routing/dynamic-routes#catch-all-segments) | `.js` `.jsx` `.tsx` | Catch-all route segment |
|
||||
| [`[[...folder]]/index`](/docs/pages/building-your-application/routing/dynamic-routes#optional-catch-all-segments) | `.js` `.jsx` `.tsx` | Optional catch-all route segment |
|
||||
| **File convention** | | |
|
||||
| [`[file]`](/docs/pages/building-your-application/routing/dynamic-routes) | `.js` `.jsx` `.tsx` | Dynamic route segment |
|
||||
| [`[...file]`](/docs/pages/building-your-application/routing/dynamic-routes#catch-all-segments) | `.js` `.jsx` `.tsx` | Catch-all route segment |
|
||||
| [`[[...file]]`](/docs/pages/building-your-application/routing/dynamic-routes#optional-catch-all-segments) | `.js` `.jsx` `.tsx` | Optional catch-all route segment |
|
||||
|
||||
</PagesOnly>
|
||||
|
||||
<AppOnly>
|
||||
|
||||
## Organizing your project
|
||||
|
||||
Next.js is **unopinionated** about how you organize and colocate your project files. But it does provide several features to help you organize your project.
|
||||
|
||||
### Component hierarchy
|
||||
|
||||
The components defined in special files are rendered in a specific hierarchy:
|
||||
|
||||
- `layout.js`
|
||||
- `template.js`
|
||||
- `error.js` (React error boundary)
|
||||
- `loading.js` (React suspense boundary)
|
||||
- `not-found.js` (React error boundary for "not found" UI)
|
||||
- `page.js` or nested `layout.js`
|
||||
|
||||
<Image
|
||||
alt="Component Hierarchy for File Conventions"
|
||||
srcLight="/docs/light/file-conventions-component-hierarchy.png"
|
||||
srcDark="/docs/dark/file-conventions-component-hierarchy.png"
|
||||
width="1600"
|
||||
height="643"
|
||||
/>
|
||||
|
||||
The components are rendered recursively in nested routes, meaning the components of a route segment will be nested **inside** the components of its parent segment.
|
||||
|
||||
<Image
|
||||
alt="Nested File Conventions Component Hierarchy"
|
||||
srcLight="/docs/light/nested-file-conventions-component-hierarchy.png"
|
||||
srcDark="/docs/dark/nested-file-conventions-component-hierarchy.png"
|
||||
width="1600"
|
||||
height="863"
|
||||
/>
|
||||
|
||||
### Colocation
|
||||
|
||||
In the `app` directory, nested folders define route structure. Each folder represents a route segment that is mapped to a corresponding segment in a URL path.
|
||||
|
||||
However, even though route structure is defined through folders, a route is **not publicly accessible** until a `page.js` or `route.js` file is added to a route segment.
|
||||
|
||||
<Image
|
||||
alt="A diagram showing how a route is not publicly accessible until a page.js or route.js file is added to a route segment."
|
||||
srcLight="/docs/light/project-organization-not-routable.png"
|
||||
srcDark="/docs/dark/project-organization-not-routable.png"
|
||||
width="1600"
|
||||
height="444"
|
||||
/>
|
||||
|
||||
And, even when a route is made publicly accessible, only the **content returned** by `page.js` or `route.js` is sent to the client.
|
||||
|
||||
<Image
|
||||
alt="A diagram showing how page.js and route.js files make routes publicly accessible."
|
||||
srcLight="/docs/light/project-organization-routable.png"
|
||||
srcDark="/docs/dark/project-organization-routable.png"
|
||||
width="1600"
|
||||
height="687"
|
||||
/>
|
||||
|
||||
This means that **project files** can be **safely colocated** inside route segments in the `app` directory without accidentally being routable.
|
||||
|
||||
<Image
|
||||
alt="A diagram showing colocated project files are not routable even when a segment contains a page.js or route.js file."
|
||||
srcLight="/docs/light/project-organization-colocation.png"
|
||||
srcDark="/docs/dark/project-organization-colocation.png"
|
||||
width="1600"
|
||||
height="1011"
|
||||
/>
|
||||
|
||||
> **Good to know**: While you **can** colocate your project files in `app` you don't **have** to. If you prefer, you can [keep them outside the `app` directory](#store-project-files-outside-of-app).
|
||||
|
||||
### Private folders
|
||||
|
||||
Private folders can be created by prefixing a folder with an underscore: `_folderName`
|
||||
|
||||
This indicates the folder is a private implementation detail and should not be considered by the routing system, thereby **opting the folder and all its subfolders** out of routing.
|
||||
|
||||
<Image
|
||||
alt="An example folder structure using private folders"
|
||||
srcLight="/docs/light/project-organization-private-folders.png"
|
||||
srcDark="/docs/dark/project-organization-private-folders.png"
|
||||
width="1600"
|
||||
height="849"
|
||||
/>
|
||||
|
||||
Since files in the `app` directory can be [safely colocated by default](#colocation), private folders are not required for colocation. However, they can be useful for:
|
||||
|
||||
- Separating UI logic from routing logic.
|
||||
- Consistently organizing internal files across a project and the Next.js ecosystem.
|
||||
- Sorting and grouping files in code editors.
|
||||
- Avoiding potential naming conflicts with future Next.js file conventions.
|
||||
|
||||
> **Good to know**:
|
||||
>
|
||||
> - While not a framework convention, you might also consider marking files outside private folders as "private" using the same underscore pattern.
|
||||
> - You can create URL segments that start with an underscore by prefixing the folder name with `%5F` (the URL-encoded form of an underscore): `%5FfolderName`.
|
||||
> - If you don't use private folders, it would be helpful to know Next.js [special file conventions](/docs/app/getting-started/project-structure#routing-files) to prevent unexpected naming conflicts.
|
||||
|
||||
### Route groups
|
||||
|
||||
Route groups can be created by wrapping a folder in parenthesis: `(folderName)`
|
||||
|
||||
This indicates the folder is for organizational purposes and should **not be included** in the route's URL path.
|
||||
|
||||
<Image
|
||||
alt="An example folder structure using route groups"
|
||||
srcLight="/docs/light/project-organization-route-groups.png"
|
||||
srcDark="/docs/dark/project-organization-route-groups.png"
|
||||
width="1600"
|
||||
height="849"
|
||||
/>
|
||||
|
||||
Route groups are useful for:
|
||||
|
||||
- Organizing routes by site section, intent, or team. e.g. marketing pages, admin pages, etc.
|
||||
- Enabling nested layouts in the same route segment level:
|
||||
- [Creating multiple nested layouts in the same segment, including multiple root layouts](#creating-multiple-root-layouts)
|
||||
- [Adding a layout to a subset of routes in a common segment](#opting-specific-segments-into-a-layout)
|
||||
|
||||
### `src` folder
|
||||
|
||||
Next.js supports storing application code (including `app`) inside an optional [`src` folder](/docs/app/api-reference/file-conventions/src-folder). This separates application code from project configuration files which mostly live in the root of a project.
|
||||
|
||||
<Image
|
||||
alt="An example folder structure with the `src` folder"
|
||||
srcLight="/docs/light/project-organization-src-directory.png"
|
||||
srcDark="/docs/dark/project-organization-src-directory.png"
|
||||
width="1600"
|
||||
height="687"
|
||||
/>
|
||||
|
||||
## Examples
|
||||
|
||||
The following section lists a very high-level overview of common strategies. The simplest takeaway is to choose a strategy that works for you and your team and be consistent across the project.
|
||||
|
||||
> **Good to know**: In our examples below, we're using `components` and `lib` folders as generalized placeholders, their naming has no special framework significance and your projects might use other folders like `ui`, `utils`, `hooks`, `styles`, etc.
|
||||
|
||||
### Store project files outside of `app`
|
||||
|
||||
This strategy stores all application code in shared folders in the **root of your project** and keeps the `app` directory purely for routing purposes.
|
||||
|
||||
<Image
|
||||
alt="An example folder structure with project files outside of app"
|
||||
srcLight="/docs/light/project-organization-project-root.png"
|
||||
srcDark="/docs/dark/project-organization-project-root.png"
|
||||
width="1600"
|
||||
height="849"
|
||||
/>
|
||||
|
||||
### Store project files in top-level folders inside of `app`
|
||||
|
||||
This strategy stores all application code in shared folders in the **root of the `app` directory**.
|
||||
|
||||
<Image
|
||||
alt="An example folder structure with project files inside app"
|
||||
srcLight="/docs/light/project-organization-app-root.png"
|
||||
srcDark="/docs/dark/project-organization-app-root.png"
|
||||
width="1600"
|
||||
height="849"
|
||||
/>
|
||||
|
||||
### Split project files by feature or route
|
||||
|
||||
This strategy stores globally shared application code in the root `app` directory and **splits** more specific application code into the route segments that use them.
|
||||
|
||||
<Image
|
||||
alt="An example folder structure with project files split by feature or route"
|
||||
srcLight="/docs/light/project-organization-app-root-split.png"
|
||||
srcDark="/docs/dark/project-organization-app-root-split.png"
|
||||
width="1600"
|
||||
height="1011"
|
||||
/>
|
||||
|
||||
### Organize routes without affecting the URL path
|
||||
|
||||
To organize routes without affecting the URL, create a group to keep related routes together. The folders in parenthesis will be omitted from the URL (e.g. `(marketing)` or `(shop)`).
|
||||
|
||||
<Image
|
||||
alt="Organizing Routes with Route Groups"
|
||||
srcLight="/docs/light/route-group-organisation.png"
|
||||
srcDark="/docs/dark/route-group-organisation.png"
|
||||
width="1600"
|
||||
height="930"
|
||||
/>
|
||||
|
||||
Even though routes inside `(marketing)` and `(shop)` share the same URL hierarchy, you can create a different layout for each group by adding a `layout.js` file inside their folders.
|
||||
|
||||
<Image
|
||||
alt="Route Groups with Multiple Layouts"
|
||||
srcLight="/docs/light/route-group-multiple-layouts.png"
|
||||
srcDark="/docs/dark/route-group-multiple-layouts.png"
|
||||
width="1600"
|
||||
height="768"
|
||||
/>
|
||||
|
||||
### Opting specific segments into a layout
|
||||
|
||||
To opt specific routes into a layout, create a new route group (e.g. `(shop)`) and move the routes that share the same layout into the group (e.g. `account` and `cart`). The routes outside of the group will not share the layout (e.g. `checkout`).
|
||||
|
||||
<Image
|
||||
alt="Route Groups with Opt-in Layouts"
|
||||
srcLight="/docs/light/route-group-opt-in-layouts.png"
|
||||
srcDark="/docs/dark/route-group-opt-in-layouts.png"
|
||||
width="1600"
|
||||
height="930"
|
||||
/>
|
||||
|
||||
### Opting for loading skeletons on a specific route
|
||||
|
||||
To apply a [loading skeleton](/docs/app/api-reference/file-conventions/loading) via a `loading.js` file to a specific route, create a new route group (e.g., `/(overview)`) and then move your `loading.tsx` inside that route group.
|
||||
|
||||
<Image
|
||||
alt="Folder structure showing a loading.tsx and a page.tsx inside the route group"
|
||||
srcLight="/docs/light/route-group-loading.png"
|
||||
srcDark="/docs/dark/route-group-loading.png"
|
||||
width="1600"
|
||||
height="444"
|
||||
/>
|
||||
|
||||
Now, the `loading.tsx` file will only apply to your dashboard → overview page instead of all your dashboard pages without affecting the URL path structure.
|
||||
|
||||
### Creating multiple root layouts
|
||||
|
||||
To create multiple [root layouts](/docs/app/api-reference/file-conventions/layout#root-layout), remove the top-level `layout.js` file, and add a `layout.js` file inside each route group. This is useful for partitioning an application into sections that have a completely different UI or experience. The `<html>` and `<body>` tags need to be added to each root layout.
|
||||
|
||||
<Image
|
||||
alt="Route Groups with Multiple Root Layouts"
|
||||
srcLight="/docs/light/route-group-multiple-root-layouts.png"
|
||||
srcDark="/docs/dark/route-group-multiple-root-layouts.png"
|
||||
width="1600"
|
||||
height="687"
|
||||
/>
|
||||
|
||||
In the example above, both `(marketing)` and `(shop)` have their own root layout.
|
||||
|
||||
</AppOnly>
|
||||
360
node_modules/next/dist/docs/01-app/01-getting-started/03-layouts-and-pages.md
generated
vendored
Normal file
360
node_modules/next/dist/docs/01-app/01-getting-started/03-layouts-and-pages.md
generated
vendored
Normal file
@@ -0,0 +1,360 @@
|
||||
---
|
||||
title: Layouts and Pages
|
||||
description: Learn how to create your first pages and layouts, and link between them with the Link component.
|
||||
related:
|
||||
title: API Reference
|
||||
description: Learn more about the features mentioned in this page by reading the API Reference.
|
||||
links:
|
||||
- app/getting-started/linking-and-navigating
|
||||
- app/api-reference/file-conventions/layout
|
||||
- app/api-reference/file-conventions/page
|
||||
- app/api-reference/components/link
|
||||
- app/api-reference/file-conventions/dynamic-routes
|
||||
---
|
||||
|
||||
Next.js uses **file-system based routing**, meaning you can use folders and files to define routes. This page will guide you through how to create layouts and pages, and link between them.
|
||||
|
||||
## Creating a page
|
||||
|
||||
A **page** is UI that is rendered on a specific route. To create a page, add a [`page` file](/docs/app/api-reference/file-conventions/page) inside the `app` directory and default export a React component. For example, to create an index page (`/`):
|
||||
|
||||
<Image
|
||||
alt="page.js special file"
|
||||
srcLight="/docs/light/page-special-file.png"
|
||||
srcDark="/docs/dark/page-special-file.png"
|
||||
width="1600"
|
||||
height="282"
|
||||
/>
|
||||
|
||||
```tsx filename="app/page.tsx" switcher
|
||||
export default function Page() {
|
||||
return <h1>Hello Next.js!</h1>
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/page.js" switcher
|
||||
export default function Page() {
|
||||
return <h1>Hello Next.js!</h1>
|
||||
}
|
||||
```
|
||||
|
||||
## Creating a layout
|
||||
|
||||
A layout is UI that is **shared** between multiple pages. On navigation, layouts preserve state, remain interactive, and do not rerender.
|
||||
|
||||
You can define a layout by default exporting a React component from a [`layout` file](/docs/app/api-reference/file-conventions/layout). The component should accept a `children` prop which can be a page or another [layout](#nesting-layouts).
|
||||
|
||||
For example, to create a layout that accepts your index page as child, add a `layout` file inside the `app` directory:
|
||||
|
||||
<Image
|
||||
alt="layout.js special file"
|
||||
srcLight="/docs/light/layout-special-file.png"
|
||||
srcDark="/docs/dark/layout-special-file.png"
|
||||
width="1600"
|
||||
height="363"
|
||||
/>
|
||||
|
||||
```tsx filename="app/layout.tsx" switcher
|
||||
export default function DashboardLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>
|
||||
{/* Layout UI */}
|
||||
{/* Place children where you want to render a page or nested layout */}
|
||||
<main>{children}</main>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/layout.js" switcher
|
||||
export default function DashboardLayout({ children }) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>
|
||||
{/* Layout UI */}
|
||||
{/* Place children where you want to render a page or nested layout */}
|
||||
<main>{children}</main>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
The layout above is called a [root layout](/docs/app/api-reference/file-conventions/layout#root-layout) because it's defined at the root of the `app` directory. The root layout is **required** and must contain `html` and `body` tags.
|
||||
|
||||
## Creating a nested route
|
||||
|
||||
A nested route is a route composed of multiple URL segments. For example, the `/blog/[slug]` route is composed of three segments:
|
||||
|
||||
- `/` (Root Segment)
|
||||
- `blog` (Segment)
|
||||
- `[slug]` (Leaf Segment)
|
||||
|
||||
In Next.js:
|
||||
|
||||
- **Folders** are used to define the route segments that map to URL segments.
|
||||
- **Files** (like `page` and `layout`) are used to create UI that is shown for a segment.
|
||||
|
||||
To create nested routes, you can nest folders inside each other. For example, to add a route for `/blog`, create a folder called `blog` in the `app` directory. Then, to make `/blog` publicly accessible, add a `page.tsx` file:
|
||||
|
||||
<Image
|
||||
alt="File hierarchy showing blog folder and a page.js file"
|
||||
srcLight="/docs/light/blog-nested-route.png"
|
||||
srcDark="/docs/dark/blog-nested-route.png"
|
||||
width="1600"
|
||||
height="525"
|
||||
/>
|
||||
|
||||
```tsx filename="app/blog/page.tsx" switcher
|
||||
// Dummy imports
|
||||
import { getPosts } from '@/lib/posts'
|
||||
import { Post } from '@/ui/post'
|
||||
|
||||
export default async function Page() {
|
||||
const posts = await getPosts()
|
||||
|
||||
return (
|
||||
<ul>
|
||||
{posts.map((post) => (
|
||||
<Post key={post.id} post={post} />
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/blog/page.js" switcher
|
||||
// Dummy imports
|
||||
import { getPosts } from '@/lib/posts'
|
||||
import { Post } from '@/ui/post'
|
||||
|
||||
export default async function Page() {
|
||||
const posts = await getPosts()
|
||||
|
||||
return (
|
||||
<ul>
|
||||
{posts.map((post) => (
|
||||
<Post key={post.id} post={post} />
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
You can continue nesting folders to create nested routes. For example, to create a route for a specific blog post, create a new `[slug]` folder inside `blog` and add a `page` file:
|
||||
|
||||
<Image
|
||||
alt="File hierarchy showing blog folder with a nested slug folder and a page.js file"
|
||||
srcLight="/docs/light/blog-post-nested-route.png"
|
||||
srcDark="/docs/dark/blog-post-nested-route.png"
|
||||
width="1600"
|
||||
height="687"
|
||||
/>
|
||||
|
||||
```tsx filename="app/blog/[slug]/page.tsx" switcher
|
||||
function generateStaticParams() {}
|
||||
|
||||
export default function Page() {
|
||||
return <h1>Hello, Blog Post Page!</h1>
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/blog/[slug]/page.js" switcher
|
||||
function generateStaticParams() {}
|
||||
|
||||
export default function Page() {
|
||||
return <h1>Hello, Blog Post Page!</h1>
|
||||
}
|
||||
```
|
||||
|
||||
Wrapping a folder name in square brackets (e.g. `[slug]`) creates a [dynamic route segment](/docs/app/api-reference/file-conventions/dynamic-routes) which is used to generate multiple pages from data. e.g. blog posts, product pages, etc.
|
||||
|
||||
## Nesting layouts
|
||||
|
||||
By default, layouts in the folder hierarchy are also nested, which means they wrap child layouts via their `children` prop. You can nest layouts by adding `layout` inside specific route segments (folders).
|
||||
|
||||
For example, to create a layout for the `/blog` route, add a new `layout` file inside the `blog` folder.
|
||||
|
||||
<Image
|
||||
alt="File hierarchy showing root layout wrapping the blog layout"
|
||||
srcLight="/docs/light/nested-layouts.png"
|
||||
srcDark="/docs/dark/nested-layouts.png"
|
||||
width="1600"
|
||||
height="768"
|
||||
/>
|
||||
|
||||
```tsx filename="app/blog/layout.tsx" switcher
|
||||
export default function BlogLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return <section>{children}</section>
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/blog/layout.js" switcher
|
||||
export default function BlogLayout({ children }) {
|
||||
return <section>{children}</section>
|
||||
}
|
||||
```
|
||||
|
||||
If you were to combine the two layouts above, the root layout (`app/layout.js`) would wrap the blog layout (`app/blog/layout.js`), which would wrap the blog (`app/blog/page.js`) and blog post page (`app/blog/[slug]/page.js`).
|
||||
|
||||
## Creating a dynamic segment
|
||||
|
||||
[Dynamic segments](/docs/app/api-reference/file-conventions/dynamic-routes) allow you to create routes that are generated from data. For example, instead of manually creating a route for each individual blog post, you can create a dynamic segment to generate the routes based on blog post data.
|
||||
|
||||
To create a dynamic segment, wrap the segment (folder) name in square brackets: `[segmentName]`. For example, in the `app/blog/[slug]/page.tsx` route, the `[slug]` is the dynamic segment.
|
||||
|
||||
```tsx filename="app/blog/[slug]/page.tsx" switcher
|
||||
export default async function BlogPostPage({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ slug: string }>
|
||||
}) {
|
||||
const { slug } = await params
|
||||
const post = await getPost(slug)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>{post.title}</h1>
|
||||
<p>{post.content}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/blog/[slug]/page.js" switcher
|
||||
export default async function BlogPostPage({ params }) {
|
||||
const { slug } = await params
|
||||
const post = await getPost(slug)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>{post.title}</h1>
|
||||
<p>{post.content}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
Learn more about [Dynamic Segments](/docs/app/api-reference/file-conventions/dynamic-routes) and the [`params`](/docs/app/api-reference/file-conventions/page#params-optional) props.
|
||||
|
||||
Nested [layouts within Dynamic Segments](/docs/app/api-reference/file-conventions/layout#params-optional), can also access the `params` props.
|
||||
|
||||
## Rendering with search params
|
||||
|
||||
In a Server Component **page**, you can access search parameters using the [`searchParams`](/docs/app/api-reference/file-conventions/page#searchparams-optional) prop:
|
||||
|
||||
```tsx filename="app/page.tsx" switcher
|
||||
export default async function Page({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: Promise<{ [key: string]: string | string[] | undefined }>
|
||||
}) {
|
||||
const filters = (await searchParams).filters
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/page.jsx" switcher
|
||||
export default async function Page({ searchParams }) {
|
||||
const filters = (await searchParams).filters
|
||||
}
|
||||
```
|
||||
|
||||
Using `searchParams` opts your page into [**dynamic rendering**](/docs/app/glossary#dynamic-rendering) because it requires an incoming request to read the search parameters from.
|
||||
|
||||
Client Components can read search params using the [`useSearchParams`](/docs/app/api-reference/functions/use-search-params) hook.
|
||||
|
||||
Learn more about `useSearchParams` in [prerendered](/docs/app/api-reference/functions/use-search-params#prerendering) and [dynamically rendered](/docs/app/api-reference/functions/use-search-params#dynamic-rendering) routes.
|
||||
|
||||
### What to use and when
|
||||
|
||||
- Use the `searchParams` prop when you need search parameters to **load data for the page** (e.g. pagination, filtering from a database).
|
||||
- Use `useSearchParams` when search parameters are used **only on the client** (e.g. filtering a list already loaded via props).
|
||||
- As a small optimization, you can use `new URLSearchParams(window.location.search)` in **callbacks or event handlers** to read search params without triggering re-renders.
|
||||
|
||||
## Linking between pages
|
||||
|
||||
You can use the [`<Link>` component](/docs/app/api-reference/components/link) to navigate between routes. `<Link>` is a built-in Next.js component that extends the HTML `<a>` tag to provide [prefetching](/docs/app/getting-started/linking-and-navigating#prefetching) and [client-side navigation](/docs/app/getting-started/linking-and-navigating#client-side-transitions).
|
||||
|
||||
For example, to generate a list of blog posts, import `<Link>` from `next/link` and pass a `href` prop to the component:
|
||||
|
||||
```tsx filename="app/ui/post.tsx" highlight={1,10} switcher
|
||||
import Link from 'next/link'
|
||||
|
||||
export default async function Post({ post }) {
|
||||
const posts = await getPosts()
|
||||
|
||||
return (
|
||||
<ul>
|
||||
{posts.map((post) => (
|
||||
<li key={post.slug}>
|
||||
<Link href={`/blog/${post.slug}`}>{post.title}</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/ui/post.js" highlight={1,10} switcher
|
||||
import Link from 'next/link'
|
||||
|
||||
export default async function Post({ post }) {
|
||||
const posts = await getPosts()
|
||||
|
||||
return (
|
||||
<ul>
|
||||
{posts.map((post) => (
|
||||
<li key={post.slug}>
|
||||
<Link href={`/blog/${post.slug}`}>{post.title}</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
> **Good to know**: `<Link>` is the primary way to navigate between routes in Next.js. You can also use the [`useRouter` hook](/docs/app/api-reference/functions/use-router) for more advanced navigation.
|
||||
|
||||
## Route Props Helpers
|
||||
|
||||
Next.js exposes utility types that infer `params` and named slots from your route structure:
|
||||
|
||||
- [**PageProps**](/docs/app/api-reference/file-conventions/page#page-props-helper): Props for `page` components, including `params` and `searchParams`.
|
||||
- [**LayoutProps**](/docs/app/api-reference/file-conventions/layout#layout-props-helper): Props for `layout` components, including `children` and any named slots (e.g. folders like `@analytics`).
|
||||
|
||||
These are globally available helpers, generated when running either `next dev`, `next build` or [`next typegen`](/docs/app/api-reference/cli/next#next-typegen-options).
|
||||
|
||||
```tsx filename="app/blog/[slug]/page.tsx"
|
||||
export default async function Page(props: PageProps<'/blog/[slug]'>) {
|
||||
const { slug } = await props.params
|
||||
return <h1>Blog post: {slug}</h1>
|
||||
}
|
||||
```
|
||||
|
||||
```tsx filename="app/dashboard/layout.tsx"
|
||||
export default function Layout(props: LayoutProps<'/dashboard'>) {
|
||||
return (
|
||||
<section>
|
||||
{props.children}
|
||||
{/* If you have app/dashboard/@analytics, it appears as a typed slot: */}
|
||||
{/* {props.analytics} */}
|
||||
</section>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
> **Good to know**
|
||||
>
|
||||
> - Static routes resolve `params` to `{}`.
|
||||
> - `PageProps`, `LayoutProps` are global helpers — no imports required.
|
||||
> - Types are generated during `next dev`, `next build` or `next typegen`.
|
||||
445
node_modules/next/dist/docs/01-app/01-getting-started/04-linking-and-navigating.md
generated
vendored
Normal file
445
node_modules/next/dist/docs/01-app/01-getting-started/04-linking-and-navigating.md
generated
vendored
Normal file
@@ -0,0 +1,445 @@
|
||||
---
|
||||
title: Linking and Navigating
|
||||
description: Learn how the built-in navigation optimizations work, including prefetching, prerendering, and client-side navigation, and how to optimize navigation for dynamic routes and slow networks.
|
||||
related:
|
||||
links:
|
||||
- app/api-reference/components/link
|
||||
- app/api-reference/file-conventions/loading
|
||||
- app/guides/prefetching
|
||||
---
|
||||
|
||||
{/* AI agent hint: To ensure client-side navigations are instant, export `unstable_instant` from the route in addition to using Suspense. See docs/01-app/02-guides/instant-navigation.mdx */}
|
||||
|
||||
In Next.js, routes are rendered on the server by default. This often means the client has to wait for a server response before a new route can be shown. Next.js comes with built-in [prefetching](#prefetching), [streaming](#streaming), and [client-side transitions](#client-side-transitions) ensuring navigation stays fast and responsive.
|
||||
|
||||
This guide explains how navigation works in Next.js and how you can optimize it for [dynamic routes](#dynamic-routes-without-loadingtsx) and [slow networks](#slow-networks).
|
||||
|
||||
## How navigation works
|
||||
|
||||
To understand how navigation works in Next.js, it helps to be familiar with the following concepts:
|
||||
|
||||
- [Server Rendering](#server-rendering)
|
||||
- [Prefetching](#prefetching)
|
||||
- [Streaming](#streaming)
|
||||
- [Client-side transitions](#client-side-transitions)
|
||||
|
||||
### Server Rendering
|
||||
|
||||
In Next.js, [Layouts and Pages](/docs/app/getting-started/layouts-and-pages) are [React Server Components](https://react.dev/reference/rsc/server-components) by default. On initial and subsequent navigations, the [Server Component Payload](/docs/app/getting-started/server-and-client-components#how-do-server-and-client-components-work-in-nextjs) is generated on the server before being sent to the client.
|
||||
|
||||
There are two types of server rendering, based on _when_ it happens:
|
||||
|
||||
- **Prerendering** happens at build time or during [revalidation](/docs/app/getting-started/revalidating) and the result is cached.
|
||||
- **Dynamic Rendering** happens at request time in response to a client request.
|
||||
|
||||
The trade-off of server rendering is that the client must wait for the server to respond before the new route can be shown. Next.js addresses this delay by [prefetching](#prefetching) routes the user is likely to visit and performing [client-side transitions](#client-side-transitions).
|
||||
|
||||
> **Good to know**: HTML is also generated for the initial visit.
|
||||
|
||||
### Prefetching
|
||||
|
||||
Prefetching is the process of loading a route in the background before the user navigates to it. This makes navigation between routes in your application feel instant, because by the time a user clicks on a link, the data to render the next route is already available client side.
|
||||
|
||||
Next.js automatically prefetches routes linked with the [`<Link>` component](/docs/app/api-reference/components/link) when they enter the user's viewport.
|
||||
|
||||
```tsx filename="app/layout.tsx" switcher
|
||||
import Link from 'next/link'
|
||||
|
||||
export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<html>
|
||||
<body>
|
||||
<nav>
|
||||
{/* Prefetched when the link is hovered or enters the viewport */}
|
||||
<Link href="/blog">Blog</Link>
|
||||
{/* No prefetching */}
|
||||
<a href="/contact">Contact</a>
|
||||
</nav>
|
||||
{children}
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/layout.js" switcher
|
||||
import Link from 'next/link'
|
||||
|
||||
export default function Layout() {
|
||||
return (
|
||||
<html>
|
||||
<body>
|
||||
<nav>
|
||||
{/* Prefetched when the link is hovered or enters the viewport */}
|
||||
<Link href="/blog">Blog</Link>
|
||||
{/* No prefetching */}
|
||||
<a href="/contact">Contact</a>
|
||||
</nav>
|
||||
{children}
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
How much of the route is prefetched depends on whether it's static or dynamic:
|
||||
|
||||
- **Static Route**: the full route is prefetched.
|
||||
- **Dynamic Route**: prefetching is skipped, or the route is partially prefetched if [`loading.tsx`](/docs/app/api-reference/file-conventions/loading) is present.
|
||||
|
||||
By skipping or partially prefetching dynamic routes, Next.js avoids unnecessary work on the server for routes the users may never visit. However, waiting for a server response before navigation can give the users the impression that the app is not responding.
|
||||
|
||||
<Image
|
||||
alt="Server Rendering without Streaming"
|
||||
srcLight="/docs/light/server-rendering-without-streaming.png"
|
||||
srcDark="/docs/dark/server-rendering-without-streaming.png"
|
||||
width="1600"
|
||||
height="748"
|
||||
/>
|
||||
|
||||
To improve the navigation experience to dynamic routes, you can use [streaming](#streaming).
|
||||
|
||||
### Streaming
|
||||
|
||||
Streaming allows the server to send parts of a dynamic route to the client as soon as they're ready, rather than waiting for the entire route to be rendered. This means users see something sooner, even if parts of the page are still loading. See the [Streaming guide](/docs/app/guides/streaming) for a deep dive into how streaming works in Next.js.
|
||||
|
||||
For dynamic routes, it means they can be **partially prefetched**. That is, shared layouts and loading skeletons can be requested ahead of time.
|
||||
|
||||
<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"
|
||||
/>
|
||||
|
||||
To use streaming, create a `loading.tsx` in your route folder:
|
||||
|
||||
<Image
|
||||
alt="loading.js special file"
|
||||
srcLight="/docs/light/loading-special-file.png"
|
||||
srcDark="/docs/dark/loading-special-file.png"
|
||||
width="1600"
|
||||
height="606"
|
||||
/>
|
||||
|
||||
```tsx filename="app/dashboard/loading.tsx" switcher
|
||||
export default function Loading() {
|
||||
// Add fallback UI that will be shown while the route is loading.
|
||||
return <LoadingSkeleton />
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/dashboard/loading.js" switcher
|
||||
export default function Loading() {
|
||||
// Add fallback UI that will be shown while the route is loading.
|
||||
return <LoadingSkeleton />
|
||||
}
|
||||
```
|
||||
|
||||
Behind the scenes, Next.js will automatically wrap the `page.tsx` contents in a `<Suspense>` boundary. The prefetched fallback UI will be shown while the route is loading, and swapped for the actual content once ready.
|
||||
|
||||
> **Good to know**: You can also use [`<Suspense>`](https://react.dev/reference/react/Suspense) to create loading UI for nested components.
|
||||
|
||||
Benefits of `loading.tsx`:
|
||||
|
||||
- Immediate navigation and visual feedback for the user.
|
||||
- Shared layouts remain interactive and navigation is interruptible.
|
||||
- Improved Core Web Vitals: [TTFB](https://web.dev/articles/ttfb), [FCP](https://web.dev/articles/fcp), and [TTI](https://web.dev/articles/tti).
|
||||
|
||||
To further improve the navigation experience, Next.js performs a [client-side transition](#client-side-transitions) with the `<Link>` component.
|
||||
|
||||
### Client-side transitions
|
||||
|
||||
Traditionally, navigation to a server-rendered page triggers a full page load. This clears state, resets scroll position, and blocks interactivity.
|
||||
|
||||
Next.js avoids this with client-side transitions using the `<Link>` component. Instead of reloading the page, it updates the content dynamically by:
|
||||
|
||||
- Keeping any shared layouts and UI.
|
||||
- Replacing the current page with the prefetched loading state or a new page if available.
|
||||
|
||||
Client-side transitions are what makes a server-rendered apps _feel_ like client-rendered apps. And when paired with [prefetching](#prefetching) and [streaming](#streaming), it enables fast transitions, even for dynamic routes.
|
||||
|
||||
Next.js also handles [scrolling to the top of the page](/docs/app/api-reference/components/link#scroll) during client-side transitions. If content scrolls behind a sticky or fixed header after navigation, you can fix this with CSS [`scroll-padding-top`](/docs/app/api-reference/components/link#scroll-offset-with-sticky-headers).
|
||||
|
||||
## What can make transitions slow?
|
||||
|
||||
These Next.js optimizations make navigation fast and responsive. However, under certain conditions, transitions can still _feel_ slow. Here are some common causes and how to improve the user experience:
|
||||
|
||||
### Dynamic routes without `loading.tsx`
|
||||
|
||||
When navigating to a dynamic route, the client must wait for the server response before showing the result. This can give the users the impression that the app is not responding.
|
||||
|
||||
We recommend adding `loading.tsx` to dynamic routes to enable partial prefetching, trigger immediate navigation, and display a loading UI while the route renders.
|
||||
|
||||
```tsx filename="app/blog/[slug]/loading.tsx" switcher
|
||||
export default function Loading() {
|
||||
return <LoadingSkeleton />
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/blog/[slug]/loading.js" switcher
|
||||
export default function Loading() {
|
||||
return <LoadingSkeleton />
|
||||
}
|
||||
```
|
||||
|
||||
> **Good to know**: In development mode, you can use the Next.js Devtools to identify if the route is static or dynamic. See [`devIndicators`](/docs/app/api-reference/config/next-config-js/devIndicators) for more information.
|
||||
|
||||
### Dynamic segments without `generateStaticParams`
|
||||
|
||||
If a [dynamic segment](/docs/app/api-reference/file-conventions/dynamic-routes) could be prerendered but isn't because it's missing [`generateStaticParams`](/docs/app/api-reference/functions/generate-static-params), the route will fallback to dynamic rendering at request time.
|
||||
|
||||
Ensure the route is statically generated at build time by adding `generateStaticParams`:
|
||||
|
||||
```tsx filename="app/blog/[slug]/page.tsx" switcher
|
||||
export async function generateStaticParams() {
|
||||
const posts = await fetch('https://.../posts').then((res) => res.json())
|
||||
|
||||
return posts.map((post) => ({
|
||||
slug: post.slug,
|
||||
}))
|
||||
}
|
||||
|
||||
export default async function Page({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ slug: string }>
|
||||
}) {
|
||||
const { slug } = await params
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/blog/[slug]/page.js" switcher
|
||||
export async function generateStaticParams() {
|
||||
const posts = await fetch('https://.../posts').then((res) => res.json())
|
||||
|
||||
return posts.map((post) => ({
|
||||
slug: post.slug,
|
||||
}))
|
||||
}
|
||||
|
||||
export default async function Page({ params }) {
|
||||
const { slug } = await params
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### Slow networks
|
||||
|
||||
On slow or unstable networks, prefetching may not finish before the user clicks a link. This can affect both static and dynamic routes. In these cases, the `loading.js` fallback may not appear immediately because it hasn't been prefetched yet.
|
||||
|
||||
To improve perceived performance, you can use the [`useLinkStatus` hook](/docs/app/api-reference/functions/use-link-status) to show immediate feedback while the transition is in progress.
|
||||
|
||||
```tsx filename="app/ui/loading-indicator.tsx" switcher
|
||||
'use client'
|
||||
|
||||
import { useLinkStatus } from 'next/link'
|
||||
|
||||
export default function LoadingIndicator() {
|
||||
const { pending } = useLinkStatus()
|
||||
return (
|
||||
<span aria-hidden className={`link-hint ${pending ? 'is-pending' : ''}`} />
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/ui/loading-indicator.js" switcher
|
||||
'use client'
|
||||
|
||||
import { useLinkStatus } from 'next/link'
|
||||
|
||||
export default function LoadingIndicator() {
|
||||
const { pending } = useLinkStatus()
|
||||
return (
|
||||
<span aria-hidden className={`link-hint ${pending ? 'is-pending' : ''}`} />
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
You can "debounce" the hint by adding an initial animation delay (e.g. 100ms) and starting as invisible (e.g. `opacity: 0`). This means the loading indicator will only be shown if the navigation takes longer than the specified delay. See the [`useLinkStatus` reference](/docs/app/api-reference/functions/use-link-status#gracefully-handling-fast-navigation) for a CSS example.
|
||||
|
||||
> **Good to know**: You can use other visual feedback patterns like a progress bar. View an example [here](https://github.com/vercel/react-transition-progress).
|
||||
|
||||
### Disabling prefetching
|
||||
|
||||
You can opt out of prefetching by setting the `prefetch` prop to `false` on the `<Link>` component. This is useful to avoid unnecessary usage of resources when rendering large lists of links (e.g. an infinite scroll table).
|
||||
|
||||
```tsx
|
||||
<Link prefetch={false} href="/blog">
|
||||
Blog
|
||||
</Link>
|
||||
```
|
||||
|
||||
However, disabling prefetching comes with trade-offs:
|
||||
|
||||
- **Static routes** will only be fetched when the user clicks the link.
|
||||
- **Dynamic routes** will need to be rendered on the server first before the client can navigate to it.
|
||||
|
||||
To reduce resource usage without fully disabling prefetch, you can prefetch only on hover. This limits prefetching to routes the user is more _likely_ to visit, rather than all links in the viewport.
|
||||
|
||||
```tsx filename="app/ui/hover-prefetch-link.tsx" switcher
|
||||
'use client'
|
||||
|
||||
import Link from 'next/link'
|
||||
import { useState } from 'react'
|
||||
|
||||
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'
|
||||
|
||||
function HoverPrefetchLink({ href, children }) {
|
||||
const [active, setActive] = useState(false)
|
||||
|
||||
return (
|
||||
<Link
|
||||
href={href}
|
||||
prefetch={active ? null : false}
|
||||
onMouseEnter={() => setActive(true)}
|
||||
>
|
||||
{children}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Hydration not completed
|
||||
|
||||
`<Link>` is a Client Component and must be hydrated before it can prefetch routes. On the initial visit, large JavaScript bundles can delay hydration, preventing prefetching from starting right away.
|
||||
|
||||
React mitigates this with Selective Hydration and you can further improve this by:
|
||||
|
||||
- Using the [`@next/bundle-analyzer`](/docs/app/guides/package-bundling#nextbundle-analyzer-for-webpack) plugin to identify and reduce bundle size by removing large dependencies.
|
||||
- Moving logic from the client to the server where possible. See the [Server and Client Components](/docs/app/getting-started/server-and-client-components) docs for guidance.
|
||||
|
||||
## Examples
|
||||
|
||||
### Native History API
|
||||
|
||||
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).
|
||||
|
||||
#### `window.history.pushState`
|
||||
|
||||
Use it to add a new entry to the browser's history stack. The user can navigate back to the previous state. For example, to sort a list of products:
|
||||
|
||||
```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 params = new URLSearchParams(searchParams.toString())
|
||||
params.set('sort', sortOrder)
|
||||
window.history.pushState(null, '', `?${params.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 params = new URLSearchParams(searchParams.toString())
|
||||
params.set('sort', sortOrder)
|
||||
window.history.pushState(null, '', `?${params.toString()}`)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<button onClick={() => updateSorting('asc')}>Sort Ascending</button>
|
||||
<button onClick={() => updateSorting('desc')}>Sort Descending</button>
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
#### `window.history.replaceState`
|
||||
|
||||
Use it to replace the current entry on the browser's history stack. The user is not able to navigate back to the previous state. For example, to switch the application's locale:
|
||||
|
||||
```tsx fileName="app/ui/locale-switcher.tsx" switcher
|
||||
'use client'
|
||||
|
||||
import { usePathname } from 'next/navigation'
|
||||
|
||||
export function LocaleSwitcher() {
|
||||
const pathname = usePathname()
|
||||
|
||||
function switchLocale(locale: string) {
|
||||
// e.g. '/en/about' or '/fr/contact'
|
||||
const newPath = `/${locale}${pathname}`
|
||||
window.history.replaceState(null, '', newPath)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<button onClick={() => switchLocale('en')}>English</button>
|
||||
<button onClick={() => switchLocale('fr')}>French</button>
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx fileName="app/ui/locale-switcher.js" switcher
|
||||
'use client'
|
||||
|
||||
import { usePathname } from 'next/navigation'
|
||||
|
||||
export function LocaleSwitcher() {
|
||||
const pathname = usePathname()
|
||||
|
||||
function switchLocale(locale) {
|
||||
// e.g. '/en/about' or '/fr/contact'
|
||||
const newPath = `/${locale}${pathname}`
|
||||
window.history.replaceState(null, '', newPath)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<button onClick={() => switchLocale('en')}>English</button>
|
||||
<button onClick={() => switchLocale('fr')}>French</button>
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
593
node_modules/next/dist/docs/01-app/01-getting-started/05-server-and-client-components.md
generated
vendored
Normal file
593
node_modules/next/dist/docs/01-app/01-getting-started/05-server-and-client-components.md
generated
vendored
Normal file
@@ -0,0 +1,593 @@
|
||||
---
|
||||
title: Server and Client Components
|
||||
description: Learn how you can use React Server and Client Components to render parts of your application on the server or the client.
|
||||
related:
|
||||
title: Next Steps
|
||||
description: Learn more about the APIs mentioned in this page.
|
||||
links:
|
||||
- app/api-reference/directives/use-client
|
||||
---
|
||||
|
||||
By default, layouts and pages are [Server Components](https://react.dev/reference/rsc/server-components), which lets you fetch data and render parts of your UI on the server, optionally cache the result, and stream it to the client. When you need interactivity or browser APIs, you can use [Client Components](https://react.dev/reference/rsc/use-client) to layer in functionality.
|
||||
|
||||
This page explains how Server and Client Components work in Next.js and when to use them, with examples of how to compose them together in your application.
|
||||
|
||||
## When to use Server and Client Components?
|
||||
|
||||
The client and server environments have different capabilities. Server and Client components allow you to run logic in each environment depending on your use case.
|
||||
|
||||
Use **Client Components** when you need:
|
||||
|
||||
- [State](https://react.dev/learn/managing-state) and [event handlers](https://react.dev/learn/responding-to-events). E.g. `onClick`, `onChange`.
|
||||
- [Lifecycle logic](https://react.dev/learn/lifecycle-of-reactive-effects). E.g. `useEffect`.
|
||||
- Browser-only APIs. E.g. `localStorage`, `window`, `Navigator.geolocation`, etc.
|
||||
- [Custom hooks](https://react.dev/learn/reusing-logic-with-custom-hooks).
|
||||
|
||||
Use **Server Components** when you need:
|
||||
|
||||
- Fetch data from databases or APIs close to the source.
|
||||
- Use API keys, tokens, and other secrets without exposing them to the client.
|
||||
- Reduce the amount of JavaScript sent to the browser.
|
||||
- Improve the [First Contentful Paint (FCP)](https://web.dev/fcp/), and stream content progressively to the client.
|
||||
|
||||
For example, the `<Page>` component is a Server Component that fetches data about a post, and passes it as props to the `<LikeButton>` which handles client-side interactivity.
|
||||
|
||||
```tsx filename="app/[id]/page.tsx" highlight={1,17} switcher
|
||||
import LikeButton from '@/app/ui/like-button'
|
||||
import { getPost } from '@/lib/data'
|
||||
|
||||
export default async function Page({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ id: string }>
|
||||
}) {
|
||||
const { id } = await params
|
||||
const post = await getPost(id)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<main>
|
||||
<h1>{post.title}</h1>
|
||||
{/* ... */}
|
||||
<LikeButton likes={post.likes} />
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/[id]/page.js" highlight={1,12} switcher
|
||||
import LikeButton from '@/app/ui/like-button'
|
||||
import { getPost } from '@/lib/data'
|
||||
|
||||
export default async function Page({ params }) {
|
||||
const post = await getPost(params.id)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<main>
|
||||
<h1>{post.title}</h1>
|
||||
{/* ... */}
|
||||
<LikeButton likes={post.likes} />
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```tsx filename="app/ui/like-button.tsx" highlight={1} switcher
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
|
||||
export default function LikeButton({ likes }: { likes: number }) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/ui/like-button.js" highlight={1} switcher
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
|
||||
export default function LikeButton({ likes }) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
## How do Server and Client Components work in Next.js?
|
||||
|
||||
### On the server
|
||||
|
||||
On the server, Next.js uses React's APIs to orchestrate rendering. The rendering work is split into chunks, by individual route segments ([layouts and pages](/docs/app/getting-started/layouts-and-pages)):
|
||||
|
||||
- **Server Components** are rendered into a special data format called the React Server Component Payload (RSC Payload).
|
||||
- **Client Components** and the RSC Payload are used to [prerender](/docs/app/glossary#prerendering) HTML.
|
||||
|
||||
> **What is the React Server Component Payload (RSC)?**
|
||||
>
|
||||
> The RSC Payload is a compact binary representation of the rendered React Server Components tree. It's used by React on the client to update the browser's DOM. The RSC Payload contains:
|
||||
>
|
||||
> - The rendered result of Server Components
|
||||
> - Placeholders for where Client Components should be rendered and references to their JavaScript files
|
||||
> - Any props passed from a Server Component to a Client Component
|
||||
|
||||
### On the client (first load)
|
||||
|
||||
Then, on the client:
|
||||
|
||||
1. **HTML** is used to immediately show a fast non-interactive preview of the route to the user.
|
||||
2. **RSC Payload** is used to reconcile the Client and Server Component trees.
|
||||
3. **JavaScript** is used to hydrate Client Components and make the application interactive.
|
||||
|
||||
> **What is hydration?**
|
||||
>
|
||||
> Hydration is React's process for attaching [event handlers](https://react.dev/learn/responding-to-events) to the DOM, to make the static HTML interactive.
|
||||
|
||||
### Subsequent Navigations
|
||||
|
||||
On subsequent navigations:
|
||||
|
||||
- The **RSC Payload** is prefetched and cached for instant navigation.
|
||||
- **Client Components** are rendered entirely on the client, without the server-rendered HTML.
|
||||
|
||||
## Examples
|
||||
|
||||
### Using Client Components
|
||||
|
||||
You can create a Client Component by adding the [`"use client"`](https://react.dev/reference/react/use-client) directive at the top of the file, above your imports.
|
||||
|
||||
```tsx filename="app/ui/counter.tsx" highlight={1} switcher
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
|
||||
export default function Counter() {
|
||||
const [count, setCount] = useState(0)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>{count} likes</p>
|
||||
<button onClick={() => setCount(count + 1)}>Click me</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/ui/counter.js" highlight={1} switcher
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
|
||||
export default function Counter() {
|
||||
const [count, setCount] = useState(0)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>{count} likes</p>
|
||||
<button onClick={() => setCount(count + 1)}>Click me</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
`"use client"` is used to declare a **boundary** between the Server and Client module graphs (trees).
|
||||
|
||||
Once a file is marked with `"use client"`, **all its imports and child components are considered part of the client bundle**. This means you don't need to add the directive to every component that is intended for the client.
|
||||
|
||||
### Reducing JS bundle size
|
||||
|
||||
To reduce the size of your client JavaScript bundles, add `'use client'` to specific interactive components instead of marking large parts of your UI as Client Components.
|
||||
|
||||
For example, the `<Layout>` component contains mostly static elements like a logo and navigation links, but includes an interactive search bar. `<Search />` is interactive and needs to be a Client Component, however, the rest of the layout can remain a Server Component.
|
||||
|
||||
```tsx filename="app/layout.tsx" highlight={12} switcher
|
||||
// Client Component
|
||||
import Search from './search'
|
||||
// Server Component
|
||||
import Logo from './logo'
|
||||
|
||||
// Layout is a Server Component by default
|
||||
export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<>
|
||||
<nav>
|
||||
<Logo />
|
||||
<Search />
|
||||
</nav>
|
||||
<main>{children}</main>
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/layout.js" highlight={12} switcher
|
||||
// Client Component
|
||||
import Search from './search'
|
||||
// Server Component
|
||||
import Logo from './logo'
|
||||
|
||||
// Layout is a Server Component by default
|
||||
export default function Layout({ children }) {
|
||||
return (
|
||||
<>
|
||||
<nav>
|
||||
<Logo />
|
||||
<Search />
|
||||
</nav>
|
||||
<main>{children}</main>
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```tsx filename="app/ui/search.tsx" highlight={1} switcher
|
||||
'use client'
|
||||
|
||||
export default function Search() {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/ui/search.js" highlight={1} switcher
|
||||
'use client'
|
||||
|
||||
export default function Search() {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### Passing data from Server to Client Components
|
||||
|
||||
You can pass data from Server Components to Client Components using props.
|
||||
|
||||
```tsx filename="app/[id]/page.tsx" highlight={1,12} switcher
|
||||
import LikeButton from '@/app/ui/like-button'
|
||||
import { getPost } from '@/lib/data'
|
||||
|
||||
export default async function Page({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ id: string }>
|
||||
}) {
|
||||
const { id } = await params
|
||||
const post = await getPost(id)
|
||||
|
||||
return <LikeButton likes={post.likes} />
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/[id]/page.js" highlight={1,7} switcher
|
||||
import LikeButton from '@/app/ui/like-button'
|
||||
import { getPost } from '@/lib/data'
|
||||
|
||||
export default async function Page({ params }) {
|
||||
const post = await getPost(params.id)
|
||||
|
||||
return <LikeButton likes={post.likes} />
|
||||
}
|
||||
```
|
||||
|
||||
```tsx filename="app/ui/like-button.tsx" highlight={1} switcher
|
||||
'use client'
|
||||
|
||||
export default function LikeButton({ likes }: { likes: number }) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/ui/like-button.js" highlight={1} switcher
|
||||
'use client'
|
||||
|
||||
export default function LikeButton({ likes }) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Alternatively, you can stream data from a Server Component to a Client Component with the [`use` API](https://react.dev/reference/react/use). See an [example](/docs/app/getting-started/fetching-data#streaming-data-with-the-use-api).
|
||||
|
||||
> **Good to know**: Props passed to Client Components need to be [serializable](https://react.dev/reference/react/use-server#serializable-parameters-and-return-values) by React.
|
||||
|
||||
### Interleaving Server and Client Components
|
||||
|
||||
You can pass Server Components as a prop to a Client Component. This allows you to visually nest server-rendered UI within Client components.
|
||||
|
||||
A common pattern is to use `children` to create a _slot_ in a `<ClientComponent>`. For example, a `<Cart>` component that fetches data on the server, inside a `<Modal>` component that uses client state to toggle visibility.
|
||||
|
||||
```tsx filename="app/ui/modal.tsx" switcher
|
||||
'use client'
|
||||
|
||||
export default function Modal({ children }: { children: React.ReactNode }) {
|
||||
return <div>{children}</div>
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/ui/modal.js" switcher
|
||||
'use client'
|
||||
|
||||
export default function Modal({ children }) {
|
||||
return <div>{children}</div>
|
||||
}
|
||||
```
|
||||
|
||||
Then, in a parent Server Component (e.g.`<Page>`), you can pass a `<Cart>` as the child of the `<Modal>`:
|
||||
|
||||
```tsx filename="app/page.tsx" highlight={7} switcher
|
||||
import Modal from './ui/modal'
|
||||
import Cart from './ui/cart'
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<Modal>
|
||||
<Cart />
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/page.js" highlight={7} switcher
|
||||
import Modal from './ui/modal'
|
||||
import Cart from './ui/cart'
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<Modal>
|
||||
<Cart />
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
In this pattern, all Server Components will be rendered on the server ahead of time, including those as props. The resulting RSC payload will contain references of where Client Components should be rendered within the component tree.
|
||||
|
||||
### Context providers
|
||||
|
||||
[React context](https://react.dev/learn/passing-data-deeply-with-context) is commonly used to share global state like the current theme. However, React context is not supported in Server Components.
|
||||
|
||||
To use context, create a Client Component that accepts `children`:
|
||||
|
||||
```tsx filename="app/theme-provider.tsx" switcher
|
||||
'use client'
|
||||
|
||||
import { createContext } from 'react'
|
||||
|
||||
export const ThemeContext = createContext({})
|
||||
|
||||
export default function ThemeProvider({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return <ThemeContext.Provider value="dark">{children}</ThemeContext.Provider>
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/theme-provider.js" switcher
|
||||
'use client'
|
||||
|
||||
import { createContext } from 'react'
|
||||
|
||||
export const ThemeContext = createContext({})
|
||||
|
||||
export default function ThemeProvider({ children }) {
|
||||
return <ThemeContext.Provider value="dark">{children}</ThemeContext.Provider>
|
||||
}
|
||||
```
|
||||
|
||||
Then, import it into a Server Component (e.g. `layout`):
|
||||
|
||||
```tsx filename="app/layout.tsx" switcher
|
||||
import ThemeProvider from './theme-provider'
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<html>
|
||||
<body>
|
||||
<ThemeProvider>{children}</ThemeProvider>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/layout.js" switcher
|
||||
import ThemeProvider from './theme-provider'
|
||||
|
||||
export default function RootLayout({ children }) {
|
||||
return (
|
||||
<html>
|
||||
<body>
|
||||
<ThemeProvider>{children}</ThemeProvider>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
Your Server Component will now be able to directly render your provider, and all other Client Components throughout your app will be able to consume this context.
|
||||
|
||||
> **Good to know**: You should render providers as deep as possible in the tree – notice how `ThemeProvider` only wraps `{children}` instead of the entire `<html>` document. This makes it easier for Next.js to optimize the static parts of your Server Components.
|
||||
|
||||
### Third-party components
|
||||
|
||||
When using a third-party component that relies on client-only features, you can wrap it in a Client Component to ensure it works as expected.
|
||||
|
||||
For example, the `<Carousel />` can be imported from the `acme-carousel` package. This component uses `useState`, but it doesn't yet have the `"use client"` directive.
|
||||
|
||||
If you use `<Carousel />` within a Client Component, it will work as expected:
|
||||
|
||||
```tsx filename="app/gallery.tsx" switcher
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import { Carousel } from 'acme-carousel'
|
||||
|
||||
export default function Gallery() {
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button onClick={() => setIsOpen(true)}>View pictures</button>
|
||||
{/* Works, since Carousel is used within a Client Component */}
|
||||
{isOpen && <Carousel />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/gallery.js" switcher
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import { Carousel } from 'acme-carousel'
|
||||
|
||||
export default function Gallery() {
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button onClick={() => setIsOpen(true)}>View pictures</button>
|
||||
{/* Works, since Carousel is used within a Client Component */}
|
||||
{isOpen && <Carousel />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
However, if you try to use it directly within a Server Component, you'll see an error. This is because Next.js doesn't know `<Carousel />` is using client-only features.
|
||||
|
||||
To fix this, you can wrap third-party components that rely on client-only features in your own Client Components:
|
||||
|
||||
```tsx filename="app/carousel.tsx" switcher
|
||||
'use client'
|
||||
|
||||
import { Carousel } from 'acme-carousel'
|
||||
|
||||
export default Carousel
|
||||
```
|
||||
|
||||
```jsx filename="app/carousel.js" switcher
|
||||
'use client'
|
||||
|
||||
import { Carousel } from 'acme-carousel'
|
||||
|
||||
export default Carousel
|
||||
```
|
||||
|
||||
Now, you can use `<Carousel />` directly within a Server Component:
|
||||
|
||||
```tsx filename="app/page.tsx" switcher
|
||||
import Carousel from './carousel'
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<div>
|
||||
<p>View pictures</p>
|
||||
{/* Works, since Carousel is a Client Component */}
|
||||
<Carousel />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/page.js" switcher
|
||||
import Carousel from './carousel'
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<div>
|
||||
<p>View pictures</p>
|
||||
{/* Works, since Carousel is a Client Component */}
|
||||
<Carousel />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
> **Advice for Library Authors**
|
||||
>
|
||||
> If you’re building a component library, add the `"use client"` directive to entry points that rely on client-only features. This lets your users import components into Server Components without needing to create wrappers.
|
||||
>
|
||||
> It's worth noting some bundlers might strip out `"use client"` directives. You can find an example of how to configure esbuild to include the `"use client"` directive in the [React Wrap Balancer](https://github.com/shuding/react-wrap-balancer/blob/main/tsup.config.ts#L10-L13) and [Vercel Analytics](https://github.com/vercel/analytics/blob/main/packages/web/tsup.config.js#L26-L30) repositories.
|
||||
|
||||
### Preventing environment poisoning
|
||||
|
||||
JavaScript modules can be shared between both Server and Client Components modules. This means it's possible to accidentally import server-only code into the client. For example, consider the following function:
|
||||
|
||||
```ts filename="lib/data.ts" switcher
|
||||
export async function getData() {
|
||||
const res = await fetch('https://external-service.com/data', {
|
||||
headers: {
|
||||
authorization: process.env.API_KEY,
|
||||
},
|
||||
})
|
||||
|
||||
return res.json()
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="lib/data.js" switcher
|
||||
export async function getData() {
|
||||
const res = await fetch('https://external-service.com/data', {
|
||||
headers: {
|
||||
authorization: process.env.API_KEY,
|
||||
},
|
||||
})
|
||||
|
||||
return res.json()
|
||||
}
|
||||
```
|
||||
|
||||
This function contains an `API_KEY` that should never be exposed to the client.
|
||||
|
||||
In Next.js, only environment variables prefixed with `NEXT_PUBLIC_` are included in the client bundle. If variables are not prefixed, Next.js replaces them with an empty string.
|
||||
|
||||
As a result, even though `getData()` can be imported and executed on the client, it won't work as expected.
|
||||
|
||||
To prevent accidental usage in Client Components, you can use the [`server-only` package](https://www.npmjs.com/package/server-only).
|
||||
|
||||
Then, import the package into a file that contains server-only code:
|
||||
|
||||
```js filename="lib/data.js"
|
||||
import 'server-only'
|
||||
|
||||
export async function getData() {
|
||||
const res = await fetch('https://external-service.com/data', {
|
||||
headers: {
|
||||
authorization: process.env.API_KEY,
|
||||
},
|
||||
})
|
||||
|
||||
return res.json()
|
||||
}
|
||||
```
|
||||
|
||||
Now, if you try to import the module into a Client Component, there will be a build-time error.
|
||||
|
||||
The corresponding [`client-only` package](https://www.npmjs.com/package/client-only) can be used to mark modules that contain client-only logic like code that accesses the `window` object.
|
||||
|
||||
In Next.js, installing `server-only` or `client-only` is **optional**. However, if your linting rules flag extraneous dependencies, you may install them to avoid issues.
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
Next.js handles `server-only` and `client-only` imports internally to provide clearer error messages when a module is used in the wrong environment. The contents of these packages from NPM are not used by Next.js.
|
||||
|
||||
Next.js also provides its own type declarations for `server-only` and `client-only`, for TypeScript configurations where [`noUncheckedSideEffectImports`](https://www.typescriptlang.org/tsconfig/#noUncheckedSideEffectImports) is active.
|
||||
724
node_modules/next/dist/docs/01-app/01-getting-started/06-fetching-data.md
generated
vendored
Normal file
724
node_modules/next/dist/docs/01-app/01-getting-started/06-fetching-data.md
generated
vendored
Normal file
@@ -0,0 +1,724 @@
|
||||
---
|
||||
title: Fetching Data
|
||||
description: Learn how to fetch data and stream content that depends on data.
|
||||
related:
|
||||
title: API Reference
|
||||
description: Learn more about the features mentioned in this page by reading the API Reference.
|
||||
links:
|
||||
- app/guides/data-security
|
||||
- app/api-reference/functions/fetch
|
||||
- app/api-reference/file-conventions/loading
|
||||
- app/api-reference/config/next-config-js/logging
|
||||
- app/api-reference/config/next-config-js/taint
|
||||
---
|
||||
|
||||
{/* AI agent hint: If client-side navigations feel slow, Suspense and streaming alone are not enough. Export `unstable_instant` from the route to ensure instant navigations. See docs/01-app/02-guides/instant-navigation.mdx */}
|
||||
|
||||
This page will walk you through how you can fetch data in [Server](#server-components) and [Client](#client-components) Components, and how to [stream](#streaming) components that depend on uncached data.
|
||||
|
||||
## Fetching data
|
||||
|
||||
### Server Components
|
||||
|
||||
You can fetch data in Server Components using any asynchronous I/O, such as:
|
||||
|
||||
1. The [`fetch` API](#with-the-fetch-api)
|
||||
2. An [ORM or database](#with-an-orm-or-database)
|
||||
|
||||
#### With the `fetch` API
|
||||
|
||||
To fetch data with the `fetch` API, turn your component into an asynchronous function, and await the `fetch` call. For example:
|
||||
|
||||
```tsx filename="app/blog/page.tsx" switcher
|
||||
export default async function Page() {
|
||||
const data = await fetch('https://api.vercel.app/blog')
|
||||
const posts = await data.json()
|
||||
return (
|
||||
<ul>
|
||||
{posts.map((post) => (
|
||||
<li key={post.id}>{post.title}</li>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/blog/page.js" switcher
|
||||
export default async function Page() {
|
||||
const data = await fetch('https://api.vercel.app/blog')
|
||||
const posts = await data.json()
|
||||
return (
|
||||
<ul>
|
||||
{posts.map((post) => (
|
||||
<li key={post.id}>{post.title}</li>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
> **Good to know:**
|
||||
>
|
||||
> - Identical `fetch` requests in a React component tree are [memoized](/docs/app/glossary#memoization) by default, so you can fetch data in the component that needs it instead of drilling props.
|
||||
> - `fetch` requests are not cached by default and will block the page from rendering until the request is complete. Use the [`use cache`](/docs/app/api-reference/directives/use-cache) directive to cache results, or wrap the fetching component in [`<Suspense>`](/docs/app/getting-started/caching#streaming-uncached-data) to stream fresh data at request time. See [caching](/docs/app/getting-started/caching) for details.
|
||||
> - During development, you can log `fetch` calls for better visibility and debugging. See the [`logging` API reference](/docs/app/api-reference/config/next-config-js/logging).
|
||||
|
||||
#### With an ORM or database
|
||||
|
||||
Since Server Components are rendered on the server, credentials and query logic will not be included in the client bundle so you can safely make database queries using an ORM or database client.
|
||||
|
||||
```tsx filename="app/blog/page.tsx" switcher
|
||||
import { db, posts } from '@/lib/db'
|
||||
|
||||
export default async function Page() {
|
||||
const allPosts = await db.select().from(posts)
|
||||
return (
|
||||
<ul>
|
||||
{allPosts.map((post) => (
|
||||
<li key={post.id}>{post.title}</li>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/blog/page.js" switcher
|
||||
import { db, posts } from '@/lib/db'
|
||||
|
||||
export default async function Page() {
|
||||
const allPosts = await db.select().from(posts)
|
||||
return (
|
||||
<ul>
|
||||
{allPosts.map((post) => (
|
||||
<li key={post.id}>{post.title}</li>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
You should still ensure requests are properly authenticated and authorized. For best practices on securing server-side data access, see the [data security guide](/docs/app/guides/data-security).
|
||||
|
||||
### Streaming
|
||||
|
||||
When you fetch data in Server Components, the data is fetched and rendered on the server for each request. If you have any slow data requests, the whole route will be blocked from rendering until all the data is fetched.
|
||||
|
||||
To improve the initial load time and user experience, you can break the page into smaller _chunks_ and progressively send those chunks from the server to the client. This is called streaming. See the [Streaming guide](/docs/app/guides/streaming) for a deeper look at how streaming works, including the HTTP contract, infrastructure considerations, and performance trade-offs.
|
||||
|
||||
<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"
|
||||
/>
|
||||
|
||||
There are two ways you can use streaming in your application:
|
||||
|
||||
1. Wrapping a page with a [`loading.js` file](#with-loadingjs)
|
||||
2. Wrapping a component with [`<Suspense>`](#with-suspense)
|
||||
|
||||
#### With `loading.js`
|
||||
|
||||
You can create a `loading.js` file in the same folder as your page to stream the **entire page** while the data is being fetched. For example, to stream `app/blog/page.js`, add the file inside the `app/blog` folder.
|
||||
|
||||
<Image
|
||||
alt="Blog folder structure with loading.js file"
|
||||
srcLight="/docs/light/loading-file.png"
|
||||
srcDark="/docs/dark/loading-file.png"
|
||||
width="1600"
|
||||
height="525"
|
||||
/>
|
||||
|
||||
```tsx filename="app/blog/loading.tsx" switcher
|
||||
export default function Loading() {
|
||||
// Define the Loading UI here
|
||||
return <div>Loading...</div>
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/blog/loading.js" switcher
|
||||
export default function Loading() {
|
||||
// Define the Loading UI here
|
||||
return <div>Loading...</div>
|
||||
}
|
||||
```
|
||||
|
||||
On navigation, the user will immediately see the layout and a [loading state](#creating-meaningful-loading-states) while the page is being rendered. The new content will then be automatically swapped in once rendering is complete.
|
||||
|
||||
<Image
|
||||
alt="Loading UI"
|
||||
srcLight="/docs/light/loading-ui.png"
|
||||
srcDark="/docs/dark/loading-ui.png"
|
||||
width="1600"
|
||||
height="691"
|
||||
/>
|
||||
|
||||
Behind the scenes, `loading.js` will be [nested inside `layout.js`](/docs/app/getting-started/project-structure#component-hierarchy), and will automatically wrap the `page.js` file and any children below in a `<Suspense>` boundary.
|
||||
|
||||
<Image
|
||||
alt="loading.js overview"
|
||||
srcLight="/docs/light/loading-overview.png"
|
||||
srcDark="/docs/dark/loading-overview.png"
|
||||
width="1600"
|
||||
height="768"
|
||||
/>
|
||||
|
||||
Because of this, a layout that accesses uncached or runtime data (e.g. `cookies()`, `headers()`, or uncached fetches) does not fall back to a same route segment `loading.js`. Instead, it blocks navigation until the layout finishes rendering. [Cache Components](/docs/app/getting-started/caching) prevents this by guiding you with a build-time error.
|
||||
|
||||
To fix this, wrap the uncached access in its own [`<Suspense>`](#with-suspense) boundary with a fallback, or move the data fetching into `page.js` where `loading.js` can cover it. See [`loading.js`](/docs/app/api-reference/file-conventions/loading) for more details.
|
||||
|
||||
This is why, while `loading.js` works well for streaming route segments, using `<Suspense>` closer to the runtime or uncached data access is recommended.
|
||||
|
||||
#### With `<Suspense>`
|
||||
|
||||
`<Suspense>` allows you to be more granular about what parts of the page to stream. For example, you can immediately show any page content that falls outside of the `<Suspense>` boundary, and stream in the list of blog posts inside the boundary.
|
||||
|
||||
```tsx filename="app/blog/page.tsx" switcher
|
||||
import { Suspense } from 'react'
|
||||
import BlogList from '@/components/BlogList'
|
||||
import BlogListSkeleton from '@/components/BlogListSkeleton'
|
||||
|
||||
export default function BlogPage() {
|
||||
return (
|
||||
<div>
|
||||
{/* This content will be sent to the client immediately */}
|
||||
<header>
|
||||
<h1>Welcome to the Blog</h1>
|
||||
<p>Read the latest posts below.</p>
|
||||
</header>
|
||||
<main>
|
||||
{/* If there's any dynamic content inside this boundary, it will be streamed in */}
|
||||
<Suspense fallback={<BlogListSkeleton />}>
|
||||
<BlogList />
|
||||
</Suspense>
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/blog/page.js" switcher
|
||||
import { Suspense } from 'react'
|
||||
import BlogList from '@/components/BlogList'
|
||||
import BlogListSkeleton from '@/components/BlogListSkeleton'
|
||||
|
||||
export default function BlogPage() {
|
||||
return (
|
||||
<div>
|
||||
{/* This content will be sent to the client immediately */}
|
||||
<header>
|
||||
<h1>Welcome to the Blog</h1>
|
||||
<p>Read the latest posts below.</p>
|
||||
</header>
|
||||
<main>
|
||||
{/* If there's any dynamic content inside this boundary, it will be streamed in */}
|
||||
<Suspense fallback={<BlogListSkeleton />}>
|
||||
<BlogList />
|
||||
</Suspense>
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
#### Creating meaningful loading states
|
||||
|
||||
An instant loading state is fallback UI that is shown immediately to the user after navigation. For the best user experience, we recommend designing loading states that are meaningful and help users understand the app is responding. For example, you can use skeletons and spinners, or a small but meaningful part of future screens such as a cover photo, title, etc.
|
||||
|
||||
In development, you can preview and inspect the loading state of your components using the [React Devtools](https://react.dev/learn/react-developer-tools).
|
||||
|
||||
### Client Components
|
||||
|
||||
There are two ways to fetch data in Client Components, using:
|
||||
|
||||
1. React's [`use` API](https://react.dev/reference/react/use)
|
||||
2. A community library like [SWR](https://swr.vercel.app/) or [React Query](https://tanstack.com/query/latest)
|
||||
|
||||
#### Streaming data with the `use` API
|
||||
|
||||
You can use React's [`use` API](https://react.dev/reference/react/use) to [stream](#streaming) data from the server to client. Start by fetching data in your Server component, and pass the promise to your Client Component as prop:
|
||||
|
||||
```tsx filename="app/blog/page.tsx" switcher
|
||||
import Posts from '@/app/ui/posts'
|
||||
import { Suspense } from 'react'
|
||||
|
||||
export default function Page() {
|
||||
// Don't await the data fetching function
|
||||
const posts = getPosts()
|
||||
|
||||
return (
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
<Posts posts={posts} />
|
||||
</Suspense>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/blog/page.js" switcher
|
||||
import Posts from '@/app/ui/posts'
|
||||
import { Suspense } from 'react'
|
||||
|
||||
export default function Page() {
|
||||
// Don't await the data fetching function
|
||||
const posts = getPosts()
|
||||
|
||||
return (
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
<Posts posts={posts} />
|
||||
</Suspense>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
Then, in your Client Component, use the `use` API to read the promise:
|
||||
|
||||
```tsx filename="app/ui/posts.tsx" switcher
|
||||
'use client'
|
||||
import { use } from 'react'
|
||||
|
||||
export default function Posts({
|
||||
posts,
|
||||
}: {
|
||||
posts: Promise<{ id: string; title: string }[]>
|
||||
}) {
|
||||
const allPosts = use(posts)
|
||||
|
||||
return (
|
||||
<ul>
|
||||
{allPosts.map((post) => (
|
||||
<li key={post.id}>{post.title}</li>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/ui/posts.js" switcher
|
||||
'use client'
|
||||
import { use } from 'react'
|
||||
|
||||
export default function Posts({ posts }) {
|
||||
const allPosts = use(posts)
|
||||
|
||||
return (
|
||||
<ul>
|
||||
{allPosts.map((post) => (
|
||||
<li key={post.id}>{post.title}</li>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
In the example above, the `<Posts>` component is wrapped in a [`<Suspense>` boundary](https://react.dev/reference/react/Suspense). This means the fallback will be shown while the promise is being resolved. Learn more about [streaming](#streaming).
|
||||
|
||||
#### Community libraries
|
||||
|
||||
You can use a community library like [SWR](https://swr.vercel.app/) or [React Query](https://tanstack.com/query/latest) to fetch data in Client Components. These libraries have their own semantics for caching, streaming, and other features. For example, with SWR:
|
||||
|
||||
```tsx filename="app/blog/page.tsx" switcher
|
||||
'use client'
|
||||
import useSWR from 'swr'
|
||||
|
||||
const fetcher = (url) => fetch(url).then((r) => r.json())
|
||||
|
||||
export default function BlogPage() {
|
||||
const { data, error, isLoading } = useSWR(
|
||||
'https://api.vercel.app/blog',
|
||||
fetcher
|
||||
)
|
||||
|
||||
if (isLoading) return <div>Loading...</div>
|
||||
if (error) return <div>Error: {error.message}</div>
|
||||
|
||||
return (
|
||||
<ul>
|
||||
{data.map((post: { id: string; title: string }) => (
|
||||
<li key={post.id}>{post.title}</li>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/blog/page.js" switcher
|
||||
'use client'
|
||||
|
||||
import useSWR from 'swr'
|
||||
|
||||
const fetcher = (url) => fetch(url).then((r) => r.json())
|
||||
|
||||
export default function BlogPage() {
|
||||
const { data, error, isLoading } = useSWR(
|
||||
'https://api.vercel.app/blog',
|
||||
fetcher
|
||||
)
|
||||
|
||||
if (isLoading) return <div>Loading...</div>
|
||||
if (error) return <div>Error: {error.message}</div>
|
||||
|
||||
return (
|
||||
<ul>
|
||||
{data.map((post) => (
|
||||
<li key={post.id}>{post.title}</li>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Sequential data fetching
|
||||
|
||||
Sequential data fetching happens when one request depends on data from another.
|
||||
|
||||
For example, `<Playlists>` can only fetch data after `<Artist>` completes because it needs the `artistID`:
|
||||
|
||||
```tsx filename="app/artist/[username]/page.tsx" switcher
|
||||
export default async function Page({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ username: string }>
|
||||
}) {
|
||||
const { username } = await params
|
||||
// Get artist information
|
||||
const artist = await getArtist(username)
|
||||
|
||||
return (
|
||||
<>
|
||||
<h1>{artist.name}</h1>
|
||||
{/* Show fallback UI while the Playlists component is loading */}
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
{/* Pass the artist ID to the Playlists component */}
|
||||
<Playlists artistID={artist.id} />
|
||||
</Suspense>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
async function Playlists({ artistID }: { artistID: string }) {
|
||||
// Use the artist ID to fetch playlists
|
||||
const playlists = await getArtistPlaylists(artistID)
|
||||
|
||||
return (
|
||||
<ul>
|
||||
{playlists.map((playlist) => (
|
||||
<li key={playlist.id}>{playlist.name}</li>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/artist/[username]/page.js" switcher
|
||||
export default async function Page({ params }) {
|
||||
const { username } = await params
|
||||
// Get artist information
|
||||
const artist = await getArtist(username)
|
||||
|
||||
return (
|
||||
<>
|
||||
<h1>{artist.name}</h1>
|
||||
{/* Show fallback UI while the Playlists component is loading */}
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
{/* Pass the artist ID to the Playlists component */}
|
||||
<Playlists artistID={artist.id} />
|
||||
</Suspense>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
async function Playlists({ artistID }) {
|
||||
// Use the artist ID to fetch playlists
|
||||
const playlists = await getArtistPlaylists(artistID)
|
||||
|
||||
return (
|
||||
<ul>
|
||||
{playlists.map((playlist) => (
|
||||
<li key={playlist.id}>{playlist.name}</li>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
In this example, `<Suspense>` allows the playlists to stream in after the artist data loads. However, the page still waits for the artist data before displaying anything. To prevent this, you can wrap the entire page component in a `<Suspense>` boundary (for example, using a [`loading.js` file](#with-loadingjs)) to show a loading state immediately.
|
||||
|
||||
Ensure your data source can resolve the first request quickly, as it blocks everything else. If you can't optimize the request further, consider [caching](/docs/app/getting-started/caching) the result if the data changes infrequently.
|
||||
|
||||
### Parallel data fetching
|
||||
|
||||
Parallel data fetching happens when data requests in a route are eagerly initiated and start at the same time.
|
||||
|
||||
By default, [layouts and pages](/docs/app/getting-started/layouts-and-pages) are rendered in parallel. So each segment starts fetching data as soon as possible.
|
||||
|
||||
However, within _any_ component, multiple `async`/`await` requests can still be sequential if placed after the other. For example, `getAlbums` will be blocked until `getArtist` is resolved:
|
||||
|
||||
```tsx filename="app/artist/[username]/page.tsx" switcher
|
||||
import { getArtist, getAlbums } from '@/app/lib/data'
|
||||
|
||||
export default async function Page({ params }) {
|
||||
// These requests will be sequential
|
||||
const { username } = await params
|
||||
const artist = await getArtist(username)
|
||||
const albums = await getAlbums(username)
|
||||
return <div>{artist.name}</div>
|
||||
}
|
||||
```
|
||||
|
||||
Start multiple requests by calling `fetch`, then await them with [`Promise.all`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all). Requests begin as soon as `fetch` is called.
|
||||
|
||||
```tsx filename="app/artist/[username]/page.tsx" highlight={3,8,24} switcher
|
||||
import Albums from './albums'
|
||||
|
||||
async function getArtist(username: string) {
|
||||
const res = await fetch(`https://api.example.com/artist/${username}`)
|
||||
return res.json()
|
||||
}
|
||||
|
||||
async function getAlbums(username: string) {
|
||||
const res = await fetch(`https://api.example.com/artist/${username}/albums`)
|
||||
return res.json()
|
||||
}
|
||||
|
||||
export default async function Page({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ username: string }>
|
||||
}) {
|
||||
const { username } = await params
|
||||
|
||||
// Initiate requests
|
||||
const artistData = getArtist(username)
|
||||
const albumsData = getAlbums(username)
|
||||
|
||||
const [artist, albums] = await Promise.all([artistData, albumsData])
|
||||
|
||||
return (
|
||||
<>
|
||||
<h1>{artist.name}</h1>
|
||||
<Albums list={albums} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/artist/[username]/page.js" highlight={3,8,20} switcher
|
||||
import Albums from './albums'
|
||||
|
||||
async function getArtist(username) {
|
||||
const res = await fetch(`https://api.example.com/artist/${username}`)
|
||||
return res.json()
|
||||
}
|
||||
|
||||
async function getAlbums(username) {
|
||||
const res = await fetch(`https://api.example.com/artist/${username}/albums`)
|
||||
return res.json()
|
||||
}
|
||||
|
||||
export default async function Page({ params }) {
|
||||
const { username } = await params
|
||||
|
||||
// Initiate requests
|
||||
const artistData = getArtist(username)
|
||||
const albumsData = getAlbums(username)
|
||||
|
||||
const [artist, albums] = await Promise.all([artistData, albumsData])
|
||||
|
||||
return (
|
||||
<>
|
||||
<h1>{artist.name}</h1>
|
||||
<Albums list={albums} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
> **Good to know:** If one request fails when using `Promise.all`, the entire operation will fail. To handle this, you can use the [`Promise.allSettled`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled) method instead.
|
||||
|
||||
### Sharing data with context and `React.cache`
|
||||
|
||||
You can share fetched data across both Server and Client Components by combining [`React.cache`](https://react.dev/reference/react/cache) with context providers.
|
||||
|
||||
Create a cached function that fetches data:
|
||||
|
||||
```ts filename="app/lib/user.ts" switcher
|
||||
import { cache } from 'react'
|
||||
|
||||
export const getUser = cache(async () => {
|
||||
const res = await fetch('https://api.example.com/user')
|
||||
return res.json()
|
||||
})
|
||||
```
|
||||
|
||||
```js filename="app/lib/user.js" switcher
|
||||
import { cache } from 'react'
|
||||
|
||||
export const getUser = cache(async () => {
|
||||
const res = await fetch('https://api.example.com/user')
|
||||
return res.json()
|
||||
})
|
||||
```
|
||||
|
||||
Create a context provider that stores the promise:
|
||||
|
||||
```tsx filename="app/user-provider.tsx" switcher
|
||||
'use client'
|
||||
|
||||
import { createContext } from 'react'
|
||||
|
||||
type User = {
|
||||
id: string
|
||||
name: string
|
||||
}
|
||||
|
||||
export const UserContext = createContext<Promise<User> | null>(null)
|
||||
|
||||
export default function UserProvider({
|
||||
children,
|
||||
userPromise,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
userPromise: Promise<User>
|
||||
}) {
|
||||
return <UserContext value={userPromise}>{children}</UserContext>
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/user-provider.js" switcher
|
||||
'use client'
|
||||
|
||||
import { createContext } from 'react'
|
||||
|
||||
export const UserContext = createContext(null)
|
||||
|
||||
export default function UserProvider({ children, userPromise }) {
|
||||
return <UserContext value={userPromise}>{children}</UserContext>
|
||||
}
|
||||
```
|
||||
|
||||
In a layout, pass the promise to the provider without awaiting:
|
||||
|
||||
```tsx filename="app/layout.tsx" switcher
|
||||
import UserProvider from './user-provider'
|
||||
import { getUser } from './lib/user'
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
const userPromise = getUser() // Don't await
|
||||
|
||||
return (
|
||||
<html>
|
||||
<body>
|
||||
<UserProvider userPromise={userPromise}>{children}</UserProvider>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/layout.js" switcher
|
||||
import UserProvider from './user-provider'
|
||||
import { getUser } from './lib/user'
|
||||
|
||||
export default function RootLayout({ children }) {
|
||||
const userPromise = getUser() // Don't await
|
||||
|
||||
return (
|
||||
<html>
|
||||
<body>
|
||||
<UserProvider userPromise={userPromise}>{children}</UserProvider>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
Client Components use [`use()`](https://react.dev/reference/react/use) to resolve the promise from context, wrapped in `<Suspense>` for fallback UI:
|
||||
|
||||
```tsx filename="app/ui/profile.tsx" switcher
|
||||
'use client'
|
||||
|
||||
import { use, useContext } from 'react'
|
||||
import { UserContext } from '../user-provider'
|
||||
|
||||
export function Profile() {
|
||||
const userPromise = useContext(UserContext)
|
||||
if (!userPromise) {
|
||||
throw new Error('useContext must be used within a UserProvider')
|
||||
}
|
||||
const user = use(userPromise)
|
||||
return <p>Welcome, {user.name}</p>
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/ui/profile.js" switcher
|
||||
'use client'
|
||||
|
||||
import { use, useContext } from 'react'
|
||||
import { UserContext } from '../user-provider'
|
||||
|
||||
export function Profile() {
|
||||
const userPromise = useContext(UserContext)
|
||||
if (!userPromise) {
|
||||
throw new Error('useContext must be used within a UserProvider')
|
||||
}
|
||||
const user = use(userPromise)
|
||||
return <p>Welcome, {user.name}</p>
|
||||
}
|
||||
```
|
||||
|
||||
```tsx filename="app/page.tsx" switcher
|
||||
import { Suspense } from 'react'
|
||||
import { Profile } from './ui/profile'
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<Suspense fallback={<div>Loading profile...</div>}>
|
||||
<Profile />
|
||||
</Suspense>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/page.js" switcher
|
||||
import { Suspense } from 'react'
|
||||
import { Profile } from './ui/profile'
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<Suspense fallback={<div>Loading profile...</div>}>
|
||||
<Profile />
|
||||
</Suspense>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
Server Components can also call `getUser()` directly:
|
||||
|
||||
```tsx filename="app/dashboard/page.tsx" switcher
|
||||
import { getUser } from '../lib/user'
|
||||
|
||||
export default async function DashboardPage() {
|
||||
const user = await getUser() // Cached - same request, no duplicate fetch
|
||||
return <h1>Dashboard for {user.name}</h1>
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/dashboard/page.js" switcher
|
||||
import { getUser } from '../lib/user'
|
||||
|
||||
export default async function DashboardPage() {
|
||||
const user = await getUser() // Cached - same request, no duplicate fetch
|
||||
return <h1>Dashboard for {user.name}</h1>
|
||||
}
|
||||
```
|
||||
|
||||
Since `getUser` is wrapped with `React.cache`, multiple calls within the same request return the same memoized result, whether called directly in Server Components or resolved via context in Client Components.
|
||||
|
||||
> **Good to know**: `React.cache` is scoped to the current request only. Each request gets its own memoization scope with no sharing between requests.
|
||||
594
node_modules/next/dist/docs/01-app/01-getting-started/07-mutating-data.md
generated
vendored
Normal file
594
node_modules/next/dist/docs/01-app/01-getting-started/07-mutating-data.md
generated
vendored
Normal file
@@ -0,0 +1,594 @@
|
||||
---
|
||||
title: Mutating Data
|
||||
description: Learn how to mutate data using Server Functions and Server Actions in Next.js.
|
||||
related:
|
||||
title: API Reference
|
||||
description: Learn more about the features mentioned in this page by reading the API Reference.
|
||||
links:
|
||||
- app/api-reference/functions/revalidatePath
|
||||
- app/api-reference/functions/revalidateTag
|
||||
- app/api-reference/functions/redirect
|
||||
---
|
||||
|
||||
You can mutate data in Next.js using [React Server Functions](https://react.dev/reference/rsc/server-functions). This page will go through how you can [create](#creating-server-functions) and [invoke](#invoking-server-functions) Server Functions.
|
||||
|
||||
## What are Server Functions?
|
||||
|
||||
A **Server Function** is an asynchronous function that runs on the server. You can call them from the client through a network request, which is why they must be asynchronous.
|
||||
|
||||
In an `action` or mutation context, they are also called **Server Actions**.
|
||||
|
||||
By convention, a Server Action is an async function used with [`startTransition`](https://react.dev/reference/react/startTransition). This happens automatically when the function is:
|
||||
|
||||
- Passed to a `<form>` using the `action` prop.
|
||||
- Passed to a `<button>` using the `formAction` prop.
|
||||
|
||||
When an action is invoked, Next.js can return both the updated UI and new data in a single server roundtrip.
|
||||
|
||||
Behind the scenes, actions use the `POST` method, and only this HTTP method can invoke them.
|
||||
|
||||
> [!WARNING]
|
||||
> Server Functions are reachable via direct POST requests, not just through your application's UI. Always verify authentication and authorization inside every Server Function. See the [Data Security guide](/docs/app/guides/data-security#authentication-and-authorization) for recommended patterns.
|
||||
|
||||
> **Good to know:** A Server Action is a Server Function used in a specific way (for handling form submissions and mutations). Server Function is the broader term.
|
||||
|
||||
## Creating Server Functions
|
||||
|
||||
A Server Function can be defined by using the [`use server`](https://react.dev/reference/rsc/use-server) directive. You can place the directive at the top of an **asynchronous** function to mark the function as a Server Function, or at the top of a separate file to mark all exports of that file.
|
||||
|
||||
```ts filename="app/lib/actions.ts" switcher
|
||||
import { auth } from '@/lib/auth'
|
||||
|
||||
export async function createPost(formData: FormData) {
|
||||
'use server'
|
||||
const session = await auth()
|
||||
if (!session?.user) {
|
||||
throw new Error('Unauthorized')
|
||||
}
|
||||
|
||||
const title = formData.get('title')
|
||||
const content = formData.get('content')
|
||||
|
||||
// Mutate data
|
||||
// Revalidate cache
|
||||
}
|
||||
|
||||
export async function deletePost(formData: FormData) {
|
||||
'use server'
|
||||
const session = await auth()
|
||||
if (!session?.user) {
|
||||
throw new Error('Unauthorized')
|
||||
}
|
||||
|
||||
const id = formData.get('id')
|
||||
|
||||
// Verify the user owns this resource before deleting
|
||||
// Mutate data
|
||||
// Revalidate cache
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="app/lib/actions.js" switcher
|
||||
import { auth } from '@/lib/auth'
|
||||
|
||||
export async function createPost(formData) {
|
||||
'use server'
|
||||
const session = await auth()
|
||||
if (!session?.user) {
|
||||
throw new Error('Unauthorized')
|
||||
}
|
||||
|
||||
const title = formData.get('title')
|
||||
const content = formData.get('content')
|
||||
|
||||
// Mutate data
|
||||
// Revalidate cache
|
||||
}
|
||||
|
||||
export async function deletePost(formData) {
|
||||
'use server'
|
||||
const session = await auth()
|
||||
if (!session?.user) {
|
||||
throw new Error('Unauthorized')
|
||||
}
|
||||
|
||||
const id = formData.get('id')
|
||||
|
||||
// Verify the user owns this resource before deleting
|
||||
// Mutate data
|
||||
// Revalidate cache
|
||||
}
|
||||
```
|
||||
|
||||
### Server Components
|
||||
|
||||
Server Functions can be inlined in Server Components by adding the `"use server"` directive to the top of the function body:
|
||||
|
||||
```tsx filename="app/page.tsx" switcher
|
||||
export default function Page() {
|
||||
// Server Action
|
||||
async function createPost(formData: FormData) {
|
||||
'use server'
|
||||
// ...
|
||||
}
|
||||
|
||||
return <></>
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/page.js" switcher
|
||||
export default function Page() {
|
||||
// Server Action
|
||||
async function createPost(formData) {
|
||||
'use server'
|
||||
// ...
|
||||
}
|
||||
|
||||
return <></>
|
||||
}
|
||||
```
|
||||
|
||||
> **Good to know:** Server Components support progressive enhancement by default, meaning forms that call Server Actions will be submitted even if JavaScript hasn't loaded yet or is disabled.
|
||||
|
||||
### Client Components
|
||||
|
||||
It's not possible to define Server Functions in Client Components. However, you can invoke them in Client Components by importing them from a file that has the `"use server"` directive at the top of it:
|
||||
|
||||
```ts filename="app/actions.ts" switcher
|
||||
'use server'
|
||||
|
||||
export async function createPost() {}
|
||||
```
|
||||
|
||||
```js filename="app/actions.js" switcher
|
||||
'use server'
|
||||
|
||||
export async function createPost() {}
|
||||
```
|
||||
|
||||
```tsx filename="app/ui/button.tsx" switcher
|
||||
'use client'
|
||||
|
||||
import { createPost } from '@/app/actions'
|
||||
|
||||
export function Button() {
|
||||
return <button formAction={createPost}>Create</button>
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/ui/button.js" switcher
|
||||
'use client'
|
||||
|
||||
import { createPost } from '@/app/actions'
|
||||
|
||||
export function Button() {
|
||||
return <button formAction={createPost}>Create</button>
|
||||
}
|
||||
```
|
||||
|
||||
> **Good to know:** In Client Components, forms invoking Server Actions will queue submissions if JavaScript isn't loaded yet, and will be prioritized for hydration. After hydration, the browser does not refresh on form submission.
|
||||
|
||||
### Passing actions as props
|
||||
|
||||
You can also pass an action to a Client Component as a prop:
|
||||
|
||||
```jsx
|
||||
<ClientComponent updateItemAction={updateItem} />
|
||||
```
|
||||
|
||||
```tsx filename="app/client-component.tsx" switcher
|
||||
'use client'
|
||||
|
||||
export default function ClientComponent({
|
||||
updateItemAction,
|
||||
}: {
|
||||
updateItemAction: (formData: FormData) => void
|
||||
}) {
|
||||
return <form action={updateItemAction}>{/* ... */}</form>
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/client-component.js" switcher
|
||||
'use client'
|
||||
|
||||
export default function ClientComponent({ updateItemAction }) {
|
||||
return <form action={updateItemAction}>{/* ... */}</form>
|
||||
}
|
||||
```
|
||||
|
||||
## Invoking Server Functions
|
||||
|
||||
There are two main ways you can invoke a Server Function:
|
||||
|
||||
1. [Forms](#forms) in Server and Client Components
|
||||
2. [Event Handlers](#event-handlers) and [useEffect](#useeffect) in Client Components
|
||||
|
||||
> **Good to know:** Server Functions are designed for server-side mutations. The client currently dispatches and awaits them one at a time. This is an implementation detail and may change. If you need parallel data fetching, use [data fetching](/docs/app/getting-started/fetching-data#server-components) in Server Components, or perform parallel work inside a single Server Function or [Route Handler](/docs/app/guides/backend-for-frontend#manipulating-data).
|
||||
|
||||
### Forms
|
||||
|
||||
React extends the HTML [`<form>`](https://react.dev/reference/react-dom/components/form) element to allow a Server Function to be invoked with the HTML `action` prop.
|
||||
|
||||
When invoked in a form, the function automatically receives the [`FormData`](https://developer.mozilla.org/docs/Web/API/FormData/FormData) object. You can extract the data using the native [`FormData` methods](https://developer.mozilla.org/en-US/docs/Web/API/FormData#instance_methods):
|
||||
|
||||
```tsx filename="app/ui/form.tsx" switcher
|
||||
import { createPost } from '@/app/actions'
|
||||
|
||||
export function Form() {
|
||||
return (
|
||||
<form action={createPost}>
|
||||
<input type="text" name="title" />
|
||||
<input type="text" name="content" />
|
||||
<button type="submit">Create</button>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/ui/form.js" switcher
|
||||
import { createPost } from '@/app/actions'
|
||||
|
||||
export function Form() {
|
||||
return (
|
||||
<form action={createPost}>
|
||||
<input type="text" name="title" />
|
||||
<input type="text" name="content" />
|
||||
<button type="submit">Create</button>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```ts filename="app/actions.ts" switcher
|
||||
'use server'
|
||||
|
||||
import { auth } from '@/lib/auth'
|
||||
|
||||
export async function createPost(formData: FormData) {
|
||||
const session = await auth()
|
||||
if (!session?.user) {
|
||||
throw new Error('Unauthorized')
|
||||
}
|
||||
|
||||
const title = formData.get('title')
|
||||
const content = formData.get('content')
|
||||
|
||||
// Mutate data
|
||||
// Revalidate cache
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="app/actions.js" switcher
|
||||
'use server'
|
||||
|
||||
import { auth } from '@/lib/auth'
|
||||
|
||||
export async function createPost(formData) {
|
||||
const session = await auth()
|
||||
if (!session?.user) {
|
||||
throw new Error('Unauthorized')
|
||||
}
|
||||
|
||||
const title = formData.get('title')
|
||||
const content = formData.get('content')
|
||||
|
||||
// Mutate data
|
||||
// Revalidate cache
|
||||
}
|
||||
```
|
||||
|
||||
### Event Handlers
|
||||
|
||||
You can invoke a Server Function in a Client Component by using event handlers such as `onClick`.
|
||||
|
||||
```tsx filename="app/like-button.tsx" switcher
|
||||
'use client'
|
||||
|
||||
import { incrementLike } from './actions'
|
||||
import { useState } from 'react'
|
||||
|
||||
export default function LikeButton({ initialLikes }: { initialLikes: number }) {
|
||||
const [likes, setLikes] = useState(initialLikes)
|
||||
|
||||
return (
|
||||
<>
|
||||
<p>Total Likes: {likes}</p>
|
||||
<button
|
||||
onClick={async () => {
|
||||
const updatedLikes = await incrementLike()
|
||||
setLikes(updatedLikes)
|
||||
}}
|
||||
>
|
||||
Like
|
||||
</button>
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/like-button.js" switcher
|
||||
'use client'
|
||||
|
||||
import { incrementLike } from './actions'
|
||||
import { useState } from 'react'
|
||||
|
||||
export default function LikeButton({ initialLikes }) {
|
||||
const [likes, setLikes] = useState(initialLikes)
|
||||
|
||||
return (
|
||||
<>
|
||||
<p>Total Likes: {likes}</p>
|
||||
<button
|
||||
onClick={async () => {
|
||||
const updatedLikes = await incrementLike()
|
||||
setLikes(updatedLikes)
|
||||
}}
|
||||
>
|
||||
Like
|
||||
</button>
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Showing a pending state
|
||||
|
||||
While executing a Server Function, you can show a loading indicator with React's [`useActionState`](https://react.dev/reference/react/useActionState) hook. This hook returns a `pending` boolean:
|
||||
|
||||
```tsx filename="app/ui/button.tsx" switcher
|
||||
'use client'
|
||||
|
||||
import { useActionState, startTransition } from 'react'
|
||||
import { createPost } from '@/app/actions'
|
||||
import { LoadingSpinner } from '@/app/ui/loading-spinner'
|
||||
|
||||
export function Button() {
|
||||
const [state, action, pending] = useActionState(createPost, false)
|
||||
|
||||
return (
|
||||
<button onClick={() => startTransition(action)}>
|
||||
{pending ? <LoadingSpinner /> : 'Create Post'}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/ui/button.js" switcher
|
||||
'use client'
|
||||
|
||||
import { useActionState, startTransition } from 'react'
|
||||
import { createPost } from '@/app/actions'
|
||||
import { LoadingSpinner } from '@/app/ui/loading-spinner'
|
||||
|
||||
export function Button() {
|
||||
const [state, action, pending] = useActionState(createPost, false)
|
||||
|
||||
return (
|
||||
<button onClick={() => startTransition(action)}>
|
||||
{pending ? <LoadingSpinner /> : 'Create Post'}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Refresh data
|
||||
|
||||
After a mutation, you may want to refresh the current page to show the latest data. You can do this by calling [`refresh`](/docs/app/api-reference/functions/refresh) from `next/cache` in a Server Action:
|
||||
|
||||
```ts filename="app/lib/actions.ts" switcher
|
||||
'use server'
|
||||
|
||||
import { auth } from '@/lib/auth'
|
||||
import { refresh } from 'next/cache'
|
||||
|
||||
export async function updatePost(formData: FormData) {
|
||||
const session = await auth()
|
||||
if (!session?.user) {
|
||||
throw new Error('Unauthorized')
|
||||
}
|
||||
// Mutate data
|
||||
// ...
|
||||
|
||||
refresh()
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="app/lib/actions.js" switcher
|
||||
'use server'
|
||||
|
||||
import { auth } from '@/lib/auth'
|
||||
import { refresh } from 'next/cache'
|
||||
|
||||
export async function updatePost(formData) {
|
||||
const session = await auth()
|
||||
if (!session?.user) {
|
||||
throw new Error('Unauthorized')
|
||||
}
|
||||
// Mutate data
|
||||
// ...
|
||||
|
||||
refresh()
|
||||
}
|
||||
```
|
||||
|
||||
This refreshes the client router, ensuring the UI reflects the latest state. The `refresh()` function does not revalidate tagged data. To revalidate tagged data, use [`updateTag`](/docs/app/api-reference/functions/updateTag) or [`revalidateTag`](/docs/app/api-reference/functions/revalidateTag) instead.
|
||||
|
||||
### Revalidate data
|
||||
|
||||
After performing a mutation, you can revalidate the Next.js cache and show the updated data by calling [`revalidatePath`](/docs/app/api-reference/functions/revalidatePath) or [`revalidateTag`](/docs/app/api-reference/functions/revalidateTag) within the Server Function:
|
||||
|
||||
```ts filename="app/lib/actions.ts" switcher
|
||||
import { auth } from '@/lib/auth'
|
||||
import { revalidatePath } from 'next/cache'
|
||||
|
||||
export async function createPost(formData: FormData) {
|
||||
'use server'
|
||||
const session = await auth()
|
||||
if (!session?.user) {
|
||||
throw new Error('Unauthorized')
|
||||
}
|
||||
// Mutate data
|
||||
// ...
|
||||
|
||||
revalidatePath('/posts')
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="app/actions.js" switcher
|
||||
import { auth } from '@/lib/auth'
|
||||
import { revalidatePath } from 'next/cache'
|
||||
|
||||
export async function createPost(formData) {
|
||||
'use server'
|
||||
const session = await auth()
|
||||
if (!session?.user) {
|
||||
throw new Error('Unauthorized')
|
||||
}
|
||||
// Mutate data
|
||||
// ...
|
||||
revalidatePath('/posts')
|
||||
}
|
||||
```
|
||||
|
||||
### Redirect after a mutation
|
||||
|
||||
You may want to redirect the user to a different page after a mutation. You can do this by calling [`redirect`](/docs/app/api-reference/functions/redirect) within the Server Function.
|
||||
|
||||
```ts filename="app/lib/actions.ts" switcher
|
||||
'use server'
|
||||
|
||||
import { auth } from '@/lib/auth'
|
||||
import { revalidatePath } from 'next/cache'
|
||||
import { redirect } from 'next/navigation'
|
||||
|
||||
export async function createPost(formData: FormData) {
|
||||
const session = await auth()
|
||||
if (!session?.user) {
|
||||
throw new Error('Unauthorized')
|
||||
}
|
||||
// Mutate data
|
||||
// ...
|
||||
|
||||
revalidatePath('/posts')
|
||||
redirect('/posts')
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="app/actions.js" switcher
|
||||
'use server'
|
||||
|
||||
import { auth } from '@/lib/auth'
|
||||
import { revalidatePath } from 'next/cache'
|
||||
import { redirect } from 'next/navigation'
|
||||
|
||||
export async function createPost(formData) {
|
||||
const session = await auth()
|
||||
if (!session?.user) {
|
||||
throw new Error('Unauthorized')
|
||||
}
|
||||
// Mutate data
|
||||
// ...
|
||||
|
||||
revalidatePath('/posts')
|
||||
redirect('/posts')
|
||||
}
|
||||
```
|
||||
|
||||
Calling `redirect` [throws](/docs/app/api-reference/functions/redirect#behavior) a framework handled control-flow exception. Any code after it won't execute. If you need fresh data, call [`revalidatePath`](/docs/app/api-reference/functions/revalidatePath) or [`revalidateTag`](/docs/app/api-reference/functions/revalidateTag) beforehand.
|
||||
|
||||
### Cookies
|
||||
|
||||
You can `get`, `set`, and `delete` cookies inside a Server Action using the [`cookies`](/docs/app/api-reference/functions/cookies) API.
|
||||
|
||||
When you [set or delete](/docs/app/api-reference/functions/cookies#understanding-cookie-behavior-in-server-functions) a cookie in a Server Action, Next.js re-renders the current page and its layouts on the server so the **UI reflects the new cookie value**.
|
||||
|
||||
> **Good to know**: The server update applies to the current React tree, re-rendering, mounting, or unmounting components, as needed. Client state is preserved for re-rendered components, and effects re-run if their dependencies changed.
|
||||
|
||||
```ts filename="app/actions.ts" switcher
|
||||
'use server'
|
||||
|
||||
import { cookies } from 'next/headers'
|
||||
|
||||
export async function exampleAction() {
|
||||
const cookieStore = await cookies()
|
||||
|
||||
// Get cookie
|
||||
cookieStore.get('name')?.value
|
||||
|
||||
// Set cookie
|
||||
cookieStore.set('name', 'Delba')
|
||||
|
||||
// Delete cookie
|
||||
cookieStore.delete('name')
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="app/actions.js" switcher
|
||||
'use server'
|
||||
|
||||
import { cookies } from 'next/headers'
|
||||
|
||||
export async function exampleAction() {
|
||||
// Get cookie
|
||||
const cookieStore = await cookies()
|
||||
|
||||
// Get cookie
|
||||
cookieStore.get('name')?.value
|
||||
|
||||
// Set cookie
|
||||
cookieStore.set('name', 'Delba')
|
||||
|
||||
// Delete cookie
|
||||
cookieStore.delete('name')
|
||||
}
|
||||
```
|
||||
|
||||
### useEffect
|
||||
|
||||
You can use the React [`useEffect`](https://react.dev/reference/react/useEffect) hook to invoke a Server Action when the component mounts or a dependency changes. This is useful for mutations that depend on global events or need to be triggered automatically. For example, `onKeyDown` for app shortcuts, an intersection observer hook for infinite scrolling, or when the component mounts to update a view count:
|
||||
|
||||
```tsx filename="app/view-count.tsx" switcher
|
||||
'use client'
|
||||
|
||||
import { incrementViews } from './actions'
|
||||
import { useState, useEffect, useTransition } from 'react'
|
||||
|
||||
export default function ViewCount({ initialViews }: { initialViews: number }) {
|
||||
const [views, setViews] = useState(initialViews)
|
||||
const [isPending, startTransition] = useTransition()
|
||||
|
||||
useEffect(() => {
|
||||
startTransition(async () => {
|
||||
const updatedViews = await incrementViews()
|
||||
setViews(updatedViews)
|
||||
})
|
||||
}, [])
|
||||
|
||||
// You can use `isPending` to give users feedback
|
||||
return <p>Total Views: {views}</p>
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/view-count.js" switcher
|
||||
'use client'
|
||||
|
||||
import { incrementViews } from './actions'
|
||||
import { useState, useEffect, useTransition } from 'react'
|
||||
|
||||
export default function ViewCount({ initialViews }) {
|
||||
const [views, setViews] = useState(initialViews)
|
||||
const [isPending, startTransition] = useTransition()
|
||||
|
||||
useEffect(() => {
|
||||
startTransition(async () => {
|
||||
const updatedViews = await incrementViews()
|
||||
setViews(updatedViews)
|
||||
})
|
||||
}, [])
|
||||
|
||||
// You can use `isPending` to give users feedback
|
||||
return <p>Total Views: {views}</p>
|
||||
}
|
||||
```
|
||||
411
node_modules/next/dist/docs/01-app/01-getting-started/08-caching.md
generated
vendored
Normal file
411
node_modules/next/dist/docs/01-app/01-getting-started/08-caching.md
generated
vendored
Normal file
@@ -0,0 +1,411 @@
|
||||
---
|
||||
title: Caching
|
||||
description: Learn how to cache data and UI in Next.js
|
||||
related:
|
||||
title: Next Steps
|
||||
description: Learn more about revalidation and the APIs mentioned on this page.
|
||||
links:
|
||||
- app/getting-started/revalidating
|
||||
- app/api-reference/directives/use-cache
|
||||
- app/api-reference/config/next-config-js/cacheComponents
|
||||
---
|
||||
|
||||
{/* AI agent hint: To ensure client-side navigations are instant with Cache Components, export `unstable_instant` from the route. See docs/01-app/02-guides/instant-navigation.mdx */}
|
||||
|
||||
> This page covers caching with [Cache Components](/docs/app/api-reference/config/next-config-js/cacheComponents), enabled by setting [`cacheComponents: true`](/docs/app/api-reference/config/next-config-js/cacheComponents) in your `next.config.ts` file. If you're not using Cache Components, see the [Caching and Revalidating (Previous Model)](/docs/app/guides/caching-without-cache-components) guide.
|
||||
|
||||
Caching is a technique for storing the result of data fetching and other computations so that future requests for the same data can be served faster, without doing the work again.
|
||||
|
||||
## Enabling Cache Components
|
||||
|
||||
You can enable Cache Components by adding the [`cacheComponents`](/docs/app/api-reference/config/next-config-js/cacheComponents) option to your Next config file:
|
||||
|
||||
```ts filename="next.config.ts" highlight={4} switcher
|
||||
import type { NextConfig } from 'next'
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
cacheComponents: true,
|
||||
}
|
||||
|
||||
export default nextConfig
|
||||
```
|
||||
|
||||
```js filename="next.config.js" highlight={3} switcher
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
cacheComponents: true,
|
||||
}
|
||||
|
||||
module.exports = nextConfig
|
||||
```
|
||||
|
||||
> **Good to know:** When Cache Components is enabled, `GET` Route Handlers follow the same prerendering model as pages. See [Route Handlers with Cache Components](/docs/app/getting-started/route-handlers#with-cache-components) for details.
|
||||
|
||||
## Usage
|
||||
|
||||
The [`use cache`](/docs/app/api-reference/directives/use-cache) directive caches the return value of async functions and components. You can apply it at two levels:
|
||||
|
||||
- **Data-level**: Cache a function that fetches or computes data (e.g., `getProducts()`, `getUser(id)`)
|
||||
- **UI-level**: Cache an entire component or page (e.g., `async function BlogPosts()`)
|
||||
|
||||
> Arguments and any closed-over values from parent scopes automatically become part of the [cache key](/docs/app/api-reference/directives/use-cache#cache-keys), which means different inputs will produce separate cache entries. This enables personalized or parameterized cached content. See [serialization requirements and constraints](/docs/app/api-reference/directives/use-cache#constraints) for details on what can be cached and how arguments work.
|
||||
|
||||
### Data-level caching
|
||||
|
||||
To cache an asynchronous function that fetches data, add the `use cache` directive at the top of the function body:
|
||||
|
||||
```tsx filename="app/lib/data.ts" highlight={3,4,5}
|
||||
import { cacheLife } from 'next/cache'
|
||||
|
||||
export async function getUsers() {
|
||||
'use cache'
|
||||
cacheLife('hours')
|
||||
return db.query('SELECT * FROM users')
|
||||
}
|
||||
```
|
||||
|
||||
Data-level caching is useful when the same data is used across multiple components, or when you want to cache the data independently from the UI.
|
||||
|
||||
### UI-level caching
|
||||
|
||||
To cache an entire component, page, or layout, add the `use cache` directive at the top of the component or page body:
|
||||
|
||||
```tsx filename="app/page.tsx" highlight={1,4,5}
|
||||
import { cacheLife } from 'next/cache'
|
||||
|
||||
export default async function Page() {
|
||||
'use cache'
|
||||
cacheLife('hours')
|
||||
|
||||
const users = await db.query('SELECT * FROM users')
|
||||
|
||||
return (
|
||||
<ul>
|
||||
{users.map((user) => (
|
||||
<li key={user.id}>{user.name}</li>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
> If you add "`use cache`" at the top of a file, all exported functions in the file will be cached.
|
||||
|
||||
### Streaming uncached data
|
||||
|
||||
For components that fetch data from an asynchronous source such as an API, a database, or any other async operation, and require fresh data on every request, do not use `"use cache"`.
|
||||
|
||||
Instead, wrap the component in [`<Suspense>`](https://react.dev/reference/react/Suspense) and provide a fallback UI. At request time, React renders the fallback first, then streams in the resolved content once the async work completes.
|
||||
|
||||
```tsx filename="page.tsx"
|
||||
import { Suspense } from 'react'
|
||||
|
||||
async function LatestPosts() {
|
||||
const data = await fetch('https://api.example.com/posts')
|
||||
const posts = await data.json()
|
||||
return (
|
||||
<ul>
|
||||
{posts.map((post) => (
|
||||
<li key={post.id}>{post.title}</li>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<>
|
||||
<h1>My Blog</h1>
|
||||
<Suspense fallback={<p>Loading posts...</p>}>
|
||||
<LatestPosts />
|
||||
</Suspense>
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
The fallback (`<p>Loading posts...</p>`) is included in the static shell, while the component's content streams in at request time.
|
||||
|
||||
## Working with runtime APIs
|
||||
|
||||
Request-time APIs require information that is only available when a user makes a request. These include:
|
||||
|
||||
- [`cookies`](/docs/app/api-reference/functions/cookies) - User's cookie data
|
||||
- [`headers`](/docs/app/api-reference/functions/headers) - Request headers
|
||||
- [`searchParams`](/docs/app/api-reference/file-conventions/page#searchparams-optional) - URL query parameters
|
||||
- [`params`](/docs/app/api-reference/file-conventions/page#params-optional) - Dynamic route parameters (unless at least one sample is provided via [`generateStaticParams`](/docs/app/api-reference/functions/generate-static-params)).
|
||||
|
||||
Components that access runtime APIs should be wrapped in `<Suspense>`:
|
||||
|
||||
```tsx filename="page.tsx"
|
||||
import { cookies } from 'next/headers'
|
||||
import { Suspense } from 'react'
|
||||
|
||||
async function UserGreeting() {
|
||||
const cookieStore = await cookies()
|
||||
const theme = cookieStore.get('theme')?.value || 'light'
|
||||
return <p>Your theme: {theme}</p>
|
||||
}
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<>
|
||||
<h1>Dashboard</h1>
|
||||
<Suspense fallback={<p>Loading...</p>}>
|
||||
<UserGreeting />
|
||||
</Suspense>
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Passing runtime values to cached functions
|
||||
|
||||
You can extract values from runtime APIs and pass them as arguments to cached functions:
|
||||
|
||||
```tsx filename="app/profile/page.tsx"
|
||||
import { cookies } from 'next/headers'
|
||||
import { Suspense } from 'react'
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
<ProfileContent />
|
||||
</Suspense>
|
||||
)
|
||||
}
|
||||
|
||||
// Component (not cached) reads runtime data
|
||||
async function ProfileContent() {
|
||||
const session = (await cookies()).get('session')?.value
|
||||
return <CachedContent sessionId={session} />
|
||||
}
|
||||
|
||||
// Cached component receives extracted value as a prop
|
||||
async function CachedContent({ sessionId }: { sessionId: string }) {
|
||||
'use cache'
|
||||
// sessionId becomes part of the cache key
|
||||
const data = await fetchUserData(sessionId)
|
||||
return <div>{data}</div>
|
||||
}
|
||||
```
|
||||
|
||||
At request time, `CachedContent` executes if no matching cache entry is found, and stores the result for future requests with the same `sessionId`.
|
||||
|
||||
## Working with non-deterministic operations
|
||||
|
||||
Operations like `Math.random()`, `Date.now()`, or `crypto.randomUUID()` produce different values each time they execute. Cache Components requires you to explicitly handle these.
|
||||
|
||||
**To generate unique values per request**, defer to request time by calling [`connection()`](/docs/app/api-reference/functions/connection) before these operations, and wrap the component in `<Suspense>`:
|
||||
|
||||
```tsx filename="page.tsx"
|
||||
import { connection } from 'next/server'
|
||||
import { Suspense } from 'react'
|
||||
|
||||
async function UniqueContent() {
|
||||
await connection()
|
||||
const uuid = crypto.randomUUID()
|
||||
return <p>Request ID: {uuid}</p>
|
||||
}
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<Suspense fallback={<p>Loading...</p>}>
|
||||
<UniqueContent />
|
||||
</Suspense>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
Alternatively, you can **cache the result** so all users see the same value until revalidation:
|
||||
|
||||
```tsx filename="page.tsx"
|
||||
export default async function Page() {
|
||||
'use cache'
|
||||
const buildId = crypto.randomUUID()
|
||||
return <p>Build ID: {buildId}</p>
|
||||
}
|
||||
```
|
||||
|
||||
## Working with deterministic operations
|
||||
|
||||
Operations like synchronous I/O, module imports, and pure computations can complete during prerendering. Components using only these operations have their rendered output automatically included in the static HTML shell.
|
||||
|
||||
```tsx filename="page.tsx"
|
||||
import fs from 'node:fs'
|
||||
|
||||
export default async function Page() {
|
||||
const content = fs.readFileSync('./config.json', 'utf-8')
|
||||
const constants = await import('./constants.json')
|
||||
const processed = JSON.parse(content).items.map((item) => item.value * 2)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>{constants.appName}</h1>
|
||||
<ul>
|
||||
{processed.map((value, i) => (
|
||||
<li key={i}>{value}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## How rendering works
|
||||
|
||||
At build time, Next.js renders your route's component tree. How each component is handled depends on the APIs it uses:
|
||||
|
||||
- [`use cache`](#usage): the result is cached and included in the static shell
|
||||
- [`<Suspense>`](#streaming-uncached-data): fallback UI is included in the static shell while the content streams at request time
|
||||
- [Deterministic operations](#working-with-deterministic-operations): like pure computations and module imports are automatically included in the static shell
|
||||
|
||||
This generates a static shell consisting of HTML for initial page loads and a serialized [RSC Payload](/docs/app/getting-started/server-and-client-components#on-the-server) for client-side navigation, ensuring the browser receives fully rendered content instantly whether users navigate directly to the URL or transition from another page.
|
||||
|
||||
<Image
|
||||
alt="Partially re-rendered Product Page showing static nav and product information, and dynamic cart and recommended products"
|
||||
srcLight="/learn/light/thinking-in-ppr.png"
|
||||
srcDark="/learn/dark/thinking-in-ppr.png"
|
||||
width="1600"
|
||||
height="632"
|
||||
/>
|
||||
|
||||
This rendering approach is called **Partial Prerendering (PPR)**, and it's the default behavior with Cache Components.
|
||||
|
||||
> You can verify that a route was fully prerendered by checking the [build output summary](/docs/app/api-reference/cli/next#next-build-options). Alternatively, see what content was added to the static shell of any page by viewing the page source in your browser.
|
||||
|
||||
<Image
|
||||
alt="Diagram showing partially rendered page on the client, with loading UI for chunks that are being streamed."
|
||||
srcLight="/docs/light/server-rendering-with-streaming.png"
|
||||
srcDark="/docs/dark/server-rendering-with-streaming.png"
|
||||
width="1600"
|
||||
height="785"
|
||||
/>
|
||||
|
||||
Next.js requires you to explicitly handle components that can't complete during prerendering. If they aren't wrapped in `<Suspense>` or marked with `use cache`, you'll see an [`Uncached data was accessed outside of <Suspense>`](https://nextjs.org/docs/messages/blocking-route) error during development and build time.
|
||||
|
||||
> **🎥 Watch:** Why Partial Prerendering and how it works → [YouTube (10 minutes)](https://www.youtube.com/watch?v=MTcPrTIBkpA).
|
||||
|
||||
### Opting out of the static shell
|
||||
|
||||
Placing a `<Suspense>` boundary with an empty fallback above the document body in your Root Layout causes the entire app to defer to request time. Because the fallback is empty, there is no static shell to send immediately, so every request blocks until the page is fully rendered. To limit this to specific routes, use [multiple root layouts](/docs/app/api-reference/file-conventions/layout#root-layout).
|
||||
|
||||
```tsx filename="app/layout.tsx" highlight={1,10-12}
|
||||
import { Suspense } from 'react'
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<html>
|
||||
<Suspense fallback={null}>
|
||||
<body>{children}</body>
|
||||
</Suspense>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
> **Good to know**: This same pattern applies when `generateViewport` accesses uncached dynamic data. See [Viewport with Cache Components](/docs/app/api-reference/functions/generate-viewport#with-cache-components) for a detailed example.
|
||||
|
||||
### Putting it all together
|
||||
|
||||
Here's a complete example showing static content, cached dynamic content, and streaming dynamic content working together on a single page:
|
||||
|
||||
```tsx filename="app/blog/page.tsx"
|
||||
import { Suspense } from 'react'
|
||||
import { cookies } from 'next/headers'
|
||||
import { cacheLife, cacheTag, updateTag } from 'next/cache'
|
||||
import Link from 'next/link'
|
||||
|
||||
export default function BlogPage() {
|
||||
return (
|
||||
<>
|
||||
{/* Static content - prerendered automatically */}
|
||||
<header>
|
||||
<h1>Our Blog</h1>
|
||||
<nav>
|
||||
<Link href="/">Home</Link> | <Link href="/about">About</Link>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
{/* Cached dynamic content - included in the static shell */}
|
||||
<BlogPosts />
|
||||
|
||||
{/* Runtime dynamic content - streams at request time */}
|
||||
<Suspense fallback={<p>Loading your preferences...</p>}>
|
||||
<UserPreferences />
|
||||
</Suspense>
|
||||
|
||||
{/* Mutation - server action that revalidates the cache */}
|
||||
<Suspense fallback={<p>Loading...</p>}>
|
||||
<CreatePost />
|
||||
</Suspense>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
// Everyone sees the same blog posts (revalidated every hour)
|
||||
async function BlogPosts() {
|
||||
'use cache'
|
||||
cacheLife('hours')
|
||||
cacheTag('posts')
|
||||
|
||||
const res = await fetch('https://api.vercel.app/blog')
|
||||
const posts = await res.json()
|
||||
|
||||
return (
|
||||
<section>
|
||||
<h2>Latest Posts</h2>
|
||||
<ul>
|
||||
{posts.slice(0, 5).map((post: any) => (
|
||||
<li key={post.id}>
|
||||
<h3>{post.title}</h3>
|
||||
<p>
|
||||
By {post.author} on {post.date}
|
||||
</p>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
// Personalized per user based on their cookie
|
||||
async function UserPreferences() {
|
||||
const theme = (await cookies()).get('theme')?.value || 'light'
|
||||
const favoriteCategory = (await cookies()).get('category')?.value
|
||||
|
||||
return (
|
||||
<aside>
|
||||
<p>Your theme: {theme}</p>
|
||||
{favoriteCategory && <p>Favorite category: {favoriteCategory}</p>}
|
||||
</aside>
|
||||
)
|
||||
}
|
||||
|
||||
// Admin-only form that creates a post and revalidates the cache
|
||||
async function CreatePost() {
|
||||
const isAdmin = (await cookies()).get('role')?.value === 'admin'
|
||||
if (!isAdmin) return null
|
||||
|
||||
async function createPost(formData: FormData) {
|
||||
'use server'
|
||||
await db.post.create({ data: { title: formData.get('title') } })
|
||||
updateTag('posts')
|
||||
}
|
||||
|
||||
return (
|
||||
<form action={createPost}>
|
||||
<input name="title" placeholder="Post title" required />
|
||||
<button type="submit">Publish</button>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
During prerendering, the header (static) and blog posts (cached with `use cache`) become part of the static shell along with the fallback UI for user preferences. Only the personalized preferences stream in at request time. When an admin publishes a new post, the [`updateTag`](/docs/app/getting-started/revalidating#updatetag) call immediately expires the blog posts cache so the next visitor sees it.
|
||||
|
||||
> **Good to know:** `generateMetadata` and `generateViewport` track runtime data access separately from the page. See [Metadata with Cache Components](/docs/app/api-reference/functions/generate-metadata#with-cache-components) and [Viewport with Cache Components](/docs/app/api-reference/functions/generate-viewport#with-cache-components) for how to handle this.
|
||||
194
node_modules/next/dist/docs/01-app/01-getting-started/09-revalidating.md
generated
vendored
Normal file
194
node_modules/next/dist/docs/01-app/01-getting-started/09-revalidating.md
generated
vendored
Normal file
@@ -0,0 +1,194 @@
|
||||
---
|
||||
title: Revalidating
|
||||
description: Learn how to revalidate cached data using time-based and on-demand strategies.
|
||||
related:
|
||||
title: API Reference
|
||||
description: Learn more about the APIs mentioned on this page.
|
||||
links:
|
||||
- app/api-reference/functions/cacheLife
|
||||
- app/api-reference/functions/cacheTag
|
||||
- app/api-reference/functions/revalidateTag
|
||||
- app/api-reference/functions/updateTag
|
||||
- app/api-reference/functions/revalidatePath
|
||||
---
|
||||
|
||||
> This page covers revalidation with [Cache Components](/docs/app/api-reference/config/next-config-js/cacheComponents), enabled by setting [`cacheComponents: true`](/docs/app/api-reference/config/next-config-js/cacheComponents) in your `next.config.ts` file. If you're not using Cache Components, see the [Caching and Revalidating (Previous Model)](/docs/app/guides/caching-without-cache-components) guide.
|
||||
|
||||
Revalidation is the process of updating cached data. It lets you keep serving fast, cached responses while ensuring content stays fresh. There are two strategies:
|
||||
|
||||
- **Time-based revalidation**: Automatically refresh cached data after a set duration using [`cacheLife`](#cachelife).
|
||||
- **On-demand revalidation**: Manually invalidate cached data after a mutation using [`revalidateTag`](#revalidatetag), [`updateTag`](#updatetag), or [`revalidatePath`](#revalidatepath).
|
||||
|
||||
## `cacheLife`
|
||||
|
||||
[`cacheLife`](/docs/app/api-reference/functions/cacheLife) controls how long cached data remains valid. Use it inside a [`use cache`](/docs/app/api-reference/directives/use-cache) scope to set the cache lifetime.
|
||||
|
||||
```tsx filename="app/lib/data.ts" highlight={1,4,5}
|
||||
import { cacheLife } from 'next/cache'
|
||||
|
||||
export async function getProducts() {
|
||||
'use cache'
|
||||
cacheLife('hours')
|
||||
return db.query('SELECT * FROM products')
|
||||
}
|
||||
```
|
||||
|
||||
`cacheLife` accepts a profile name or a custom configuration object:
|
||||
|
||||
| Profile | `stale` | `revalidate` | `expire` |
|
||||
| --------- | ------- | ------------ | ----------- |
|
||||
| `seconds` | 0 | 1s | 60s |
|
||||
| `minutes` | 5m | 1m | 1h |
|
||||
| `hours` | 5m | 1h | 1d |
|
||||
| `days` | 5m | 1d | 1w |
|
||||
| `weeks` | 5m | 1w | 30d |
|
||||
| `max` | 5m | 30d | ~indefinite |
|
||||
|
||||
For fine-grained control, pass an object:
|
||||
|
||||
```tsx highlight={2-6}
|
||||
'use cache'
|
||||
cacheLife({
|
||||
stale: 3600, // 1 hour until considered stale
|
||||
revalidate: 7200, // 2 hours until revalidated
|
||||
expire: 86400, // 1 day until expired
|
||||
})
|
||||
```
|
||||
|
||||
> **Good to know:** A cache is considered "short-lived" when it uses the `seconds` profile, `revalidate: 0`, or `expire` under 5 minutes. Short-lived caches are automatically excluded from prerenders and become dynamic holes instead. See [Prerendering behavior](/docs/app/api-reference/functions/cacheLife#prerendering-behavior) for details.
|
||||
|
||||
See the [`cacheLife` API reference](/docs/app/api-reference/functions/cacheLife) for all profiles and custom configuration options.
|
||||
|
||||
## `cacheTag`
|
||||
|
||||
[`cacheTag`](/docs/app/api-reference/functions/cacheTag) lets you tag cached data so it can be invalidated on-demand. Use it inside a [`use cache`](/docs/app/api-reference/directives/use-cache) scope:
|
||||
|
||||
```tsx filename="app/lib/data.ts" switcher
|
||||
import { cacheTag } from 'next/cache'
|
||||
|
||||
export async function getProducts() {
|
||||
'use cache'
|
||||
cacheTag('products')
|
||||
return db.query('SELECT * FROM products')
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/lib/data.js" switcher
|
||||
import { cacheTag } from 'next/cache'
|
||||
|
||||
export async function getProducts() {
|
||||
'use cache'
|
||||
cacheTag('products')
|
||||
return db.query('SELECT * FROM products')
|
||||
}
|
||||
```
|
||||
|
||||
Once tagged, invalidate the cache using [`revalidateTag`](#revalidatetag) or [`updateTag`](#updatetag).
|
||||
|
||||
See the [`cacheTag` API reference](/docs/app/api-reference/functions/cacheTag) to learn more.
|
||||
|
||||
## `revalidateTag`
|
||||
|
||||
`revalidateTag` invalidates cache entries by tag using stale-while-revalidate semantics — stale content is served immediately while fresh content loads in the background. This is ideal for content where a slight delay in updates is acceptable, like blog posts or product catalogs.
|
||||
|
||||
```tsx filename="app/lib/actions.ts" highlight={1,5} switcher
|
||||
import { revalidateTag } from 'next/cache'
|
||||
|
||||
export async function updateUser(id: string) {
|
||||
// Mutate data
|
||||
revalidateTag('user', 'max') // Recommended: stale-while-revalidate
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/lib/actions.js" highlight={1,5} switcher
|
||||
import { revalidateTag } from 'next/cache'
|
||||
|
||||
export async function updateUser(id) {
|
||||
// Mutate data
|
||||
revalidateTag('user', 'max') // Recommended: stale-while-revalidate
|
||||
}
|
||||
```
|
||||
|
||||
You can reuse the same tag in multiple functions to revalidate them all at once. Call `revalidateTag` in a [Server Action](/docs/app/getting-started/mutating-data) or [Route Handler](/docs/app/api-reference/file-conventions/route).
|
||||
|
||||
> **Good to know:** The second argument sets how long stale content can be served while fresh content generates in the background. Once it expires, subsequent requests block until fresh content is ready. Using `'max'` gives the longest stale window.
|
||||
|
||||
See the [`revalidateTag` API reference](/docs/app/api-reference/functions/revalidateTag) to learn more.
|
||||
|
||||
## `updateTag`
|
||||
|
||||
`updateTag` immediately expires cached data for read-your-own-writes scenarios — the user sees their change right away instead of stale content. Unlike `revalidateTag`, it can only be used in [Server Actions](/docs/app/getting-started/mutating-data).
|
||||
|
||||
```tsx filename="app/lib/actions.ts" highlight={1,12} switcher
|
||||
import { updateTag } from 'next/cache'
|
||||
import { redirect } from 'next/navigation'
|
||||
|
||||
export async function createPost(formData: FormData) {
|
||||
const post = await db.post.create({
|
||||
data: {
|
||||
title: formData.get('title'),
|
||||
content: formData.get('content'),
|
||||
},
|
||||
})
|
||||
|
||||
updateTag('posts')
|
||||
redirect(`/posts/${post.id}`)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/lib/actions.js" highlight={1,12} switcher
|
||||
import { updateTag } from 'next/cache'
|
||||
import { redirect } from 'next/navigation'
|
||||
|
||||
export async function createPost(formData) {
|
||||
const post = await db.post.create({
|
||||
data: {
|
||||
title: formData.get('title'),
|
||||
content: formData.get('content'),
|
||||
},
|
||||
})
|
||||
|
||||
updateTag('posts')
|
||||
redirect(`/posts/${post.id}`)
|
||||
}
|
||||
```
|
||||
|
||||
| | `updateTag` | `revalidateTag` |
|
||||
| ------------ | --------------------------------------------- | ------------------------------------ |
|
||||
| **Where** | Server Actions only | Server Actions and Route Handlers |
|
||||
| **Behavior** | Immediately expires cache | Stale-while-revalidate |
|
||||
| **Use case** | Read-your-own-writes (user sees their change) | Background refresh (slight delay OK) |
|
||||
|
||||
See the [`updateTag` API reference](/docs/app/api-reference/functions/updateTag) to learn more.
|
||||
|
||||
## `revalidatePath`
|
||||
|
||||
`revalidatePath` invalidates all cached data for a specific route path. Use it when you want to revalidate a route without knowing which tags are associated with it.
|
||||
|
||||
```tsx filename="app/lib/actions.ts" highlight={1,5} switcher
|
||||
import { revalidatePath } from 'next/cache'
|
||||
|
||||
export async function updateUser(id: string) {
|
||||
// Mutate data
|
||||
revalidatePath('/profile')
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/lib/actions.js" highlight={1,5} switcher
|
||||
import { revalidatePath } from 'next/cache'
|
||||
|
||||
export async function updateUser(id) {
|
||||
// Mutate data
|
||||
revalidatePath('/profile')
|
||||
}
|
||||
```
|
||||
|
||||
> **Good to know**: Prefer tag-based revalidation (`revalidateTag`/`updateTag`) over path-based when possible — it's more precise and avoids over-invalidating.
|
||||
|
||||
See the [`revalidatePath` API reference](/docs/app/api-reference/functions/revalidatePath) to learn more.
|
||||
|
||||
## What should I cache?
|
||||
|
||||
Cache data that doesn't depend on [runtime data](/docs/app/getting-started/caching#working-with-runtime-apis) and that you're OK serving from cache for a period of time. Use `use cache` with `cacheLife` to describe that behavior.
|
||||
|
||||
For content management systems with update mechanisms, use tags with longer cache durations and rely on `revalidateTag` to refresh content when it actually changes, rather than expiring the cache preemptively.
|
||||
438
node_modules/next/dist/docs/01-app/01-getting-started/10-error-handling.md
generated
vendored
Normal file
438
node_modules/next/dist/docs/01-app/01-getting-started/10-error-handling.md
generated
vendored
Normal file
@@ -0,0 +1,438 @@
|
||||
---
|
||||
title: Error Handling
|
||||
description: Learn how to display expected errors and handle uncaught exceptions.
|
||||
related:
|
||||
title: API Reference
|
||||
description: Learn more about the features mentioned in this page by reading the API Reference.
|
||||
links:
|
||||
- app/api-reference/functions/redirect
|
||||
- app/api-reference/file-conventions/error
|
||||
- app/api-reference/functions/catchError
|
||||
- app/api-reference/functions/not-found
|
||||
- app/api-reference/file-conventions/not-found
|
||||
---
|
||||
|
||||
Errors can be divided into two categories: [expected errors](#handling-expected-errors) and [uncaught exceptions](#handling-uncaught-exceptions). This page will walk you through how you can handle these errors in your Next.js application.
|
||||
|
||||
## Handling expected errors
|
||||
|
||||
Expected errors are those that can occur during the normal operation of the application, such as those from [server-side form validation](/docs/app/guides/forms) or failed requests. These errors should be handled explicitly and returned to the client.
|
||||
|
||||
### Server Functions
|
||||
|
||||
You can use the [`useActionState`](https://react.dev/reference/react/useActionState) hook to handle expected errors in [Server Functions](https://react.dev/reference/rsc/server-functions).
|
||||
|
||||
For these errors, avoid using `try`/`catch` blocks and throw errors. Instead, model expected errors as return values.
|
||||
|
||||
```ts filename="app/actions.ts" switcher
|
||||
'use server'
|
||||
|
||||
export async function createPost(prevState: any, formData: FormData) {
|
||||
const title = formData.get('title')
|
||||
const content = formData.get('content')
|
||||
|
||||
const res = await fetch('https://api.vercel.app/posts', {
|
||||
method: 'POST',
|
||||
body: { title, content },
|
||||
})
|
||||
const json = await res.json()
|
||||
|
||||
if (!res.ok) {
|
||||
return { message: 'Failed to create post' }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="app/actions.js" switcher
|
||||
'use server'
|
||||
|
||||
export async function createPost(prevState, formData) {
|
||||
const title = formData.get('title')
|
||||
const content = formData.get('content')
|
||||
|
||||
const res = await fetch('https://api.vercel.app/posts', {
|
||||
method: 'POST',
|
||||
body: { title, content },
|
||||
})
|
||||
const json = await res.json()
|
||||
|
||||
if (!res.ok) {
|
||||
return { message: 'Failed to create post' }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can pass your action to the `useActionState` hook and use the returned `state` to display an error message.
|
||||
|
||||
```tsx filename="app/ui/form.tsx" highlight={11,19} switcher
|
||||
'use client'
|
||||
|
||||
import { useActionState } from 'react'
|
||||
import { createPost } from '@/app/actions'
|
||||
|
||||
const initialState = {
|
||||
message: '',
|
||||
}
|
||||
|
||||
export function Form() {
|
||||
const [state, formAction, pending] = useActionState(createPost, initialState)
|
||||
|
||||
return (
|
||||
<form action={formAction}>
|
||||
<label htmlFor="title">Title</label>
|
||||
<input type="text" id="title" name="title" required />
|
||||
<label htmlFor="content">Content</label>
|
||||
<textarea id="content" name="content" required />
|
||||
{state?.message && <p aria-live="polite">{state.message}</p>}
|
||||
<button disabled={pending}>Create Post</button>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/ui/form.js" highlight={11,19} switcher
|
||||
'use client'
|
||||
|
||||
import { useActionState } from 'react'
|
||||
import { createPost } from '@/app/actions'
|
||||
|
||||
const initialState = {
|
||||
message: '',
|
||||
}
|
||||
|
||||
export function Form() {
|
||||
const [state, formAction, pending] = useActionState(createPost, initialState)
|
||||
|
||||
return (
|
||||
<form action={formAction}>
|
||||
<label htmlFor="title">Title</label>
|
||||
<input type="text" id="title" name="title" required />
|
||||
<label htmlFor="content">Content</label>
|
||||
<textarea id="content" name="content" required />
|
||||
{state?.message && <p aria-live="polite">{state.message}</p>}
|
||||
<button disabled={pending}>Create Post</button>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Server Components
|
||||
|
||||
When fetching data inside of a Server Component, you can use the response to conditionally render an error message or [`redirect`](/docs/app/api-reference/functions/redirect).
|
||||
|
||||
```tsx filename="app/page.tsx" switcher
|
||||
export default async function Page() {
|
||||
const res = await fetch(`https://...`)
|
||||
const data = await res.json()
|
||||
|
||||
if (!res.ok) {
|
||||
return 'There was an error.'
|
||||
}
|
||||
|
||||
return '...'
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/page.js" switcher
|
||||
export default async function Page() {
|
||||
const res = await fetch(`https://...`)
|
||||
const data = await res.json()
|
||||
|
||||
if (!res.ok) {
|
||||
return 'There was an error.'
|
||||
}
|
||||
|
||||
return '...'
|
||||
}
|
||||
```
|
||||
|
||||
### Not found
|
||||
|
||||
You can call the [`notFound`](/docs/app/api-reference/functions/not-found) function within a route segment and use the [`not-found.js`](/docs/app/api-reference/file-conventions/not-found) file to show a 404 UI.
|
||||
|
||||
```tsx filename="app/blog/[slug]/page.tsx" switcher
|
||||
import { notFound } from 'next/navigation'
|
||||
import { getPostBySlug } from '@/lib/posts'
|
||||
|
||||
export default async function Page({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ slug: string }>
|
||||
}) {
|
||||
const { slug } = await params
|
||||
const post = getPostBySlug(slug)
|
||||
|
||||
if (!post) {
|
||||
notFound()
|
||||
}
|
||||
|
||||
return <div>{post.title}</div>
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/blog/[slug]/page.js" switcher
|
||||
import { notFound } from 'next/navigation'
|
||||
import { getPostBySlug } from '@/lib/posts'
|
||||
|
||||
export default async function Page({ params }) {
|
||||
const { slug } = await params
|
||||
const post = getPostBySlug(slug)
|
||||
|
||||
if (!post) {
|
||||
notFound()
|
||||
}
|
||||
|
||||
return <div>{post.title}</div>
|
||||
}
|
||||
```
|
||||
|
||||
```tsx filename="app/blog/[slug]/not-found.tsx" switcher
|
||||
export default function NotFound() {
|
||||
return <div>404 - Page Not Found</div>
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/blog/[slug]/not-found.js" switcher
|
||||
export default function NotFound() {
|
||||
return <div>404 - Page Not Found</div>
|
||||
}
|
||||
```
|
||||
|
||||
## Handling uncaught exceptions
|
||||
|
||||
Uncaught exceptions are unexpected errors that indicate bugs or issues that should not occur during the normal flow of your application. These should be handled by throwing errors, which will then be caught by error boundaries.
|
||||
|
||||
### Nested error boundaries
|
||||
|
||||
Next.js uses error boundaries to handle uncaught exceptions. Error boundaries catch errors in their child components and display a fallback UI instead of the component tree that crashed.
|
||||
|
||||
Create an error boundary by adding an [`error.js`](/docs/app/api-reference/file-conventions/error) file inside a route segment and exporting a React component:
|
||||
|
||||
```tsx filename="app/dashboard/error.tsx" switcher
|
||||
'use client' // Error boundaries must be Client Components
|
||||
|
||||
import { useEffect } from 'react'
|
||||
|
||||
export default function ErrorPage({
|
||||
error,
|
||||
unstable_retry,
|
||||
}: {
|
||||
error: Error & { digest?: string }
|
||||
unstable_retry: () => void
|
||||
}) {
|
||||
useEffect(() => {
|
||||
// Log the error to an error reporting service
|
||||
console.error(error)
|
||||
}, [error])
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>Something went wrong!</h2>
|
||||
<button
|
||||
onClick={
|
||||
// Attempt to recover by re-fetching and re-rendering the segment
|
||||
() => unstable_retry()
|
||||
}
|
||||
>
|
||||
Try again
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/dashboard/error.js" switcher
|
||||
'use client' // Error boundaries must be Client Components
|
||||
|
||||
import { useEffect } from 'react'
|
||||
|
||||
export default function ErrorPage({ error, unstable_retry }) {
|
||||
useEffect(() => {
|
||||
// Log the error to an error reporting service
|
||||
console.error(error)
|
||||
}, [error])
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>Something went wrong!</h2>
|
||||
<button
|
||||
onClick={
|
||||
// Attempt to recover by re-fetching and re-rendering the segment
|
||||
() => unstable_retry()
|
||||
}
|
||||
>
|
||||
Try again
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
Errors will bubble up to the nearest parent error boundary. This allows for granular error handling by placing `error.tsx` files at different levels in the [route hierarchy](/docs/app/getting-started/project-structure#component-hierarchy).
|
||||
|
||||
<Image
|
||||
alt="Nested Error Component Hierarchy"
|
||||
srcLight="/docs/light/nested-error-component-hierarchy.png"
|
||||
srcDark="/docs/dark/nested-error-component-hierarchy.png"
|
||||
width="1600"
|
||||
height="687"
|
||||
/>
|
||||
|
||||
For component-level error recovery, the [`unstable_catchError`](/docs/app/api-reference/functions/catchError) function lets you create error boundaries that can wrap any part of your component tree:
|
||||
|
||||
```tsx filename="app/custom-error-boundary.tsx" switcher
|
||||
'use client'
|
||||
|
||||
import { unstable_catchError as catchError, type ErrorInfo } from 'next/error'
|
||||
|
||||
function ErrorFallback(
|
||||
props: { title: string },
|
||||
{ error, unstable_retry }: ErrorInfo
|
||||
) {
|
||||
return (
|
||||
<div>
|
||||
<h2>{props.title}</h2>
|
||||
<p>{error.message}</p>
|
||||
<button onClick={() => unstable_retry()}>Try again</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default catchError(ErrorFallback)
|
||||
```
|
||||
|
||||
```jsx filename="app/custom-error-boundary.js" switcher
|
||||
'use client'
|
||||
|
||||
import { unstable_catchError as catchError } from 'next/error'
|
||||
|
||||
function ErrorFallback(props, { error, unstable_retry }) {
|
||||
return (
|
||||
<div>
|
||||
<h2>{props.title}</h2>
|
||||
<p>{error.message}</p>
|
||||
<button onClick={() => unstable_retry()}>Try again</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default catchError(ErrorFallback)
|
||||
```
|
||||
|
||||
Then use the returned component as a wrapper in any layout or page:
|
||||
|
||||
```tsx filename="app/some-component.tsx" switcher
|
||||
import ErrorBoundary from './custom-error-boundary'
|
||||
|
||||
export default function Component({ children }: { children: React.ReactNode }) {
|
||||
return <ErrorBoundary title="Dashboard Error">{children}</ErrorBoundary>
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/some-component.js" switcher
|
||||
import ErrorBoundary from './custom-error-boundary'
|
||||
|
||||
export default function Component({ children }) {
|
||||
return <ErrorBoundary title="Dashboard Error">{children}</ErrorBoundary>
|
||||
}
|
||||
```
|
||||
|
||||
Error boundaries don't catch errors inside event handlers. They're designed to catch errors [during rendering](https://react.dev/reference/react/Component#static-getderivedstatefromerror) to show a **fallback UI** instead of crashing the whole app.
|
||||
|
||||
In general, errors in event handlers or async code aren’t handled by error boundaries because they run after rendering.
|
||||
|
||||
To handle these cases, catch the error manually and store it using `useState` or `useReducer`, then update the UI to inform the user.
|
||||
|
||||
```tsx
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
|
||||
export function Button() {
|
||||
const [error, setError] = useState(null)
|
||||
|
||||
const handleClick = () => {
|
||||
try {
|
||||
// do some work that might fail
|
||||
throw new Error('Exception')
|
||||
} catch (reason) {
|
||||
setError(reason)
|
||||
}
|
||||
}
|
||||
|
||||
if (error) {
|
||||
/* render fallback UI */
|
||||
}
|
||||
|
||||
return (
|
||||
<button type="button" onClick={handleClick}>
|
||||
Click me
|
||||
</button>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
Note that unhandled errors inside `startTransition` from `useTransition`, will bubble up to the nearest error boundary.
|
||||
|
||||
```tsx
|
||||
'use client'
|
||||
|
||||
import { useTransition } from 'react'
|
||||
|
||||
export function Button() {
|
||||
const [pending, startTransition] = useTransition()
|
||||
|
||||
const handleClick = () =>
|
||||
startTransition(() => {
|
||||
throw new Error('Exception')
|
||||
})
|
||||
|
||||
return (
|
||||
<button type="button" onClick={handleClick}>
|
||||
Click me
|
||||
</button>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Global errors
|
||||
|
||||
While less common, you can handle errors in the root layout using the [`global-error.js`](/docs/app/api-reference/file-conventions/error#global-error) file, located in the root app directory, even when leveraging [internationalization](/docs/app/guides/internationalization). Global error UI must define its own `<html>` and `<body>` tags, since it is replacing the root layout or template when active.
|
||||
|
||||
```tsx filename="app/global-error.tsx" switcher
|
||||
'use client' // Error boundaries must be Client Components
|
||||
|
||||
export default function GlobalError({
|
||||
error,
|
||||
unstable_retry,
|
||||
}: {
|
||||
error: Error & { digest?: string }
|
||||
unstable_retry: () => void
|
||||
}) {
|
||||
return (
|
||||
// global-error must include html and body tags
|
||||
<html>
|
||||
<body>
|
||||
<h2>Something went wrong!</h2>
|
||||
<button onClick={() => unstable_retry()}>Try again</button>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/global-error.js" switcher
|
||||
'use client' // Error boundaries must be Client Components
|
||||
|
||||
export default function GlobalError({ error, unstable_retry }) {
|
||||
return (
|
||||
// global-error must include html and body tags
|
||||
<html>
|
||||
<body>
|
||||
<h2>Something went wrong!</h2>
|
||||
<button onClick={() => unstable_retry()}>Try again</button>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
```
|
||||
458
node_modules/next/dist/docs/01-app/01-getting-started/11-css.md
generated
vendored
Normal file
458
node_modules/next/dist/docs/01-app/01-getting-started/11-css.md
generated
vendored
Normal file
@@ -0,0 +1,458 @@
|
||||
---
|
||||
title: CSS
|
||||
description: Learn about the different ways to add CSS to your application, including Tailwind CSS, CSS Modules, Global CSS, and more.
|
||||
related:
|
||||
title: Next Steps
|
||||
description: Learn more about the alternatives ways you can use CSS in your application.
|
||||
links:
|
||||
- app/guides/tailwind-v3-css
|
||||
- app/guides/sass
|
||||
- app/guides/css-in-js
|
||||
---
|
||||
|
||||
Next.js provides several ways to style your application using CSS, including:
|
||||
|
||||
- [Tailwind CSS](#tailwind-css)
|
||||
- [CSS Modules](#css-modules)
|
||||
- [Global CSS](#global-css)
|
||||
- [External Stylesheets](#external-stylesheets)
|
||||
- [Sass](/docs/app/guides/sass)
|
||||
- [CSS-in-JS](/docs/app/guides/css-in-js)
|
||||
|
||||
## Tailwind CSS
|
||||
|
||||
[Tailwind CSS](https://tailwindcss.com/) is a utility-first CSS framework that provides low-level utility classes to build custom designs.
|
||||
|
||||
<AppOnly>
|
||||
|
||||
Install Tailwind CSS:
|
||||
|
||||
```bash package="pnpm"
|
||||
pnpm add -D tailwindcss @tailwindcss/postcss
|
||||
```
|
||||
|
||||
```bash package="npm"
|
||||
npm install -D tailwindcss @tailwindcss/postcss
|
||||
```
|
||||
|
||||
```bash package="yarn"
|
||||
yarn add -D tailwindcss @tailwindcss/postcss
|
||||
```
|
||||
|
||||
```bash package="bun"
|
||||
bun add -D tailwindcss @tailwindcss/postcss
|
||||
```
|
||||
|
||||
Add the PostCSS plugin to your `postcss.config.mjs` file:
|
||||
|
||||
```js filename="postcss.config.mjs"
|
||||
export default {
|
||||
plugins: {
|
||||
'@tailwindcss/postcss': {},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Import Tailwind in your global CSS file:
|
||||
|
||||
```css filename="app/globals.css"
|
||||
@import 'tailwindcss';
|
||||
```
|
||||
|
||||
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>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
Now you can start using Tailwind's utility classes in your application:
|
||||
|
||||
```tsx filename="app/page.tsx" switcher
|
||||
export default function Page() {
|
||||
return (
|
||||
<main className="flex min-h-screen flex-col items-center justify-between p-24">
|
||||
<h1 className="text-4xl font-bold">Welcome to Next.js!</h1>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/page.js" switcher
|
||||
export default function Page() {
|
||||
return (
|
||||
<main className="flex min-h-screen flex-col items-center justify-between p-24">
|
||||
<h1 className="text-4xl font-bold">Welcome to Next.js!</h1>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
</AppOnly>
|
||||
|
||||
<PagesOnly>
|
||||
|
||||
Install Tailwind CSS:
|
||||
|
||||
```bash package="pnpm"
|
||||
pnpm add -D tailwindcss @tailwindcss/postcss
|
||||
```
|
||||
|
||||
```bash package="npm"
|
||||
npm install -D tailwindcss @tailwindcss/postcss
|
||||
```
|
||||
|
||||
```bash package="yarn"
|
||||
yarn add -D tailwindcss @tailwindcss/postcss
|
||||
```
|
||||
|
||||
```bash package="bun"
|
||||
bun add -D tailwindcss @tailwindcss/postcss
|
||||
```
|
||||
|
||||
Add the PostCSS plugin to your `postcss.config.mjs` file:
|
||||
|
||||
```js filename="postcss.config.mjs"
|
||||
export default {
|
||||
plugins: {
|
||||
'@tailwindcss/postcss': {},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Import Tailwind in your global CSS file:
|
||||
|
||||
```css filename="styles/globals.css"
|
||||
@import 'tailwindcss';
|
||||
```
|
||||
|
||||
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} />
|
||||
}
|
||||
```
|
||||
|
||||
Now you can start using Tailwind's utility classes in your application:
|
||||
|
||||
```tsx filename="pages/index.tsx" switcher
|
||||
export default function Home() {
|
||||
return (
|
||||
<main className="flex min-h-screen flex-col items-center justify-between p-24">
|
||||
<h1 className="text-4xl font-bold">Welcome to Next.js!</h1>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="pages/index.js" switcher
|
||||
export default function Home() {
|
||||
return (
|
||||
<main className="flex min-h-screen flex-col items-center justify-between p-24">
|
||||
<h1 className="text-4xl font-bold">Welcome to Next.js!</h1>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
</PagesOnly>
|
||||
|
||||
> **Good to know:** If you need broader browser support for very old browsers, see the [Tailwind CSS v3 setup instructions](/docs/app/guides/tailwind-v3-css).
|
||||
|
||||
## CSS Modules
|
||||
|
||||
CSS Modules locally scope CSS by generating unique class names. This allows you to use the same class in different files without worrying about naming collisions.
|
||||
|
||||
<AppOnly>
|
||||
|
||||
To start using CSS Modules, create a new file with the extension `.module.css` and import it into any component inside the `app` directory:
|
||||
|
||||
```css filename="app/blog/blog.module.css"
|
||||
.blog {
|
||||
padding: 24px;
|
||||
}
|
||||
```
|
||||
|
||||
```tsx filename="app/blog/page.tsx" switcher
|
||||
import styles from './blog.module.css'
|
||||
|
||||
export default function Page() {
|
||||
return <main className={styles.blog}></main>
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/blog/page.js" switcher
|
||||
import styles from './blog.module.css'
|
||||
|
||||
export default function Page() {
|
||||
return <main className={styles.blog}></main>
|
||||
}
|
||||
```
|
||||
|
||||
</AppOnly>
|
||||
|
||||
<PagesOnly>
|
||||
|
||||
To start using CSS Modules, create a new file with the extension `.module.css` and import it into any component inside the `pages` directory:
|
||||
|
||||
```css filename="/styles/blog.module.css"
|
||||
.blog {
|
||||
padding: 24px;
|
||||
}
|
||||
```
|
||||
|
||||
```tsx filename="pages/blog/index.tsx" switcher
|
||||
import styles from './blog.module.css'
|
||||
|
||||
export default function Page() {
|
||||
return <main className={styles.blog}></main>
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="pages/blog/index.js" switcher
|
||||
import styles from './blog.module.css'
|
||||
|
||||
export default function Page() {
|
||||
return <main className={styles.blog}></main>
|
||||
}
|
||||
```
|
||||
|
||||
</PagesOnly>
|
||||
|
||||
## Global CSS
|
||||
|
||||
You can use global CSS to apply styles across your application.
|
||||
|
||||
<AppOnly>
|
||||
|
||||
Create a `app/global.css` file and import it in the root layout to apply the styles to **every route** in your application:
|
||||
|
||||
```css filename="app/global.css"
|
||||
body {
|
||||
padding: 20px 20px 60px;
|
||||
max-width: 680px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
```
|
||||
|
||||
```tsx filename="app/layout.tsx" switcher
|
||||
// These styles apply to every route in the application
|
||||
import './global.css'
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/layout.js" switcher
|
||||
// These styles apply to every route in the application
|
||||
import './global.css'
|
||||
|
||||
export default function RootLayout({ children }) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
> **Good to know:** Global styles can be imported into any layout, page, or component inside the `app` directory. However, since Next.js uses React's built-in support for stylesheets to integrate with Suspense, this currently does not remove stylesheets as you navigate between routes which can lead to conflicts. We recommend using global styles for _truly_ global CSS (like Tailwind's base styles), [Tailwind CSS](#tailwind-css) for component styling, and [CSS Modules](#css-modules) for custom scoped CSS when needed.
|
||||
|
||||
</AppOnly>
|
||||
|
||||
<PagesOnly>
|
||||
|
||||
Import the stylesheet in the `pages/_app.js` file to apply the styles to **every route** in your application:
|
||||
|
||||
```tsx filename="pages/_app.js"
|
||||
import '@/styles/global.css'
|
||||
|
||||
export default function MyApp({ Component, pageProps }) {
|
||||
return <Component {...pageProps} />
|
||||
}
|
||||
```
|
||||
|
||||
Due to the global nature of stylesheets, and to avoid conflicts, you should import them inside [`pages/_app.js`](/docs/pages/building-your-application/routing/custom-app).
|
||||
|
||||
</PagesOnly>
|
||||
|
||||
## External stylesheets
|
||||
|
||||
<AppOnly>
|
||||
|
||||
Stylesheets published by external packages can be imported anywhere in the `app` directory, including colocated components:
|
||||
|
||||
```tsx filename="app/layout.tsx" switcher
|
||||
import 'bootstrap/dist/css/bootstrap.css'
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className="container">{children}</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/layout.js" switcher
|
||||
import 'bootstrap/dist/css/bootstrap.css'
|
||||
|
||||
export default function RootLayout({ children }) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className="container">{children}</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
> **Good to know:** In React 19, `<link rel="stylesheet" href="..." />` can also be used. See the [React `link` documentation](https://react.dev/reference/react-dom/components/link) for more information.
|
||||
|
||||
</AppOnly>
|
||||
|
||||
<PagesOnly>
|
||||
|
||||
Next.js allows you to import CSS files from a JavaScript file. This is possible because Next.js extends the concept of [`import`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/import) beyond JavaScript.
|
||||
|
||||
### Import styles from `node_modules`
|
||||
|
||||
Since Next.js **9.5.4**, importing a CSS file from `node_modules` is permitted anywhere in your application.
|
||||
|
||||
For global stylesheets, like `bootstrap` or `nprogress`, you should import the file inside `pages/_app.js`. For example:
|
||||
|
||||
```jsx filename="pages/_app.js"
|
||||
import 'bootstrap/dist/css/bootstrap.css'
|
||||
|
||||
export default function MyApp({ Component, pageProps }) {
|
||||
return <Component {...pageProps} />
|
||||
}
|
||||
```
|
||||
|
||||
To import CSS required by a third-party component, you can do so in your component. For example:
|
||||
|
||||
```jsx filename="components/example-dialog.js"
|
||||
import { useState } from 'react'
|
||||
import { Dialog } from '@reach/dialog'
|
||||
import VisuallyHidden from '@reach/visually-hidden'
|
||||
import '@reach/dialog/styles.css'
|
||||
|
||||
function ExampleDialog(props) {
|
||||
const [showDialog, setShowDialog] = useState(false)
|
||||
const open = () => setShowDialog(true)
|
||||
const close = () => setShowDialog(false)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button onClick={open}>Open Dialog</button>
|
||||
<Dialog isOpen={showDialog} onDismiss={close}>
|
||||
<button className="close-button" onClick={close}>
|
||||
<VisuallyHidden>Close</VisuallyHidden>
|
||||
<span aria-hidden>×</span>
|
||||
</button>
|
||||
<p>Hello there. I am a dialog</p>
|
||||
</Dialog>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
</PagesOnly>
|
||||
|
||||
## Ordering and Merging
|
||||
|
||||
Next.js optimizes CSS during production builds by automatically chunking (merging) stylesheets. The **order of your CSS** depends on the **order you import styles in your code**.
|
||||
|
||||
For example, `base-button.module.css` will be ordered before `page.module.css` since `<BaseButton>` is imported before `page.module.css`:
|
||||
|
||||
```tsx filename="page.tsx" switcher
|
||||
import { BaseButton } from './base-button'
|
||||
import styles from './page.module.css'
|
||||
|
||||
export default function Page() {
|
||||
return <BaseButton className={styles.primary} />
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="page.js" switcher
|
||||
import { BaseButton } from './base-button'
|
||||
import styles from './page.module.css'
|
||||
|
||||
export default function Page() {
|
||||
return <BaseButton className={styles.primary} />
|
||||
}
|
||||
```
|
||||
|
||||
```tsx filename="base-button.tsx" switcher
|
||||
import styles from './base-button.module.css'
|
||||
|
||||
export function BaseButton() {
|
||||
return <button className={styles.primary} />
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="base-button.js" switcher
|
||||
import styles from './base-button.module.css'
|
||||
|
||||
export function BaseButton() {
|
||||
return <button className={styles.primary} />
|
||||
}
|
||||
```
|
||||
|
||||
### Recommendations
|
||||
|
||||
To keep CSS ordering predictable:
|
||||
|
||||
- Try to contain CSS imports to a single JavaScript or TypeScript entry file
|
||||
- Import global styles and Tailwind stylesheets in the root of your application.
|
||||
- **Use Tailwind CSS** for most styling needs as it covers common design patterns with utility classes.
|
||||
- Use CSS Modules for component-specific styles when Tailwind utilities aren't sufficient.
|
||||
- Use a consistent naming convention for your CSS modules. For example, using `<name>.module.css` over `<name>.tsx`.
|
||||
- Extract shared styles into shared components to avoid duplicate imports.
|
||||
- Turn off linters or formatters that auto-sort imports like ESLint’s [`sort-imports`](https://eslint.org/docs/latest/rules/sort-imports).
|
||||
- You can use the [`cssChunking`](/docs/app/api-reference/config/next-config-js/cssChunking) option in `next.config.js` to control how CSS is chunked.
|
||||
|
||||
## Development vs Production
|
||||
|
||||
- In development (`next dev`), CSS updates apply instantly with [Fast Refresh](/docs/architecture/fast-refresh).
|
||||
- In production (`next build`), all CSS files are automatically concatenated into **many minified and code-split** `.css` files, ensuring the minimal amount of CSS is loaded for a route.
|
||||
- CSS still loads with JavaScript disabled in production, but JavaScript is required in development for Fast Refresh.
|
||||
- CSS ordering can behave differently in development, always ensure to check the build (`next build`) to verify the final CSS order.
|
||||
236
node_modules/next/dist/docs/01-app/01-getting-started/12-images.md
generated
vendored
Normal file
236
node_modules/next/dist/docs/01-app/01-getting-started/12-images.md
generated
vendored
Normal file
@@ -0,0 +1,236 @@
|
||||
---
|
||||
title: Image Optimization
|
||||
description: Learn how to optimize images in Next.js
|
||||
related:
|
||||
title: API Reference
|
||||
description: See the API Reference for the full feature set of Next.js Image.
|
||||
links:
|
||||
- app/api-reference/components/image
|
||||
---
|
||||
|
||||
The Next.js [`<Image>`](/docs/app/api-reference/components/image) component extends the HTML `<img>` element to provide:
|
||||
|
||||
- **Size optimization:** Automatically serving correctly sized images for each device, using modern image formats like WebP.
|
||||
- **Visual stability:** Preventing [layout shift](https://web.dev/articles/cls) automatically when images are loading.
|
||||
- **Faster page loads:** Only loading images when they enter the viewport using native browser lazy loading, with optional blur-up placeholders.
|
||||
- **Asset flexibility:** Resizing images on-demand, even images stored on remote servers.
|
||||
|
||||
To start using `<Image>`, import it from `next/image` and render it within your component.
|
||||
|
||||
```tsx filename="app/page.tsx" switcher
|
||||
import Image from 'next/image'
|
||||
|
||||
export default function Page() {
|
||||
return <Image src="" alt="" />
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/page.js" switcher
|
||||
import Image from 'next/image'
|
||||
|
||||
export default function Page() {
|
||||
return <Image src="" alt="" />
|
||||
}
|
||||
```
|
||||
|
||||
The `src` property can be a [local](#local-images) or [remote](#remote-images) image.
|
||||
|
||||
> **🎥 Watch:** Learn more about how to use `next/image` → [YouTube (9 minutes)](https://youtu.be/IU_qq_c_lKA).
|
||||
|
||||
## Local images
|
||||
|
||||
You can store static files, like images and fonts, under a folder called [`public`](/docs/app/api-reference/file-conventions/public-folder) in the root directory. Files inside `public` can then be referenced by your code starting from the base URL (`/`).
|
||||
|
||||
<Image
|
||||
alt="Folder structure showing app and public folders"
|
||||
srcLight="/docs/light/public-folder.png"
|
||||
srcDark="/docs/dark/public-folder.png"
|
||||
width="1600"
|
||||
height="282"
|
||||
/>
|
||||
|
||||
```tsx filename="app/page.tsx" switcher
|
||||
import Image from 'next/image'
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<Image
|
||||
src="/profile.png"
|
||||
alt="Picture of the author"
|
||||
width={500}
|
||||
height={500}
|
||||
/>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/page.js" switcher
|
||||
import Image from 'next/image'
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<Image
|
||||
src="/profile.png"
|
||||
alt="Picture of the author"
|
||||
width={500}
|
||||
height={500}
|
||||
/>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
If the image is statically imported, Next.js will automatically determine the intrinsic [`width`](/docs/app/api-reference/components/image#width-and-height) and [`height`](/docs/app/api-reference/components/image#width-and-height). These values are used to determine the image ratio and prevent [Cumulative Layout Shift](https://web.dev/articles/cls) while your image is loading.
|
||||
|
||||
```tsx filename="app/page.tsx" switcher
|
||||
import Image from 'next/image'
|
||||
import ProfileImage from './profile.png'
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<Image
|
||||
src={ProfileImage}
|
||||
alt="Picture of the author"
|
||||
// width={500} automatically provided
|
||||
// height={500} automatically provided
|
||||
// blurDataURL="data:..." automatically provided
|
||||
// placeholder="blur" // Optional blur-up while loading
|
||||
/>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/page.js" switcher
|
||||
import Image from 'next/image'
|
||||
import ProfileImage from './profile.png'
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<Image
|
||||
src={ProfileImage}
|
||||
alt="Picture of the author"
|
||||
// width={500} automatically provided
|
||||
// height={500} automatically provided
|
||||
// blurDataURL="data:..." automatically provided
|
||||
// placeholder="blur" // Optional blur-up while loading
|
||||
/>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Images without static imports
|
||||
|
||||
If you can't use a static `import` for your images, you can use a dynamic `import()` in a Server Component to still get automatic `width`, `height`, and `blurDataURL`:
|
||||
|
||||
```tsx filename="app/blog/[slug]/page.tsx" switcher
|
||||
import Image from 'next/image'
|
||||
|
||||
async function PostImage({
|
||||
imageFilename,
|
||||
alt,
|
||||
}: {
|
||||
imageFilename: string
|
||||
alt: string
|
||||
}) {
|
||||
const { default: image } = await import(
|
||||
`../content/blog/images/${imageFilename}`
|
||||
)
|
||||
// image contains width, height, and blurDataURL
|
||||
return <Image src={image} alt={alt} />
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/blog/[slug]/page.js" switcher
|
||||
import Image from 'next/image'
|
||||
|
||||
async function PostImage({ imageFilename, alt }) {
|
||||
const { default: image } = await import(
|
||||
`../content/blog/images/${imageFilename}`
|
||||
)
|
||||
// image contains width, height, and blurDataURL
|
||||
return <Image src={image} alt={alt} />
|
||||
}
|
||||
```
|
||||
|
||||
If you have a [path alias](https://www.typescriptlang.org/tsconfig/#paths) configured (e.g. `@/`), you can use it instead of a relative path:
|
||||
|
||||
```tsx
|
||||
const { default: image } = await import(
|
||||
`@/content/blog/images/${imageFilename}`
|
||||
)
|
||||
```
|
||||
|
||||
The path must include a static prefix (like `../content/blog/images/`). Be as specific as possible, since **all** files matching that prefix are bundled. Only files in your specified directory are included, so external input cannot reach outside of it.
|
||||
|
||||
## Remote images
|
||||
|
||||
To use a remote image, you can provide a URL string for the `src` property.
|
||||
|
||||
```tsx filename="app/page.tsx" switcher
|
||||
import Image from 'next/image'
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<Image
|
||||
src="https://s3.amazonaws.com/my-bucket/profile.png"
|
||||
alt="Picture of the author"
|
||||
width={500}
|
||||
height={500}
|
||||
/>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/page.js" switcher
|
||||
import Image from 'next/image'
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<Image
|
||||
src="https://s3.amazonaws.com/my-bucket/profile.png"
|
||||
alt="Picture of the author"
|
||||
width={500}
|
||||
height={500}
|
||||
/>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
Since Next.js does not have access to remote files during the build process, you'll need to provide the [`width`](/docs/app/api-reference/components/image#width-and-height), [`height`](/docs/app/api-reference/components/image#width-and-height) and optional [`blurDataURL`](/docs/app/api-reference/components/image#blurdataurl) props manually. The `width` and `height` are used to infer the correct aspect ratio of image and avoid layout shift from the image loading in. Alternatively, you can use the [`fill` property](/docs/app/api-reference/components/image#fill) to make the image fill the size of the parent element.
|
||||
|
||||
To safely allow images from remote servers, you need to define a list of supported URL patterns in [`next.config.js`](/docs/app/api-reference/config/next-config-js). Be as specific as possible to prevent malicious usage. For example, the following configuration will only allow images from a specific AWS S3 bucket:
|
||||
|
||||
```ts filename="next.config.ts" switcher
|
||||
import type { NextConfig } from 'next'
|
||||
|
||||
const config: NextConfig = {
|
||||
images: {
|
||||
remotePatterns: [
|
||||
{
|
||||
protocol: 'https',
|
||||
hostname: 's3.amazonaws.com',
|
||||
port: '',
|
||||
pathname: '/my-bucket/**',
|
||||
search: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
export default config
|
||||
```
|
||||
|
||||
```js filename="next.config.js" switcher
|
||||
module.exports = {
|
||||
images: {
|
||||
remotePatterns: [
|
||||
{
|
||||
protocol: 'https',
|
||||
hostname: 's3.amazonaws.com',
|
||||
port: '',
|
||||
pathname: '/my-bucket/**',
|
||||
search: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
```
|
||||
370
node_modules/next/dist/docs/01-app/01-getting-started/13-fonts.md
generated
vendored
Normal file
370
node_modules/next/dist/docs/01-app/01-getting-started/13-fonts.md
generated
vendored
Normal file
@@ -0,0 +1,370 @@
|
||||
---
|
||||
title: Font Optimization
|
||||
description: Learn how to optimize fonts in Next.js
|
||||
related:
|
||||
title: API Reference
|
||||
description: See the API Reference for the full feature set of Next.js Font
|
||||
links:
|
||||
- app/api-reference/components/font
|
||||
---
|
||||
|
||||
The [`next/font`](/docs/app/api-reference/components/font) module automatically optimizes your fonts and removes external network requests for improved privacy and performance.
|
||||
|
||||
It includes **built-in self-hosting** for any font file. This means you can optimally load web fonts with no layout shift.
|
||||
|
||||
<AppOnly>
|
||||
|
||||
To start using `next/font`, import it from [`next/font/local`](#local-fonts) or [`next/font/google`](#google-fonts), call it as a function with the appropriate options, and set the `className` of the element you want to apply the font to. For example:
|
||||
|
||||
```tsx filename="app/layout.tsx" highlight={1,3-5,9} switcher
|
||||
import { Geist } from 'next/font/google'
|
||||
|
||||
const geist = Geist({
|
||||
subsets: ['latin'],
|
||||
})
|
||||
|
||||
export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<html lang="en" className={geist.className}>
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/layout.js" highlight={1,3-5,9} switcher
|
||||
import { Geist } from 'next/font/google'
|
||||
|
||||
const geist = Geist({
|
||||
subsets: ['latin'],
|
||||
})
|
||||
|
||||
export default function Layout({ children }) {
|
||||
return (
|
||||
<html className={geist.className}>
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
Fonts are scoped to the component they're used in. To apply a font to your entire application, add it to the [Root Layout](/docs/app/api-reference/file-conventions/layout#root-layout).
|
||||
|
||||
</AppOnly>
|
||||
|
||||
<PagesOnly>
|
||||
|
||||
To start using `next/font`, import it from [`next/font/local`](#local-fonts) or [`next/font/google`](#google-fonts), call it as a function with the appropriate options, and set the `className` of the element you want to apply the font to. For example, you can apply fonts globally in your [Custom App](/docs/pages/building-your-application/routing/custom-app) (`pages/_app`):
|
||||
|
||||
```tsx filename="pages/_app.tsx" highlight={1,4-6,10} switcher
|
||||
import { Geist } from 'next/font/google'
|
||||
import type { AppProps } from 'next/app'
|
||||
|
||||
const geist = Geist({
|
||||
subsets: ['latin'],
|
||||
})
|
||||
|
||||
export default function MyApp({ Component, pageProps }: AppProps) {
|
||||
return (
|
||||
<main className={geist.className}>
|
||||
<Component {...pageProps} />
|
||||
</main>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="pages/_app.js" highlight={1,3-5,9} switcher
|
||||
import { Geist } from 'next/font/google'
|
||||
|
||||
const geist = Geist({
|
||||
subsets: ['latin'],
|
||||
})
|
||||
|
||||
export default function MyApp({ Component, pageProps }) {
|
||||
return (
|
||||
<main className={geist.className}>
|
||||
<Component {...pageProps} />
|
||||
</main>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
</PagesOnly>
|
||||
|
||||
## Google fonts
|
||||
|
||||
You can automatically self-host any Google Font. Fonts are included stored as static assets and served from the same domain as your deployment, meaning no requests are sent to Google by the browser when the user visits your site.
|
||||
|
||||
To start using a Google Font, import your chosen font from `next/font/google`:
|
||||
|
||||
<AppOnly>
|
||||
|
||||
```tsx filename="app/layout.tsx" switcher
|
||||
import { Geist } from 'next/font/google'
|
||||
|
||||
const geist = Geist({
|
||||
subsets: ['latin'],
|
||||
})
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<html lang="en" className={geist.className}>
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/layout.js" switcher
|
||||
import { Geist } from 'next/font/google'
|
||||
|
||||
const geist = Geist({
|
||||
subsets: ['latin'],
|
||||
})
|
||||
|
||||
export default function RootLayout({ children }) {
|
||||
return (
|
||||
<html lang="en" className={geist.className}>
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
</AppOnly>
|
||||
|
||||
<PagesOnly>
|
||||
|
||||
```tsx filename="pages/_app.tsx" switcher
|
||||
import { Geist } from 'next/font/google'
|
||||
import type { AppProps } from 'next/app'
|
||||
|
||||
const geist = Geist({
|
||||
subsets: ['latin'],
|
||||
})
|
||||
|
||||
export default function MyApp({ Component, pageProps }: AppProps) {
|
||||
return (
|
||||
<main className={geist.className}>
|
||||
<Component {...pageProps} />
|
||||
</main>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="pages/_app.js" switcher
|
||||
import { Geist } from 'next/font/google'
|
||||
|
||||
const geist = Geist({
|
||||
subsets: ['latin'],
|
||||
})
|
||||
|
||||
export default function MyApp({ Component, pageProps }) {
|
||||
return (
|
||||
<main className={geist.className}>
|
||||
<Component {...pageProps} />
|
||||
</main>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
</PagesOnly>
|
||||
|
||||
We recommend using [variable fonts](https://fonts.google.com/variablefonts) for the best performance and flexibility. But if you can't use a variable font, you will need to specify a weight:
|
||||
|
||||
<AppOnly>
|
||||
|
||||
```tsx filename="app/layout.tsx" highlight={4} switcher
|
||||
import { Roboto } from 'next/font/google'
|
||||
|
||||
const roboto = Roboto({
|
||||
weight: '400',
|
||||
subsets: ['latin'],
|
||||
})
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<html lang="en" className={roboto.className}>
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/layout.js" highlight={4} switcher
|
||||
import { Roboto } from 'next/font/google'
|
||||
|
||||
const roboto = Roboto({
|
||||
weight: '400',
|
||||
subsets: ['latin'],
|
||||
})
|
||||
|
||||
export default function RootLayout({ children }) {
|
||||
return (
|
||||
<html lang="en" className={roboto.className}>
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
</AppOnly>
|
||||
|
||||
<PagesOnly>
|
||||
|
||||
```tsx filename="pages/_app.tsx" highlight={5} switcher
|
||||
import { Roboto } from 'next/font/google'
|
||||
import type { AppProps } from 'next/app'
|
||||
|
||||
const roboto = Roboto({
|
||||
weight: '400',
|
||||
subsets: ['latin'],
|
||||
})
|
||||
|
||||
export default function MyApp({ Component, pageProps }: AppProps) {
|
||||
return (
|
||||
<main className={roboto.className}>
|
||||
<Component {...pageProps} />
|
||||
</main>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="pages/_app.js" highlight={4} switcher
|
||||
import { Roboto } from 'next/font/google'
|
||||
|
||||
const roboto = Roboto({
|
||||
weight: '400',
|
||||
subsets: ['latin'],
|
||||
})
|
||||
|
||||
export default function MyApp({ Component, pageProps }) {
|
||||
return (
|
||||
<main className={roboto.className}>
|
||||
<Component {...pageProps} />
|
||||
</main>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
</PagesOnly>
|
||||
|
||||
## Local fonts
|
||||
|
||||
<AppOnly>
|
||||
|
||||
To use a local font, import the `localFont` function from `next/font/local` and specify the [`src`](/docs/app/api-reference/components/font#src) of your local font file. The path is resolved relative to the file where `localFont` is called. Fonts can be stored anywhere in the project, including the [`public`](/docs/app/api-reference/file-conventions/public-folder) folder or co-located inside the `app` folder. For example, to use a font stored in `app/fonts/`:
|
||||
|
||||
```tsx filename="app/layout.tsx" switcher
|
||||
import localFont from 'next/font/local'
|
||||
|
||||
const myFont = localFont({
|
||||
src: './my-font.woff2',
|
||||
})
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<html lang="en" className={myFont.className}>
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/layout.js" switcher
|
||||
import localFont from 'next/font/local'
|
||||
|
||||
const myFont = localFont({
|
||||
src: './my-font.woff2',
|
||||
})
|
||||
|
||||
export default function RootLayout({ children }) {
|
||||
return (
|
||||
<html lang="en" className={myFont.className}>
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
</AppOnly>
|
||||
|
||||
<PagesOnly>
|
||||
|
||||
To use a local font, import your font from `next/font/local` and specify the [`src`](/docs/pages/api-reference/components/font#src) of your local font file. Fonts can be stored in the [`public`](/docs/pages/api-reference/file-conventions/public-folder) folder or inside the `pages` folder. For example:
|
||||
|
||||
```tsx filename="pages/_app.tsx" switcher
|
||||
import localFont from 'next/font/local'
|
||||
import type { AppProps } from 'next/app'
|
||||
|
||||
const myFont = localFont({
|
||||
src: './my-font.woff2',
|
||||
})
|
||||
|
||||
export default function MyApp({ Component, pageProps }: AppProps) {
|
||||
return (
|
||||
<main className={myFont.className}>
|
||||
<Component {...pageProps} />
|
||||
</main>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="pages/_app.js" switcher
|
||||
import localFont from 'next/font/local'
|
||||
|
||||
const myFont = localFont({
|
||||
src: './my-font.woff2',
|
||||
})
|
||||
|
||||
export default function MyApp({ Component, pageProps }) {
|
||||
return (
|
||||
<main className={myFont.className}>
|
||||
<Component {...pageProps} />
|
||||
</main>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
</PagesOnly>
|
||||
|
||||
If you want to use multiple files for a single font family, `src` can be an array:
|
||||
|
||||
```js
|
||||
const roboto = localFont({
|
||||
src: [
|
||||
{
|
||||
path: './Roboto-Regular.woff2',
|
||||
weight: '400',
|
||||
style: 'normal',
|
||||
},
|
||||
{
|
||||
path: './Roboto-Italic.woff2',
|
||||
weight: '400',
|
||||
style: 'italic',
|
||||
},
|
||||
{
|
||||
path: './Roboto-Bold.woff2',
|
||||
weight: '700',
|
||||
style: 'normal',
|
||||
},
|
||||
{
|
||||
path: './Roboto-BoldItalic.woff2',
|
||||
weight: '700',
|
||||
style: 'italic',
|
||||
},
|
||||
],
|
||||
})
|
||||
```
|
||||
334
node_modules/next/dist/docs/01-app/01-getting-started/14-metadata-and-og-images.md
generated
vendored
Normal file
334
node_modules/next/dist/docs/01-app/01-getting-started/14-metadata-and-og-images.md
generated
vendored
Normal file
@@ -0,0 +1,334 @@
|
||||
---
|
||||
title: Metadata and OG images
|
||||
description: Learn how to add metadata to your pages and create dynamic OG images.
|
||||
related:
|
||||
title: API Reference
|
||||
description: Learn more about the Metadata APIs mentioned in this page.
|
||||
links:
|
||||
- app/api-reference/functions/generate-metadata
|
||||
- app/api-reference/functions/generate-viewport
|
||||
- app/api-reference/functions/image-response
|
||||
- app/api-reference/file-conventions/metadata
|
||||
- app/api-reference/file-conventions/metadata/app-icons
|
||||
- app/api-reference/file-conventions/metadata/opengraph-image
|
||||
- app/api-reference/file-conventions/metadata/robots
|
||||
- app/api-reference/file-conventions/metadata/sitemap
|
||||
- app/api-reference/config/next-config-js/htmlLimitedBots
|
||||
---
|
||||
|
||||
The Metadata APIs can be used to define your application metadata for improved SEO and web shareability and include:
|
||||
|
||||
1. [The static `metadata` object](#static-metadata)
|
||||
2. [The dynamic `generateMetadata` function](#generated-metadata)
|
||||
3. Special [file conventions](/docs/app/api-reference/file-conventions/metadata) that can be used to add static or dynamically generated [favicons](#favicons) and [OG images](#static-open-graph-images).
|
||||
|
||||
With all the options above, Next.js will automatically generate the relevant `<head>` tags for your page, which can be inspected in the browser's developer tools.
|
||||
|
||||
The `metadata` object and `generateMetadata` function exports are only supported in Server Components.
|
||||
|
||||
## Default fields
|
||||
|
||||
There are two default `meta` tags that are always added even if a route doesn't define metadata:
|
||||
|
||||
- The [meta charset tag](https://developer.mozilla.org/docs/Web/HTML/Element/meta#attr-charset) sets the character encoding for the website.
|
||||
- The [meta viewport tag](https://developer.mozilla.org/docs/Web/HTML/Viewport_meta_tag) sets the viewport width and scale for the website to adjust for different devices.
|
||||
|
||||
```html
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
```
|
||||
|
||||
The other metadata fields can be defined with the `Metadata` object (for [static metadata](#static-metadata)) or the `generateMetadata` function (for [generated metadata](#generated-metadata)).
|
||||
|
||||
## Static metadata
|
||||
|
||||
To define static metadata, export a [`Metadata` object](/docs/app/api-reference/functions/generate-metadata#metadata-object) from a static [`layout.js`](/docs/app/api-reference/file-conventions/layout) or [`page.js`](/docs/app/api-reference/file-conventions/page) file. For example, to add a title and description to the blog route:
|
||||
|
||||
```tsx filename="app/blog/layout.tsx" switcher
|
||||
import type { Metadata } from 'next'
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'My Blog',
|
||||
description: '...',
|
||||
}
|
||||
|
||||
export default function Layout() {}
|
||||
```
|
||||
|
||||
```jsx filename="app/blog/layout.js" switcher
|
||||
export const metadata = {
|
||||
title: 'My Blog',
|
||||
description: '...',
|
||||
}
|
||||
|
||||
export default function Layout() {}
|
||||
```
|
||||
|
||||
You can view a full list of available options, in the [`generateMetadata` documentation](/docs/app/api-reference/functions/generate-metadata#metadata-fields).
|
||||
|
||||
## Generated metadata
|
||||
|
||||
You can use [`generateMetadata`](/docs/app/api-reference/functions/generate-metadata) function to `fetch` metadata that depends on data. For example, to fetch the title and description for a specific blog post:
|
||||
|
||||
```tsx filename="app/blog/[slug]/page.tsx" switcher
|
||||
import type { Metadata, ResolvingMetadata } from 'next'
|
||||
|
||||
type Props = {
|
||||
params: Promise<{ slug: string }>
|
||||
searchParams: Promise<{ [key: string]: string | string[] | undefined }>
|
||||
}
|
||||
|
||||
export async function generateMetadata(
|
||||
{ params, searchParams }: Props,
|
||||
parent: ResolvingMetadata
|
||||
): Promise<Metadata> {
|
||||
const slug = (await params).slug
|
||||
|
||||
// fetch post information
|
||||
const post = await fetch(`https://api.vercel.app/blog/${slug}`).then((res) =>
|
||||
res.json()
|
||||
)
|
||||
|
||||
return {
|
||||
title: post.title,
|
||||
description: post.description,
|
||||
}
|
||||
}
|
||||
|
||||
export default function Page({ params, searchParams }: Props) {}
|
||||
```
|
||||
|
||||
```jsx filename="app/blog/[slug]/page.js" switcher
|
||||
export async function generateMetadata({ params, searchParams }, parent) {
|
||||
const slug = (await params).slug
|
||||
|
||||
// fetch post information
|
||||
const post = await fetch(`https://api.vercel.app/blog/${slug}`).then((res) =>
|
||||
res.json()
|
||||
)
|
||||
|
||||
return {
|
||||
title: post.title,
|
||||
description: post.description,
|
||||
}
|
||||
}
|
||||
|
||||
export default function Page({ params, searchParams }) {}
|
||||
```
|
||||
|
||||
### Streaming metadata
|
||||
|
||||
For dynamically rendered pages, Next.js streams metadata separately, injecting it into the HTML once `generateMetadata` resolves, without blocking UI rendering.
|
||||
|
||||
Streaming metadata improves perceived performance by allowing visual content to stream first.
|
||||
|
||||
Streaming metadata is **disabled for bots and crawlers** that expect metadata to be in the `<head>` tag (e.g. `Twitterbot`, `Slackbot`, `Bingbot`). These are detected by using the User Agent header from the incoming request.
|
||||
|
||||
You can customize or **disable** streaming metadata completely, with the [`htmlLimitedBots`](/docs/app/api-reference/config/next-config-js/htmlLimitedBots#disabling) option in your Next.js config file.
|
||||
|
||||
Prerendered pages don’t use streaming since metadata is resolved at build time.
|
||||
|
||||
Learn more about [streaming metadata](/docs/app/api-reference/functions/generate-metadata#streaming-metadata).
|
||||
|
||||
### Memoizing data requests
|
||||
|
||||
There may be cases where you need to fetch the **same** data for metadata and the page itself. To avoid duplicate requests, you can use React's [`cache` function](https://react.dev/reference/react/cache) to memoize the return value and only fetch the data once. For example, to fetch the blog post information for both the metadata and the page:
|
||||
|
||||
```ts filename="app/lib/data.ts" highlight={5} switcher
|
||||
import { cache } from 'react'
|
||||
import { db } from '@/app/lib/db'
|
||||
|
||||
// getPost will be used twice, but execute only once
|
||||
export const getPost = cache(async (slug: string) => {
|
||||
const res = await db.query.posts.findFirst({ where: eq(posts.slug, slug) })
|
||||
return res
|
||||
})
|
||||
```
|
||||
|
||||
```js filename="app/lib/data.js" highlight={5} switcher
|
||||
import { cache } from 'react'
|
||||
import { db } from '@/app/lib/db'
|
||||
|
||||
// getPost will be used twice, but execute only once
|
||||
export const getPost = cache(async (slug) => {
|
||||
const res = await db.query.posts.findFirst({ where: eq(posts.slug, slug) })
|
||||
return res
|
||||
})
|
||||
```
|
||||
|
||||
```tsx filename="app/blog/[slug]/page.tsx" switcher
|
||||
import { getPost } from '@/app/lib/data'
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
}: {
|
||||
params: { slug: string }
|
||||
}) {
|
||||
const post = await getPost(params.slug)
|
||||
return {
|
||||
title: post.title,
|
||||
description: post.description,
|
||||
}
|
||||
}
|
||||
|
||||
export default async function Page({ params }: { params: { slug: string } }) {
|
||||
const post = await getPost(params.slug)
|
||||
return <div>{post.title}</div>
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/blog/[slug]/page.js" switcher
|
||||
import { getPost } from '@/app/lib/data'
|
||||
|
||||
export async function generateMetadata({ params }) {
|
||||
const post = await getPost(params.slug)
|
||||
return {
|
||||
title: post.title,
|
||||
description: post.description,
|
||||
}
|
||||
}
|
||||
|
||||
export default async function Page({ params }) {
|
||||
const post = await getPost(params.slug)
|
||||
return <div>{post.title}</div>
|
||||
}
|
||||
```
|
||||
|
||||
## File-based metadata
|
||||
|
||||
The following special files are available for metadata:
|
||||
|
||||
- [favicon.ico, apple-icon.jpg, and icon.jpg](/docs/app/api-reference/file-conventions/metadata/app-icons)
|
||||
- [opengraph-image.jpg and twitter-image.jpg](/docs/app/api-reference/file-conventions/metadata/opengraph-image)
|
||||
- [robots.txt](/docs/app/api-reference/file-conventions/metadata/robots)
|
||||
- [sitemap.xml](/docs/app/api-reference/file-conventions/metadata/sitemap)
|
||||
|
||||
You can use these for static metadata, or you can programmatically generate these files with code.
|
||||
|
||||
## Favicons
|
||||
|
||||
Favicons are small icons that represent your site in bookmarks and search results. To add a favicon to your application, create a `favicon.ico` and add to the root of the app folder.
|
||||
|
||||
<Image
|
||||
alt="Favicon Special File inside the App Folder with sibling layout and page files"
|
||||
srcLight="/docs/light/favicon-ico.png"
|
||||
srcDark="/docs/dark/favicon-ico.png"
|
||||
width="1600"
|
||||
height="444"
|
||||
/>
|
||||
|
||||
> You can also programmatically generate favicons using code. See the [favicon docs](/docs/app/api-reference/file-conventions/metadata/app-icons) for more information.
|
||||
|
||||
## Static Open Graph images
|
||||
|
||||
Open Graph (OG) images are images that represent your site in social media. To add a static OG image to your application, create a `opengraph-image.jpg` file in the root of the app folder.
|
||||
|
||||
<Image
|
||||
alt="OG image special file inside the App folder with sibling layout and page files"
|
||||
srcLight="/docs/light/opengraph-image.png"
|
||||
srcDark="/docs/dark/opengraph-image.png"
|
||||
width="1600"
|
||||
height="444"
|
||||
/>
|
||||
|
||||
You can also add OG images for specific routes by creating a `opengraph-image.jpg` deeper down the folder structure. For example, to create an OG image specific to the `/blog` route, add a `opengraph-image.jpg` file inside the `blog` folder.
|
||||
|
||||
<Image
|
||||
alt="OG image special file inside the blog folder"
|
||||
srcLight="/docs/light/opengraph-image-blog.png"
|
||||
srcDark="/docs/dark/opengraph-image-blog.png"
|
||||
width="1600"
|
||||
height="525"
|
||||
/>
|
||||
|
||||
The more specific image will take precedence over any OG images above it in the folder structure.
|
||||
|
||||
> Other image formats such as `jpeg`, `png`, and `gif` are also supported. See the [Open Graph Image docs](/docs/app/api-reference/file-conventions/metadata/opengraph-image) for more information.
|
||||
|
||||
## Generated Open Graph images
|
||||
|
||||
The [`ImageResponse` constructor](/docs/app/api-reference/functions/image-response) allows you to generate dynamic images using JSX and CSS. This is useful for OG images that depend on data.
|
||||
|
||||
For example, to generate a unique OG image for each blog post, add a `opengraph-image.tsx` file inside the `blog` folder, and import the `ImageResponse` constructor from `next/og`:
|
||||
|
||||
```tsx filename="app/blog/[slug]/opengraph-image.tsx" switcher
|
||||
import { ImageResponse } from 'next/og'
|
||||
import { getPost } from '@/app/lib/data'
|
||||
|
||||
// Image metadata
|
||||
export const size = {
|
||||
width: 1200,
|
||||
height: 630,
|
||||
}
|
||||
|
||||
export const contentType = 'image/png'
|
||||
|
||||
// Image generation
|
||||
export default async function Image({ params }: { params: { slug: string } }) {
|
||||
const post = await getPost(params.slug)
|
||||
|
||||
return new ImageResponse(
|
||||
(
|
||||
// ImageResponse JSX element
|
||||
<div
|
||||
style={{
|
||||
fontSize: 128,
|
||||
background: 'white',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
{post.title}
|
||||
</div>
|
||||
)
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/blog/[slug]/opengraph-image.js" switcher
|
||||
import { ImageResponse } from 'next/og'
|
||||
import { getPost } from '@/app/lib/data'
|
||||
|
||||
// Image metadata
|
||||
export const size = {
|
||||
width: 1200,
|
||||
height: 630,
|
||||
}
|
||||
|
||||
export const contentType = 'image/png'
|
||||
|
||||
// Image generation
|
||||
export default async function Image({ params }) {
|
||||
const post = await getPost(params.slug)
|
||||
|
||||
return new ImageResponse(
|
||||
(
|
||||
// ImageResponse JSX element
|
||||
<div
|
||||
style={{
|
||||
fontSize: 128,
|
||||
background: 'white',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
{post.title}
|
||||
</div>
|
||||
)
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
`ImageResponse` supports common CSS properties including flexbox and absolute positioning, custom fonts, text wrapping, centering, and nested images. [See the full list of supported CSS properties](/docs/app/api-reference/functions/image-response).
|
||||
|
||||
> **Good to know**:
|
||||
>
|
||||
> - Examples are available in the [Vercel OG Playground](https://og-playground.vercel.app/).
|
||||
> - `ImageResponse` uses [`@vercel/og`](https://vercel.com/docs/og-image-generation), [`satori`](https://github.com/vercel/satori), and `resvg` to convert HTML and CSS into PNG.
|
||||
> - Only flexbox and a subset of CSS properties are supported. Advanced layouts (e.g. `display: grid`) will not work.
|
||||
202
node_modules/next/dist/docs/01-app/01-getting-started/15-route-handlers.md
generated
vendored
Normal file
202
node_modules/next/dist/docs/01-app/01-getting-started/15-route-handlers.md
generated
vendored
Normal file
@@ -0,0 +1,202 @@
|
||||
---
|
||||
title: Route Handlers
|
||||
nav_title: Route Handlers
|
||||
description: Learn how to use Route Handlers
|
||||
related:
|
||||
title: API Reference
|
||||
description: Learn more about Route Handlers
|
||||
links:
|
||||
- app/api-reference/file-conventions/route
|
||||
- app/guides/backend-for-frontend
|
||||
---
|
||||
|
||||
## Route Handlers
|
||||
|
||||
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.
|
||||
|
||||
<Image
|
||||
alt="Route.js Special File"
|
||||
srcLight="/docs/light/route-special-file.png"
|
||||
srcDark="/docs/dark/route-special-file.png"
|
||||
width="1600"
|
||||
height="444"
|
||||
/>
|
||||
|
||||
> **Good to know**: Route Handlers are only available inside the `app` directory. They are the equivalent of [API Routes](/docs/pages/building-your-application/routing/api-routes) inside the `pages` directory meaning you **do not** need to use API Routes and Route Handlers together.
|
||||
|
||||
### Convention
|
||||
|
||||
Route Handlers are defined in a [`route.js|ts` file](/docs/app/api-reference/file-conventions/route) inside the `app` directory:
|
||||
|
||||
```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) {}
|
||||
```
|
||||
|
||||
Route Handlers can be nested anywhere inside the `app` directory, similar to `page.js` and `layout.js`. But there **cannot** be a `route.js` file at the same route segment level as `page.js`.
|
||||
|
||||
### Supported HTTP Methods
|
||||
|
||||
The following [HTTP methods](https://developer.mozilla.org/docs/Web/HTTP/Methods) are supported: `GET`, `POST`, `PUT`, `PATCH`, `DELETE`, `HEAD`, and `OPTIONS`. If an unsupported method is called, Next.js will return a `405 Method Not Allowed` response.
|
||||
|
||||
### Extended `NextRequest` and `NextResponse` APIs
|
||||
|
||||
In addition to supporting the native [Request](https://developer.mozilla.org/docs/Web/API/Request) and [Response](https://developer.mozilla.org/docs/Web/API/Response) APIs, Next.js extends them with [`NextRequest`](/docs/app/api-reference/functions/next-request) and [`NextResponse`](/docs/app/api-reference/functions/next-response) to provide convenient helpers for advanced use cases.
|
||||
|
||||
### Caching
|
||||
|
||||
Route Handlers are not cached by default. You can, however, opt into caching for `GET` methods. Other supported HTTP methods are **not** cached. To cache a `GET` method, use a [route config option](/docs/app/guides/caching-without-cache-components#dynamic) such as `export const dynamic = 'force-static'` in your Route Handler file.
|
||||
|
||||
```ts filename="app/items/route.ts" switcher
|
||||
export const dynamic = 'force-static'
|
||||
|
||||
export async function GET() {
|
||||
const res = await fetch('https://data.mongodb-api.com/...', {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'API-Key': process.env.DATA_API_KEY,
|
||||
},
|
||||
})
|
||||
const data = await res.json()
|
||||
|
||||
return Response.json({ data })
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="app/items/route.js" switcher
|
||||
export const dynamic = 'force-static'
|
||||
|
||||
export async function GET() {
|
||||
const res = await fetch('https://data.mongodb-api.com/...', {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'API-Key': process.env.DATA_API_KEY,
|
||||
},
|
||||
})
|
||||
const data = await res.json()
|
||||
|
||||
return Response.json({ data })
|
||||
}
|
||||
```
|
||||
|
||||
> **Good to know**: Other supported HTTP methods are **not** cached, even if they are placed alongside a `GET` method that is cached, in the same file.
|
||||
|
||||
#### With Cache Components
|
||||
|
||||
When [Cache Components](/docs/app/getting-started/caching) is enabled, `GET` Route Handlers follow the same model as normal UI routes in your application. They run at request time by default, can be prerendered when they don't access uncached or runtime data, and you can use `use cache` to include uncached data in the static response.
|
||||
|
||||
**Static example** - doesn't access uncached or runtime data, so it will be prerendered at build time:
|
||||
|
||||
```tsx filename="app/api/project-info/route.ts"
|
||||
export async function GET() {
|
||||
return Response.json({
|
||||
projectName: 'Next.js',
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
**Dynamic example** - accesses non-deterministic operations. During the build, prerendering stops when `Math.random()` is called, deferring to request-time rendering:
|
||||
|
||||
```tsx filename="app/api/random-number/route.ts"
|
||||
export async function GET() {
|
||||
return Response.json({
|
||||
randomNumber: Math.random(),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
**Runtime data example** - accesses request-specific data. Prerendering terminates when runtime APIs like `headers()` are called:
|
||||
|
||||
```tsx filename="app/api/user-agent/route.ts"
|
||||
import { headers } from 'next/headers'
|
||||
|
||||
export async function GET() {
|
||||
const headersList = await headers()
|
||||
const userAgent = headersList.get('user-agent')
|
||||
|
||||
return Response.json({ userAgent })
|
||||
}
|
||||
```
|
||||
|
||||
> **Good to know**: Prerendering stops if the `GET` handler accesses network requests, database queries, async file system operations, request object properties (like `req.url`, `request.headers`, `request.cookies`, `request.body`), runtime APIs like [`cookies()`](/docs/app/api-reference/functions/cookies), [`headers()`](/docs/app/api-reference/functions/headers), [`connection()`](/docs/app/api-reference/functions/connection), or non-deterministic operations.
|
||||
|
||||
**Cached example** - accesses uncached data (database query) but caches it with `use cache`, allowing it to be included in the prerendered response:
|
||||
|
||||
```tsx filename="app/api/products/route.ts"
|
||||
import { cacheLife } from 'next/cache'
|
||||
|
||||
export async function GET() {
|
||||
const products = await getProducts()
|
||||
return Response.json(products)
|
||||
}
|
||||
|
||||
async function getProducts() {
|
||||
'use cache'
|
||||
cacheLife('hours')
|
||||
|
||||
return await db.query('SELECT * FROM products')
|
||||
}
|
||||
```
|
||||
|
||||
> **Good to know**: `use cache` cannot be used directly inside a Route Handler body; extract it to a helper function. Cached responses revalidate according to `cacheLife` when a new request arrives.
|
||||
|
||||
### Special Route Handlers
|
||||
|
||||
Special Route Handlers like [`sitemap.ts`](/docs/app/api-reference/file-conventions/metadata/sitemap), [`opengraph-image.tsx`](/docs/app/api-reference/file-conventions/metadata/opengraph-image), and [`icon.tsx`](/docs/app/api-reference/file-conventions/metadata/app-icons), and other [metadata files](/docs/app/api-reference/file-conventions/metadata) remain static by default unless they use Request-time APIs or dynamic config options.
|
||||
|
||||
### Route Resolution
|
||||
|
||||
You can consider a `route` the lowest level routing primitive.
|
||||
|
||||
- They **do not** participate in layouts or client-side navigations like `page`.
|
||||
- There **cannot** be a `route.js` file at the same route as `page.js`.
|
||||
|
||||
| Page | Route | Result |
|
||||
| -------------------- | ------------------ | ---------------------------- |
|
||||
| `app/page.js` | `app/route.js` | <Cross size={18} /> Conflict |
|
||||
| `app/page.js` | `app/api/route.js` | <Check size={18} /> Valid |
|
||||
| `app/[user]/page.js` | `app/api/route.js` | <Check size={18} /> Valid |
|
||||
|
||||
Each `route.js` or `page.js` file takes over all HTTP verbs for that route.
|
||||
|
||||
```ts filename="app/page.ts" switcher
|
||||
export default function Page() {
|
||||
return <h1>Hello, Next.js!</h1>
|
||||
}
|
||||
|
||||
// Conflict
|
||||
// `app/route.ts`
|
||||
export async function POST(request: Request) {}
|
||||
```
|
||||
|
||||
```js filename="app/page.js" switcher
|
||||
export default function Page() {
|
||||
return <h1>Hello, Next.js!</h1>
|
||||
}
|
||||
|
||||
// Conflict
|
||||
// `app/route.js`
|
||||
export async function POST(request) {}
|
||||
```
|
||||
|
||||
Read more about how Route Handlers [complement your frontend application](/docs/app/guides/backend-for-frontend), or explore the Route Handlers [API Reference](/docs/app/api-reference/file-conventions/route).
|
||||
|
||||
### Route Context Helper
|
||||
|
||||
In TypeScript, you can type the `context` parameter for Route Handlers with the globally available [`RouteContext`](/docs/app/api-reference/file-conventions/route#route-context-helper) helper:
|
||||
|
||||
```ts filename="app/users/[id]/route.ts" switcher
|
||||
import type { NextRequest } from 'next/server'
|
||||
|
||||
export async function GET(_req: NextRequest, ctx: RouteContext<'/users/[id]'>) {
|
||||
const { id } = await ctx.params
|
||||
return Response.json({ id })
|
||||
}
|
||||
```
|
||||
|
||||
> **Good to know**
|
||||
>
|
||||
> - Types are generated during `next dev`, `next build` or `next typegen`.
|
||||
78
node_modules/next/dist/docs/01-app/01-getting-started/16-proxy.md
generated
vendored
Normal file
78
node_modules/next/dist/docs/01-app/01-getting-started/16-proxy.md
generated
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
---
|
||||
title: Proxy
|
||||
nav_title: Proxy
|
||||
description: Learn how to use Proxy
|
||||
related:
|
||||
title: API Reference
|
||||
description: Learn more about Proxy
|
||||
links:
|
||||
- app/api-reference/file-conventions/proxy
|
||||
- app/guides/backend-for-frontend
|
||||
---
|
||||
|
||||
## Proxy
|
||||
|
||||
> **Good to know**: Starting with Next.js 16, Middleware is now called Proxy to better reflect its purpose. The functionality remains the same.
|
||||
|
||||
Proxy allows you to run code before a request is completed. Then, based on the incoming request, you can modify the response by rewriting, redirecting, modifying the request or response headers, or responding directly.
|
||||
|
||||
### Use cases
|
||||
|
||||
Some common scenarios where Proxy is effective include:
|
||||
|
||||
- Modifying headers for all pages or a subset of pages
|
||||
- Rewriting to different pages based on A/B tests or experiments
|
||||
- Programmatic redirects based on incoming request properties
|
||||
|
||||
For simple redirects, consider using the [`redirects`](/docs/app/api-reference/config/next-config-js/redirects) configuration in `next.config.ts` first. Proxy should be used when you need access to request data or more complex logic.
|
||||
|
||||
Proxy is _not_ intended for slow data fetching. While Proxy can be helpful for [optimistic checks](/docs/app/guides/authentication#optimistic-checks-with-proxy-optional) such as permission-based redirects, it should not be used as a full session management or authorization solution.
|
||||
|
||||
Using fetch with `options.cache`, `options.next.revalidate`, or `options.next.tags`, has no effect in Proxy.
|
||||
|
||||
### Convention
|
||||
|
||||
Create a `proxy.ts` (or `.js`) file in the project root, or inside `src` if applicable, so that it is located at the same level as `pages` or `app`.
|
||||
|
||||
> **Note**: While only one `proxy.ts` file is supported per project, you can still organize your proxy logic into modules. Break out proxy functionalities into separate `.ts` or `.js` files and import them into your main `proxy.ts` file. This allows for cleaner management of route-specific proxy, aggregated in the `proxy.ts` for centralized control. By enforcing a single proxy file, it simplifies configuration, prevents potential conflicts, and optimizes performance by avoiding multiple proxy layers.
|
||||
|
||||
### Example
|
||||
|
||||
You can export your proxy function as either a default export or a named `proxy` export:
|
||||
|
||||
```ts filename="proxy.ts" switcher
|
||||
import { NextResponse } from 'next/server'
|
||||
import type { NextRequest } from 'next/server'
|
||||
|
||||
// This function can be marked `async` if using `await` inside
|
||||
export function proxy(request: NextRequest) {
|
||||
return NextResponse.redirect(new URL('/home', request.url))
|
||||
}
|
||||
|
||||
// Alternatively, you can use a default export:
|
||||
// export default function proxy(request: NextRequest) { ... }
|
||||
|
||||
export const config = {
|
||||
matcher: '/about/:path*',
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="proxy.js" switcher
|
||||
import { NextResponse } from 'next/server'
|
||||
|
||||
// This function can be marked `async` if using `await` inside
|
||||
export function proxy(request) {
|
||||
return NextResponse.redirect(new URL('/home', request.url))
|
||||
}
|
||||
|
||||
// Alternatively, you can use a default export:
|
||||
// export default function proxy(request) { ... }
|
||||
|
||||
export const config = {
|
||||
matcher: '/about/:path*',
|
||||
}
|
||||
```
|
||||
|
||||
The `matcher` config allows you to filter Proxy to run on specific paths. See the [Matcher](/docs/app/api-reference/file-conventions/proxy#matcher) documentation for more details on path matching.
|
||||
|
||||
Read more about [using `proxy`](/docs/app/guides/backend-for-frontend#proxy), or refer to the `proxy` [API reference](/docs/app/api-reference/file-conventions/proxy).
|
||||
100
node_modules/next/dist/docs/01-app/01-getting-started/17-deploying.md
generated
vendored
Normal file
100
node_modules/next/dist/docs/01-app/01-getting-started/17-deploying.md
generated
vendored
Normal file
@@ -0,0 +1,100 @@
|
||||
---
|
||||
title: Deploying
|
||||
description: Learn how to deploy your Next.js application.
|
||||
---
|
||||
|
||||
Next.js can be deployed as a Node.js server, Docker container, static export, or adapted to run on different platforms.
|
||||
|
||||
| Deployment Option | Feature Support |
|
||||
| -------------------------------- | ------------------------------------------------------------------- |
|
||||
| [Node.js server](#nodejs-server) | All |
|
||||
| [Docker container](#docker) | All |
|
||||
| [Static export](#static-export) | Limited |
|
||||
| [Adapters](#adapters) | Varies ([verified](#verified-adapters) adapters run the test suite) |
|
||||
|
||||
## Node.js server
|
||||
|
||||
Next.js can be deployed to any provider that supports Node.js. Ensure your `package.json` has the `"build"` and `"start"` scripts:
|
||||
|
||||
```json filename="package.json"
|
||||
{
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Then, run `npm run build` to build your application and `npm run start` to start the Node.js server. This server supports all Next.js features. If needed, you can also eject to a [custom server](/docs/app/guides/custom-server).
|
||||
|
||||
Node.js deployments support all Next.js features. Learn how to [configure them](/docs/app/guides/self-hosting) for your infrastructure.
|
||||
|
||||
### Templates
|
||||
|
||||
- [Flightcontrol](https://github.com/nextjs/deploy-flightcontrol)
|
||||
- [Railway](https://github.com/nextjs/deploy-railway)
|
||||
- [Replit](https://github.com/nextjs/deploy-replit)
|
||||
- [Hostinger](https://github.com/hostinger/deploy-nextjs)
|
||||
|
||||
## Docker
|
||||
|
||||
Next.js can be deployed to any provider that supports [Docker](https://www.docker.com/) containers. This includes container orchestrators like Kubernetes or a cloud provider that runs Docker. For containerization best practices, see the [Docker guide for React.js](https://docs.docker.com/guides/reactjs/).
|
||||
|
||||
Docker deployments support all Next.js features. Learn how to [configure them](/docs/app/guides/self-hosting) for your infrastructure.
|
||||
|
||||
> **Note for development:** While Docker is excellent for production deployments, consider using local development (`npm run dev`) instead of Docker during development on Mac and Windows for better performance. [Learn more about optimizing local development](/docs/app/guides/local-development).
|
||||
|
||||
### Templates
|
||||
|
||||
The following examples demonstrate best practices for containerizing Next.js applications:
|
||||
|
||||
- [Docker Standalone Output](https://github.com/vercel/next.js/tree/canary/examples/with-docker) - Deploy a Next.js application using `output: "standalone"` to generate a minimal, production-ready Docker image with only the required runtime files and dependencies.
|
||||
- [Docker Export Output](https://github.com/vercel/next.js/tree/canary/examples/with-docker-export-output) - Deploy a fully static Next.js application using `output: "export"` to generate optimized HTML files that can be served from a lightweight container or any static hosting environment.
|
||||
- [Docker Multi-Environment](https://github.com/vercel/next.js/tree/canary/examples/with-docker-multi-env) - Manage separate Docker configurations for development, staging, and production environments with different environment variables.
|
||||
|
||||
Additionally, hosting providers offer guidance on deploying Next.js:
|
||||
|
||||
- [DigitalOcean](https://github.com/nextjs/deploy-digitalocean)
|
||||
- [Fly.io](https://github.com/nextjs/deploy-fly)
|
||||
- [Google Cloud Run](https://github.com/nextjs/deploy-google-cloud-run)
|
||||
- [Render](https://github.com/nextjs/deploy-render)
|
||||
- [SST](https://github.com/nextjs/deploy-sst)
|
||||
|
||||
## Static export
|
||||
|
||||
Next.js enables starting as a static site or [Single-Page Application (SPA)](/docs/app/guides/single-page-applications), then later optionally upgrading to use features that require a server.
|
||||
|
||||
Since Next.js supports [static exports](/docs/app/guides/static-exports), it can be deployed and hosted on any web server that can serve HTML/CSS/JS static assets. This includes tools like AWS S3, Nginx, or Apache.
|
||||
|
||||
Running as a [static export](/docs/app/guides/static-exports) **does not** support Next.js features that require a server. [Learn more](/docs/app/guides/static-exports#unsupported-features).
|
||||
|
||||
### Templates
|
||||
|
||||
- [GitHub Pages](https://github.com/nextjs/deploy-github-pages)
|
||||
|
||||
## Adapters
|
||||
|
||||
Next.js can be adapted to run on different platforms to support their infrastructure capabilities. The [Deployment Adapter API](/docs/app/api-reference/config/next-config-js/adapterPath) lets platforms customize how Next.js applications are built and deployed.
|
||||
|
||||
### Verified Adapters
|
||||
|
||||
Verified adapters are open source, run the full [Next.js compatibility test suite](/docs/app/api-reference/adapters/testing-adapters), and are hosted under the [Next.js GitHub organization](https://github.com/nextjs). The Next.js team coordinates testing with these platforms before major releases. Publicly visible test results for each adapter are coming soon. [Learn more about verified adapters](/docs/app/guides/deploying-to-platforms#verified-adapters).
|
||||
|
||||
- [Vercel](https://vercel.com/docs/frameworks/nextjs)
|
||||
- [Bun](https://bun.sh/docs/frameworks/nextjs)
|
||||
|
||||
Cloudflare and Netlify are working on verified adapters built on the Adapter API. In the meantime, they offer their own Next.js integrations (see below).
|
||||
|
||||
### Other Platforms
|
||||
|
||||
The following platforms offer their own Next.js integrations. These are not built on the public [Adapter API](/docs/app/api-reference/config/next-config-js/adapterPath) and are not verified by the Next.js team, so feature support and compatibility may vary. Refer to each provider's documentation for details:
|
||||
|
||||
- [Appwrite Sites](https://appwrite.io/docs/products/sites/quick-start/nextjs)
|
||||
- [AWS Amplify Hosting](https://docs.amplify.aws/nextjs/start/quickstart/nextjs-app-router-client-components)
|
||||
- [Cloudflare](https://developers.cloudflare.com/workers/frameworks/framework-guides/nextjs)
|
||||
- [Deno Deploy](https://docs.deno.com/examples/next_tutorial)
|
||||
- [Firebase App Hosting](https://firebase.google.com/docs/app-hosting/get-started)
|
||||
- [Netlify](https://docs.netlify.com/frameworks/next-js/overview/#next-js-support-on-netlify)
|
||||
|
||||
For details on which Next.js features require specific platform capabilities, see [Deploying to Platforms](/docs/app/guides/deploying-to-platforms).
|
||||
87
node_modules/next/dist/docs/01-app/01-getting-started/18-upgrading.md
generated
vendored
Normal file
87
node_modules/next/dist/docs/01-app/01-getting-started/18-upgrading.md
generated
vendored
Normal file
@@ -0,0 +1,87 @@
|
||||
---
|
||||
title: Upgrading
|
||||
description: Learn how to upgrade your Next.js application to the latest version or canary.
|
||||
related:
|
||||
title: Version guides
|
||||
description: See the version guides for in-depth upgrade instructions.
|
||||
links:
|
||||
- app/guides/upgrading/version-16
|
||||
- app/guides/upgrading/version-15
|
||||
- app/guides/upgrading/version-14
|
||||
---
|
||||
|
||||
## Latest version
|
||||
|
||||
To update to the latest version of Next.js, you can use the `upgrade` command:
|
||||
|
||||
```bash package="pnpm"
|
||||
pnpm next upgrade
|
||||
```
|
||||
|
||||
```bash package="npm"
|
||||
npx next upgrade
|
||||
```
|
||||
|
||||
```bash package="yarn"
|
||||
yarn next upgrade
|
||||
```
|
||||
|
||||
```bash package="bun"
|
||||
bunx next upgrade
|
||||
```
|
||||
|
||||
Versions before Next.js 16.1.0 do not support the `upgrade` command and need to use a separate package instead:
|
||||
|
||||
```bash filename="Terminal"
|
||||
npx @next/codemod@canary upgrade latest
|
||||
```
|
||||
|
||||
If you prefer to upgrade manually, install the latest Next.js and React versions:
|
||||
|
||||
```bash package="pnpm"
|
||||
pnpm i next@latest react@latest react-dom@latest eslint-config-next@latest
|
||||
```
|
||||
|
||||
```bash package="npm"
|
||||
npm i 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
|
||||
```
|
||||
|
||||
## Canary version
|
||||
|
||||
To update to the latest canary, make sure you're on the latest version of Next.js and everything is working as expected. Then, run the following command:
|
||||
|
||||
```bash package="pnpm"
|
||||
pnpm add next@canary
|
||||
```
|
||||
|
||||
```bash package="npm"
|
||||
npm i next@canary
|
||||
```
|
||||
|
||||
```bash package="yarn"
|
||||
yarn add next@canary
|
||||
```
|
||||
|
||||
```bash package="bun"
|
||||
bun add next@canary
|
||||
```
|
||||
|
||||
### Features available in canary
|
||||
|
||||
The following features are currently available in canary:
|
||||
|
||||
**Authentication**:
|
||||
|
||||
- [`forbidden`](/docs/app/api-reference/functions/forbidden)
|
||||
- [`unauthorized`](/docs/app/api-reference/functions/unauthorized)
|
||||
- [`forbidden.js`](/docs/app/api-reference/file-conventions/forbidden)
|
||||
- [`unauthorized.js`](/docs/app/api-reference/file-conventions/unauthorized)
|
||||
- [`authInterrupts`](/docs/app/api-reference/config/next-config-js/authInterrupts)
|
||||
21
node_modules/next/dist/docs/01-app/01-getting-started/index.md
generated
vendored
Normal file
21
node_modules/next/dist/docs/01-app/01-getting-started/index.md
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
---
|
||||
title: Getting Started
|
||||
description: Learn how to create full-stack web applications with the Next.js App Router.
|
||||
---
|
||||
|
||||
Welcome to the Next.js documentation!
|
||||
|
||||
This **Getting Started** section will help you create your first Next.js app and learn the core features you'll use in every project.
|
||||
|
||||
## Pre-requisite knowledge
|
||||
|
||||
Our documentation assumes some familiarity with web development. Before getting started, it'll help if you're comfortable with:
|
||||
|
||||
- HTML
|
||||
- CSS
|
||||
- JavaScript
|
||||
- React
|
||||
|
||||
If you're new to React or need a refresher, we recommend starting with our [React Foundations course](/learn/react-foundations), and the [Next.js Foundations course](/learn/dashboard-app) that has you building an application as you learn.
|
||||
|
||||
## Next Steps
|
||||
103
node_modules/next/dist/docs/01-app/02-guides/ai-agents.md
generated
vendored
Normal file
103
node_modules/next/dist/docs/01-app/02-guides/ai-agents.md
generated
vendored
Normal file
@@ -0,0 +1,103 @@
|
||||
---
|
||||
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
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>For earlier versions</summary>
|
||||
|
||||
On version 16.1 and earlier, use the codemod to generate these files automatically:
|
||||
|
||||
```bash
|
||||
npx @next/codemod@latest agents-md
|
||||
```
|
||||
|
||||
The codemod outputs the bundled docs to `.next-docs/` in the project root instead of `node_modules/next/dist/docs/`, and the generated agent files will point to that directory.
|
||||
|
||||
</details>
|
||||
|
||||
## 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
node_modules/next/dist/docs/01-app/02-guides/analytics.md
generated
vendored
Normal file
238
node_modules/next/dist/docs/01-app/02-guides/analytics.md
generated
vendored
Normal 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).
|
||||
1697
node_modules/next/dist/docs/01-app/02-guides/authentication.md
generated
vendored
Normal file
1697
node_modules/next/dist/docs/01-app/02-guides/authentication.md
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
915
node_modules/next/dist/docs/01-app/02-guides/backend-for-frontend.md
generated
vendored
Normal file
915
node_modules/next/dist/docs/01-app/02-guides/backend-for-frontend.md
generated
vendored
Normal 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 don’t 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 won’t work because the connection closes on timeout, or after the response is generated.
|
||||
364
node_modules/next/dist/docs/01-app/02-guides/caching-without-cache-components.md
generated
vendored
Normal file
364
node_modules/next/dist/docs/01-app/02-guides/caching-without-cache-components.md
generated
vendored
Normal file
@@ -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)
|
||||
// ...
|
||||
}
|
||||
```
|
||||
116
node_modules/next/dist/docs/01-app/02-guides/cdn-caching.md
generated
vendored
Normal file
116
node_modules/next/dist/docs/01-app/02-guides/cdn-caching.md
generated
vendored
Normal file
@@ -0,0 +1,116 @@
|
||||
---
|
||||
title: Using a CDN with Next.js
|
||||
nav_title: CDN Caching
|
||||
description: Learn how CDN caching works with Next.js, including what works today, cache variability, and the direction toward pathname-based cache keying.
|
||||
related:
|
||||
description: Related guides and references.
|
||||
links:
|
||||
- app/guides/deploying-to-platforms
|
||||
- app/guides/self-hosting
|
||||
- app/guides/streaming
|
||||
- app/api-reference/config/next-config-js/assetPrefix
|
||||
---
|
||||
|
||||
Next.js sets standard `Cache-Control` headers that CDNs can use to cache responses at the edge. This page covers what works today, where CDN caching is challenging, and the direction toward eliminating custom-header dependencies.
|
||||
|
||||
## What Works Today
|
||||
|
||||
### Cache-Control headers
|
||||
|
||||
Next.js sets `Cache-Control` headers based on the rendering strategy of each route:
|
||||
|
||||
- **Static pages** (no revalidation): `s-maxage=31536000` (one year)
|
||||
- **ISR pages** (time-based revalidation): `s-maxage={revalidate}, stale-while-revalidate={expire - revalidate}`. The default `expire` is one year, so `stale-while-revalidate` is included in the response header by default. You can customize this with [`cacheLife`](/docs/app/api-reference/functions/cacheLife).
|
||||
- **Dynamic pages** (no caching): `private, no-cache, no-store, max-age=0, must-revalidate`
|
||||
|
||||
CDNs that respect `s-maxage` and `stale-while-revalidate` can cache static and ISR pages at the edge. However, CDN-level caching alone does not support on-demand revalidation ([`revalidateTag()`](/docs/app/api-reference/functions/revalidateTag) / [`revalidatePath()`](/docs/app/api-reference/functions/revalidatePath)): those calls invalidate the Next.js server cache, but the CDN will continue serving its cached copy until the `s-maxage` TTL expires. To propagate on-demand revalidation to the CDN, trigger CDN purges alongside your revalidation call. A common pattern is: call `revalidateTag()`/`revalidatePath()` to invalidate the Next.js server cache, then call your CDN purge API for the affected keys (including both HTML and RSC variants).
|
||||
|
||||
### Static assets
|
||||
|
||||
Static assets (JavaScript, CSS, images, fonts) served from `/_next/static/` include content hashes in their filenames and have a 1 year `max-age` and `immutable` directive: `public,max-age=31536000,immutable`
|
||||
|
||||
You can use [`assetPrefix`](/docs/app/api-reference/config/next-config-js/assetPrefix) to serve static assets from a different domain or CDN origin.
|
||||
|
||||
### Static prefetches (PPR-enabled routes)
|
||||
|
||||
When a route has Partial Prerendering enabled and the `next-router-prefetch` header is set (indicating a static prefetch), the response is deterministic: it returns the same prerendered content regardless of the client's router state. The `next-router-state-tree` header is not parsed for these requests, so it does not affect the response.
|
||||
|
||||
For PPR-enabled routes, a CDN can cache static prefetch responses if it:
|
||||
|
||||
1. Includes the `_rsc` search parameter in the cache key (to distinguish prefetch variants from HTML responses).
|
||||
2. Respects the `Cache-Control` headers Next.js sets on the response.
|
||||
|
||||
> **Good to know:** For routes without PPR, the `next-router-state-tree` header is read during prefetch requests to determine which segments to include, which increases cache `vary` as it passes the current router state. When Cache Components is enabled, segment-level prefetches already use pathname-based routes (for example, `/page.segments/_tree.segment.rsc`), and CDNs can cache these with standard pathname-based cache keys.
|
||||
|
||||
## Where CDN Caching Is Challenging
|
||||
|
||||
App Router responses can vary based on several custom request headers. Next.js sets a `Vary` header on responses to signal this to CDNs:
|
||||
|
||||
- `rsc` — whether the request should return a React Server Components (RSC) payload instead of HTML
|
||||
- `next-router-state-tree` — the client's current router state, used for targeted segment updates during dynamic navigations
|
||||
- `next-router-prefetch` — whether this is a prefetch request
|
||||
- `next-router-segment-prefetch` — the specific segment being prefetched
|
||||
- `next-url` — added only for routes that use [interception routes](/docs/app/api-reference/file-conventions/intercepting-routes), carries the URL being intercepted
|
||||
|
||||
> **Good to know:** [`proxy.js`](/docs/app/api-reference/file-conventions/proxy) (previously Middleware) should run before the CDN cache so it remains the source of truth for auth, redirects, and rewrites. If your deployment places `proxy.js` behind the CDN, configure the cache layer to bypass caching for routes that depend on `proxy.js` decisions.
|
||||
|
||||
Many CDNs don't support `Vary` without additional configuration. Next.js addresses this with the `_rsc` search parameter: a hash of the relevant request header values that acts as a cache-key, ensuring different response variants get different cache keys. This ensures correct responses even on CDNs that ignore `Vary`.
|
||||
|
||||
## Handling Headers at the CDN
|
||||
|
||||
### What you can safely ignore
|
||||
|
||||
These headers can be omitted in specific cases without causing protocol errors. The server still returns a parseable response, but it may be larger or less targeted to the specific navigation:
|
||||
|
||||
**`next-router-state-tree`**: when omitted on non-prefetch RSC requests, the server returns a full payload instead of a targeted segment update.
|
||||
|
||||
**`next-router-segment-prefetch`**: when omitted on prefetch requests, the server falls back to a broader prefetch payload instead of a segment-specific one.
|
||||
|
||||
**`next-url`**: used for [interception routes](/docs/app/api-reference/file-conventions/intercepting-routes) to vary the response based on the referring page. If omitted, interception routes are not supported as the server doesn't know what original path to match against. The response returned is for regular navigation when `next-url` is omitted: the user sees the target page instead of the intercepted target page.
|
||||
|
||||
### What you must preserve
|
||||
|
||||
**The `rsc` header** must be forwarded from the client to the server. This header tells the server to return an RSC payload instead of HTML. If a CDN strips it, the server returns HTML when the client-side router expects RSC data, which breaks client-side navigation, causing browser navigations instead. The `Vary` header and `_rsc` parameter exist specifically to prevent CDNs from serving a cached HTML response to an RSC request (or vice versa).
|
||||
|
||||
**When `next-router-prefetch` is present, preserve both the prefetch header and the `_rsc` search parameter.** For prefetch flows, `_rsc` is a required cache-busting discriminator and should be treated as mandatory.
|
||||
|
||||
**The `_rsc` search parameter** must be included in the cache key. It distinguishes response variants (HTML vs. RSC, different prefetch types). Ensure your CDN does not strip query parameters from cache keys, as some CDNs do this by default. When the `experimental.validateRSCRequestHeaders` option is enabled and a RSC request arrives without the correct `_rsc` value, the server responds with a **307 redirect** to the URL with the correct hash. CDNs should follow this redirect. Platforms that compute the hash upstream can rewrite requests to include the correct `_rsc` before forwarding to avoid an extra round trip.
|
||||
|
||||
> **Good to know:** Today, `next-url` is included in the `_rsc` hash even during static prefetches. This means you cannot safely ignore it under the current scheme without potentially getting cache misses. The pathname-based direction described below resolves this gap.
|
||||
|
||||
## Direction: Pathname-Based Cache Keying
|
||||
|
||||
The Next.js team is working on moving all cache-affecting inputs into the URL pathname, eliminating the need for `Vary` on custom headers and removing the `_rsc` search parameter. This resolves the CDN caching challenges described above.
|
||||
|
||||
### How it works
|
||||
|
||||
The approach extends the routing scheme that [`output: 'export'`](/docs/app/guides/static-exports) and segment prefetches already use today. File extensions in the pathname identify the response type:
|
||||
|
||||
- **Full page RSC**: `/my/page.rsc` returns the RSC payload for the entire page
|
||||
- **Segment RSC**: `/my/page.segments/path/to/segment.segment.rsc` returns the RSC payload for a specific segment
|
||||
|
||||
Under this model:
|
||||
|
||||
- **The pathname determines the cache key.** Anything in the pathname affects which response variant is returned.
|
||||
- **Search parameters can be safely dropped** without affecting returned responses.
|
||||
- **Standard HTTP cache headers** (`Cache-Control`, `max-age`, etc.) are respected as usual.
|
||||
- **No `Vary` support needed** from the CDN.
|
||||
|
||||
A CDN would cache Next.js responses by using the pathname as the cache key, ignoring search parameters, and respecting standard `Cache-Control` headers. No need to understand `Vary`, inspect custom headers, or program edge logic.
|
||||
|
||||
### What changes for interception routes
|
||||
|
||||
Under the current scheme, `next-url` contributes to the `_rsc` hash, so dropping it causes cache misses. Under the pathname-based scheme, interception variability would be encoded in a search parameter (not the pathname):
|
||||
|
||||
- If a CDN preserves search params, interception works correctly.
|
||||
- If a CDN drops search params, interception is not supported. It would gracefully degrade to the non-intercepted page, client-side navigations won't break.
|
||||
|
||||
This makes interception route support an opt-in CDN capability rather than a requirement.
|
||||
|
||||
### Current status
|
||||
|
||||
This direction extends patterns that are already operational in the codebase (segment prefetch paths, `output: 'export'` mode). It is in active design.
|
||||
|
||||
## CDN Feature Compatibility
|
||||
|
||||
For a full table showing the infrastructure primitives available on every major CDN (edge compute, key-value storage, blob storage, PPR resuming), see [Deploying to Platforms](/docs/app/guides/deploying-to-platforms#cdn-infrastructure-compatibility).
|
||||
169
node_modules/next/dist/docs/01-app/02-guides/ci-build-caching.md
generated
vendored
Normal file
169
node_modules/next/dist/docs/01-app/02-guides/ci-build-caching.md
generated
vendored
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
729
node_modules/next/dist/docs/01-app/02-guides/content-security-policy.md
generated
vendored
Normal file
729
node_modules/next/dist/docs/01-app/02-guides/content-security-policy.md
generated
vendored
Normal file
@@ -0,0 +1,729 @@
|
||||
---
|
||||
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.
|
||||
|
||||
Here’s 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 don’t 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
|
||||
- **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
node_modules/next/dist/docs/01-app/02-guides/css-in-js.md
generated
vendored
Normal file
322
node_modules/next/dist/docs/01-app/02-guides/css-in-js.md
generated
vendored
Normal 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
node_modules/next/dist/docs/01-app/02-guides/custom-server.md
generated
vendored
Normal file
118
node_modules/next/dist/docs/01-app/02-guides/custom-server.md
generated
vendored
Normal 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
node_modules/next/dist/docs/01-app/02-guides/data-security.md
generated
vendored
Normal file
600
node_modules/next/dist/docs/01-app/02-guides/data-security.md
generated
vendored
Normal 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 platform’s 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
node_modules/next/dist/docs/01-app/02-guides/debugging.md
generated
vendored
Normal file
179
node_modules/next/dist/docs/01-app/02-guides/debugging.md
generated
vendored
Normal 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/)
|
||||
93
node_modules/next/dist/docs/01-app/02-guides/deploying-to-platforms.md
generated
vendored
Normal file
93
node_modules/next/dist/docs/01-app/02-guides/deploying-to-platforms.md
generated
vendored
Normal file
@@ -0,0 +1,93 @@
|
||||
---
|
||||
title: Deploying Next.js to different platforms
|
||||
nav_title: Deploying to Platforms
|
||||
description: Understand which Next.js features require specific platform capabilities and how to choose the right deployment target.
|
||||
related:
|
||||
description: Related guides and references.
|
||||
links:
|
||||
- app/guides/rendering-philosophy
|
||||
- app/guides/self-hosting
|
||||
- app/getting-started/deploying
|
||||
- app/api-reference/config/next-config-js/adapterPath
|
||||
---
|
||||
|
||||
Next.js [treats static and dynamic content as a spectrum](/docs/app/guides/rendering-philosophy) at the component level. Different features in this model require different platform capabilities. This page helps you understand what your platform needs to support and how to configure your deployment.
|
||||
|
||||
## Minimum Requirements
|
||||
|
||||
To run Next.js, your platform needs **a Node.js server**. That's it.
|
||||
|
||||
A single `next start` process handles every Next.js feature correctly: Server Components, ISR, PPR, Cache Components, Server Actions, Proxy, and `after()`. Streaming support is needed for features like PPR and Server Components to deliver content progressively (without it, responses are buffered and sent as a whole, which still works but loses the streaming performance benefit). Additional infrastructure (CDN caching, edge compute, shared cache) primarily improves performance and multi-instance consistency. In multi-instance deployments, shared cache and tag coordination reduce stale divergence between instances. The only additional dependency is the `sharp` package, which is required for [Image Optimization](/docs/app/api-reference/components/image).
|
||||
|
||||
## Functional Fidelity vs. Performance Fidelity
|
||||
|
||||
When evaluating platform support for Next.js, it helps to distinguish between two levels:
|
||||
|
||||
**Functional fidelity** means every Next.js feature works correctly. The [adapter test suite](/docs/app/api-reference/adapters/testing-adapters) is the contract: if a platform's adapter passes the tests, it supports Next.js. This is binary: it passes or it doesn't.
|
||||
|
||||
**Performance fidelity** means features achieve their optimal performance characteristics. Examples include PPR's static shell served at CDN latency rather than origin latency, or ISR serving stale content instantly with sub-second revalidation propagation. Performance fidelity is a spectrum that every platform will achieve differently based on their architecture.
|
||||
|
||||
A platform that achieves functional fidelity is a fully supported deployment target for Next.js. Performance fidelity is how platforms differentiate, and it improves incrementally over time.
|
||||
|
||||
Use the feature matrix below through this lens: "Streaming Required" and "Shared Cache Recommended" describe what is needed for functional fidelity. "Edge Stitching" is a performance fidelity optimization.
|
||||
|
||||
## Feature Support Matrix
|
||||
|
||||
Different features require different infrastructure capabilities. The "Edge Stitching" column is a **performance optimization**, not a correctness requirement. All features work correctly from a single origin server.
|
||||
|
||||
| Feature | Streaming | Shared Cache | Edge Stitching | Notes |
|
||||
| ------------------------------ | --------- | ------------ | -------------- | ------------------------------------------------------------------------------------------------ |
|
||||
| Server Components | Required | No | No | Basic streaming support |
|
||||
| ISR (time-based) | No | Recommended | No | Works per-instance without shared cache |
|
||||
| ISR (on-demand) | No | Recommended | No | [Tag propagation](/docs/app/guides/how-revalidation-works) needs shared cache for multi-instance |
|
||||
| Partial Prerendering | Required | Recommended | Optional | [See PPR Platform Guide](/docs/app/guides/ppr-platform-guide) |
|
||||
| Cache Components (`use cache`) | Required | Recommended | No | Shared cache enables cross-instance consistency |
|
||||
| Proxy / Middleware | No | No | No | Runs at edge or origin |
|
||||
| Server Actions | Required | No | No | POST requests with streaming response |
|
||||
| `after()` | No | No | No | Requires [graceful shutdown](/docs/app/guides/self-hosting#after) support |
|
||||
|
||||
**Streaming Required** means the platform must support chunked transfer encoding or HTTP/2 streaming and must not buffer the response before sending it to the client.
|
||||
|
||||
**Shared Cache Recommended** means multiple server instances benefit from shared cache backends to coordinate. For ISR and server response caching, use [`cacheHandler`](/docs/app/api-reference/config/next-config-js/incrementalCacheHandlerPath). For `'use cache'` entries, use [`cacheHandlers`](/docs/app/api-reference/config/next-config-js/cacheHandlers). Without shared cache, each instance maintains its own cache independently — features still work correctly on each instance, but revalidation events don't propagate across instances.
|
||||
|
||||
## CDN Infrastructure Compatibility
|
||||
|
||||
The following table maps infrastructure primitives for each major CDN. These are available building blocks, not finished integrations:
|
||||
|
||||
| CDN | Edge Compute | Key-Value / Tags | Blob Storage | PPR Resuming |
|
||||
| ----------------- | ------------ | ---------------- | -------------- | ------------ |
|
||||
| Cloudflare | Workers | KV | R2 | Yes (worker) |
|
||||
| Akamai | EdgeWorkers | EdgeKV | Object Storage | Yes (worker) |
|
||||
| Amazon CloudFront | Lambda@Edge | KeyValueStore | S3 | Yes (Lambda) |
|
||||
| Fastly | Compute | KV Store | Object Storage | Yes (WASM) |
|
||||
| Azure | Functions | Managed Redis | Blob Storage | Yes (server) |
|
||||
| Google Cloud | Cloud Run | Various KV | Cloud Storage | Yes (server) |
|
||||
|
||||
These are available building blocks, not finished integrations. Most community adapters today deploy Next.js as a Docker container or Node.js server without leveraging CDN-specific primitives like edge KV or PPR resuming. See the [Deploying](/docs/app/getting-started/deploying#adapters) page for the current list of adapters. For CDN-specific caching considerations (including known limitations with `Vary` on custom headers), see [CDN Caching](/docs/app/guides/cdn-caching).
|
||||
|
||||
## Adapters
|
||||
|
||||
Next.js provides a [Deployment Adapter API](/docs/app/api-reference/config/next-config-js/adapterPath) that lets platforms customize how Next.js applications are built and deployed for their infrastructure. Adapters run at build time and produce platform-specific output from the standard Next.js build. Anyone can build an adapter using the public API with no special access required.
|
||||
|
||||
The adapter API plus Next.js caching interfaces form the complete platform integration surface. The adapter handles build-time output, while `cacheHandler` and `cacheHandlers` cover different runtime caching paths. `cacheHandler` (singular) covers server cache paths like ISR, route handlers, patched `fetch`/`unstable_cache`, and image optimization. `cacheHandlers` (plural) configures `'use cache'` directive backends.
|
||||
|
||||
### Verified Adapters
|
||||
|
||||
A **verified adapter** is one that meets two requirements:
|
||||
|
||||
1. **Open source.** The adapter source code is publicly available so the community and the Next.js team can inspect, contribute to, and verify it.
|
||||
2. **Runs the compatibility test suite.** The platform provides a way to run the full [Next.js compatibility test suite](/docs/app/api-reference/adapters/testing-adapters) against their adapter. This gives visibility into which features work, which are in progress, and where gaps remain.
|
||||
|
||||
Verified adapters are hosted under the [Next.js GitHub organization](https://github.com/nextjs), listed as supported deployment targets in the Next.js documentation, and maintained by their respective platform teams. There are no private framework hooks or integration paths: Vercel's adapter uses the same public API as every other adapter.
|
||||
|
||||
For verified adapters and platforms working toward verified status through the [Ecosystem Working Group](https://nextjs.org/ecosystem-working-group), the Next.js team commits to:
|
||||
|
||||
- **Coordinated testing.** Before major releases, we work with platform teams to run the compatibility test suite and surface issues early.
|
||||
- **Early access.** Adapter authors receive early access to API changes during RFCs and release candidates.
|
||||
- **Direct support.** When the adapter contract needs updating, we work directly with adapter teams.
|
||||
|
||||
> **Good to know:** Platforms can build closed-source adapters on the same public API and test suite. However, closed-source adapters will not be listed as verified, since the Next.js team cannot verify what it cannot inspect.
|
||||
|
||||
## A Note on Infrastructure Requirements
|
||||
|
||||
Next.js's [rendering model](/docs/app/guides/rendering-philosophy) places the static/dynamic boundary at the component level rather than the route level. Finer-grained boundaries provide more flexibility for developers at the cost of broader requirements for hosting platforms. This is a deliberate trade-off: the infrastructure requirements on this page exist because of what the rendering model delivers.
|
||||
215
node_modules/next/dist/docs/01-app/02-guides/draft-mode.md
generated
vendored
Normal file
215
node_modules/next/dist/docs/01-app/02-guides/draft-mode.md
generated
vendored
Normal 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 browser’s 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 you’re using supports setting **custom draft URLs**. If it doesn’t, you can still use this method to secure your draft URLs, but you’ll need to construct and access the draft URL manually. The specific steps will vary depending on which headless CMS you’re 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 CMS’s 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
node_modules/next/dist/docs/01-app/02-guides/environment-variables.md
generated
vendored
Normal file
289
node_modules/next/dist/docs/01-app/02-guides/environment-variables.md
generated
vendored
Normal 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
node_modules/next/dist/docs/01-app/02-guides/forms.md
generated
vendored
Normal file
508
node_modules/next/dist/docs/01-app/02-guides/forms.md
generated
vendored
Normal 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.
|
||||
96
node_modules/next/dist/docs/01-app/02-guides/how-revalidation-works.md
generated
vendored
Normal file
96
node_modules/next/dist/docs/01-app/02-guides/how-revalidation-works.md
generated
vendored
Normal file
@@ -0,0 +1,96 @@
|
||||
---
|
||||
title: How revalidation works in Next.js
|
||||
nav_title: How Revalidation Works
|
||||
description: A deep dive into how Next.js revalidates cached content, including the tag system, cache consistency, and multi-instance coordination.
|
||||
related:
|
||||
description: Related guides and references.
|
||||
links:
|
||||
- app/getting-started/caching
|
||||
- app/guides/incremental-static-regeneration
|
||||
- app/api-reference/config/next-config-js/cacheHandlers
|
||||
- app/api-reference/functions/revalidateTag
|
||||
---
|
||||
|
||||
The [Caching](/docs/app/getting-started/caching) page covers how to use `use cache`, `cacheTag`, and `cacheLife`. This page explains **how revalidation works internally**, for platform engineers and advanced users who need to understand the system to implement [custom cache handlers](/docs/app/api-reference/config/next-config-js/cacheHandlers) or debug revalidation behavior.
|
||||
|
||||
## The Revalidation Model
|
||||
|
||||
Most routes in Next.js can be revalidated on demand. This includes App Router routes and Pages Router routes that produce ISR/prerender cache entries. Pages Router routes that are automatically statically optimized (pure static output) are not revalidated on demand. The ability to update cached content without redeploying is a core part of Next.js's [rendering model](/docs/app/guides/rendering-philosophy).
|
||||
|
||||
There are two types of revalidation:
|
||||
|
||||
- **Time-based revalidation** uses a stale-while-revalidate pattern. The cached content is served immediately, and a background regeneration is triggered when the content's age exceeds the [`cacheLife`](/docs/app/api-reference/functions/cacheLife) or `revalidate` duration. The stale content continues to be served until the fresh content is ready.
|
||||
- **On-demand revalidation** explicitly invalidates cached content by calling [`revalidateTag()`](/docs/app/api-reference/functions/revalidateTag) or [`revalidatePath()`](/docs/app/api-reference/functions/revalidatePath). The next request to that content triggers a fresh render.
|
||||
|
||||
> **Good to know:** Pages Router on-demand ISR APIs (for example `res.revalidate()` and the `x-prerender-revalidate` flow) are still supported and use the server cache handler (`cacheHandler`, singular). The `cacheHandlers` option (plural) is for `'use cache'` directives.
|
||||
|
||||
## What Gets Revalidated
|
||||
|
||||
When a route is revalidated, Next.js regenerates **both** the HTML response and the RSC payload (React Server Components payload) from the same React component tree. Both artifacts are stored together in the same cache entry.
|
||||
|
||||
This consistency matters because the RSC payload is used for client-side navigations. Browser navigations and client-side navigations should hold the same content.
|
||||
|
||||
### What happens if they get out of sync
|
||||
|
||||
If a platform's cache serves HTML from one render and an RSC payload from a different render, users may see stale or mismatched content during client-side navigation. The primary mitigation is to cache HTML and RSC responses together with the same TTL and invalidation policy, and to respect the [`Vary` header](/docs/app/guides/cdn-caching) that Next.js sets. See [CDN Caching](/docs/app/guides/cdn-caching) for details.
|
||||
|
||||
A separate but related problem is **cross-deployment skew**: during rolling deployments, a client built with deploy A may receive responses from a server running deploy B. [`deploymentId`](/docs/app/api-reference/config/next-config-js/deploymentId) mitigates this: when the client detects a different deployment ID from the server, it triggers a hard navigation to fetch consistent content.
|
||||
|
||||
## Tag System Architecture
|
||||
|
||||
Next.js uses a tag-based system to track which cached content needs to be invalidated. There are two types of tags:
|
||||
|
||||
### Explicit tags
|
||||
|
||||
Explicit tags are set by the developer using [`cacheTag()`](/docs/app/api-reference/functions/cacheTag) inside a `use cache` function, or via `next: { tags: [...] }` on a `fetch` call. When [`revalidateTag('my-tag')`](/docs/app/api-reference/functions/revalidateTag) is called, all cache entries with that tag are invalidated.
|
||||
|
||||
### Soft tags
|
||||
|
||||
Soft tags are automatically generated by Next.js based on the route path, prefixed with `_N_T_`. For example, the route `/blog/hello` generates soft tags like `_N_T_/layout`, `_N_T_/blog/layout`, `_N_T_/blog/hello/layout`, and `_N_T_/blog/hello`. Each segment in the path gets a layout tag, plus the leaf route itself.
|
||||
|
||||
Soft tags enable [`revalidatePath()`](/docs/app/api-reference/functions/revalidatePath) to work through the same tag-based system. When `revalidatePath('/blog/hello')` is called, it invalidates cache entries associated with that path's leaf route tag and its ancestor layout soft tags (for example `_N_T_/layout`, `_N_T_/blog/layout`, `_N_T_/blog/hello/layout`, and `_N_T_/blog/hello`).
|
||||
|
||||
In the [cache handler API](/docs/app/api-reference/config/next-config-js/cacheHandlers), soft tags are passed to the `get()` method as the `softTags` parameter. Your handler should check whether any soft tag has been invalidated after the cache entry's timestamp. The `getExpiration()` method returns the most recent revalidation timestamp across all provided tags, or `0` if none have been revalidated. Your handler should treat an entry as stale if the returned timestamp is newer than the entry's own timestamp. See the [cache handler API reference](/docs/app/api-reference/config/next-config-js/cacheHandlers#getexpiration) for the full semantics.
|
||||
|
||||
## Multi-Instance Considerations
|
||||
|
||||
When running multiple Next.js instances behind a load balancer, revalidation events are local by default. Calling `revalidateTag()` on instance A only invalidates the cache on that instance. Other instances continue serving the stale content until they learn about the invalidation.
|
||||
|
||||
The cache handler API provides two hooks for distributed coordination:
|
||||
|
||||
- **`updateTags()`** is called when `revalidateTag()` is invoked. Your handler should write the invalidation event to shared storage (for example, Redis or a database) so other instances can discover it.
|
||||
- **`refreshTags()`** is called periodically, but always before starting a new request. Your handler should check shared storage for recent invalidation events and update its local tag state accordingly.
|
||||
|
||||
For implementation details and a Redis example, see [Custom Cache Handlers](/docs/app/api-reference/config/next-config-js/cacheHandlers).
|
||||
|
||||
## Implementation Patterns for Platforms
|
||||
|
||||
### Single instance
|
||||
|
||||
The default file-system cache handles consistency automatically. Cache writes are atomic on the local filesystem, and tag state is maintained in memory. No additional configuration is needed.
|
||||
|
||||
### Multi-instance with shared cache
|
||||
|
||||
Without coordination, each instance independently serves content and handles revalidation using only its local cache. Different users may see different content depending on which instance serves their request, and on-demand revalidation only takes effect on the instance that received the call.
|
||||
|
||||
To reduce this window and ensure revalidation propagates across instances:
|
||||
|
||||
1. Store tag invalidation timestamps in a shared service (Redis, DynamoDB, or a simple HTTP API).
|
||||
2. Implement `updateTags()` to write to the shared service.
|
||||
3. Implement `refreshTags()` to read from the shared service. Your handler must catch errors in `refreshTags()`: if it throws, the exception propagates as a request failure. Catching the error allows requests to continue with the last known local tag state, serving potentially stale content until connectivity is restored.
|
||||
4. Store cache entries (HTML + RSC payload) in shared storage. Atomic writes reduce the mismatch window further but are not required for correctness.
|
||||
|
||||
### CDN integration
|
||||
|
||||
If a CDN caches Next.js responses, it should respect the `Vary` header and the `Cache-Control` directives that Next.js sets. Do not cache HTML and RSC payload responses separately with different TTLs. See [CDN Caching](/docs/app/guides/cdn-caching) for details.
|
||||
|
||||
## Graceful Degradation
|
||||
|
||||
The revalidation system prioritizes availability over strict consistency. Content is always served, even when infrastructure guarantees cannot be fully met:
|
||||
|
||||
- **Cache write failure**: the response is still served to the user because writes are asynchronous. The cache entry is lost, and the next request triggers a fresh render.
|
||||
- **Cache read failure**: your handler should catch internal errors and return `undefined` (the cache miss signal). The route is then server-rendered fresh. The framework does not wrap `get()` in a try/catch, so unhandled exceptions will propagate as render errors.
|
||||
- **HTML/RSC cache inconsistency**: if a CDN caches HTML and RSC responses with different TTLs or invalidation timing, users may see mismatched content during client-side navigation. Cache them together and respect the `Vary` header to avoid this.
|
||||
- **Cross-deployment skew**: during rolling deployments, configure [`deploymentId`](/docs/app/api-reference/config/next-config-js/deploymentId) so that a build ID change triggers a hard navigation to fetch consistent content.
|
||||
|
||||
Cache failures result in degraded performance (stale content, extra renders), not broken applications.
|
||||
611
node_modules/next/dist/docs/01-app/02-guides/incremental-static-regeneration.md
generated
vendored
Normal file
611
node_modules/next/dist/docs/01-app/02-guides/incremental-static-regeneration.md
generated
vendored
Normal file
@@ -0,0 +1,611 @@
|
||||
---
|
||||
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`.
|
||||
- When running multiple instances, the default file-system cache is per-instance. On-demand revalidation only invalidates the instance that receives the call. Use a shared [custom cache handler](/docs/app/api-reference/config/next-config-js/incrementalCacheHandlerPath) to coordinate across instances. See [How Revalidation Works](/docs/app/guides/how-revalidation-works) for the full architecture.
|
||||
- Background regeneration (stale-while-revalidate) runs on the instance that receives the triggering request. On platforms with per-request billing, this background work counts as additional compute.
|
||||
- You can use the `x-nextjs-cache` response header to observe cache behavior. Values are `HIT` (served from cache), `STALE` (served from cache, revalidating in background), `MISS` (not in cache, rendered fresh), or `REVALIDATED` (regenerated via on-demand revalidation).
|
||||
|
||||
</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
node_modules/next/dist/docs/01-app/02-guides/index.md
generated
vendored
Normal file
4
node_modules/next/dist/docs/01-app/02-guides/index.md
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
title: Guides
|
||||
description: Learn how to implement common patterns and real-world use cases using Next.js
|
||||
---
|
||||
287
node_modules/next/dist/docs/01-app/02-guides/instant-navigation.md
generated
vendored
Normal file
287
node_modules/next/dist/docs/01-app/02-guides/instant-navigation.md
generated
vendored
Normal 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
node_modules/next/dist/docs/01-app/02-guides/instrumentation.md
generated
vendored
Normal file
96
node_modules/next/dist/docs/01-app/02-guides/instrumentation.md
generated
vendored
Normal 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
node_modules/next/dist/docs/01-app/02-guides/internationalization.md
generated
vendored
Normal file
226
node_modules/next/dist/docs/01-app/02-guides/internationalization.md
generated
vendored
Normal 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
|
||||
|
||||
It’s recommended to use the user’s 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 user’s preferred locale, or localization, is not something specific to Next.js. The patterns described below would work the same with any web application.
|
||||
|
||||
Let’s 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
node_modules/next/dist/docs/01-app/02-guides/json-ld.md
generated
vendored
Normal file
87
node_modules/next/dist/docs/01-app/02-guides/json-ld.md
generated
vendored
Normal 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
node_modules/next/dist/docs/01-app/02-guides/lazy-loading.md
generated
vendored
Normal file
306
node_modules/next/dist/docs/01-app/02-guides/lazy-loading.md
generated
vendored
Normal 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
node_modules/next/dist/docs/01-app/02-guides/local-development.md
generated
vendored
Normal file
276
node_modules/next/dist/docs/01-app/02-guides/local-development.md
generated
vendored
Normal 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"** → **"Manage settings"** → **"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"** → **"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
node_modules/next/dist/docs/01-app/02-guides/mcp.md
generated
vendored
Normal file
188
node_modules/next/dist/docs/01-app/02-guides/mcp.md
generated
vendored
Normal 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
node_modules/next/dist/docs/01-app/02-guides/mdx.md
generated
vendored
Normal file
825
node_modules/next/dist/docs/01-app/02-guides/mdx.md
generated
vendored
Normal 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
node_modules/next/dist/docs/01-app/02-guides/memory-usage.md
generated
vendored
Normal file
169
node_modules/next/dist/docs/01-app/02-guides/memory-usage.md
generated
vendored
Normal 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.
|
||||
175
node_modules/next/dist/docs/01-app/02-guides/migrating-to-cache-components.md
generated
vendored
Normal file
175
node_modules/next/dist/docs/01-app/02-guides/migrating-to-cache-components.md
generated
vendored
Normal file
@@ -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.
|
||||
970
node_modules/next/dist/docs/01-app/02-guides/migrating/app-router-migration.md
generated
vendored
Normal file
970
node_modules/next/dist/docs/01-app/02-guides/migrating/app-router-migration.md
generated
vendored
Normal file
@@ -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.
|
||||
611
node_modules/next/dist/docs/01-app/02-guides/migrating/from-create-react-app.md
generated
vendored
Normal file
611
node_modules/next/dist/docs/01-app/02-guides/migrating/from-create-react-app.md
generated
vendored
Normal file
@@ -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 application’s 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, we’ll 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 you’re 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 CRA’s `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, we’ll 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, we’ll embed your CRA’s 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 CRA’s 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.js’s 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 aren’t 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
node_modules/next/dist/docs/01-app/02-guides/migrating/from-vite.md
generated
vendored
Normal file
614
node_modules/next/dist/docs/01-app/02-guides/migrating/from-vite.md
generated
vendored
Normal 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, you’ll 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
|
||||
aren’t 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
node_modules/next/dist/docs/01-app/02-guides/migrating/index.md
generated
vendored
Normal file
4
node_modules/next/dist/docs/01-app/02-guides/migrating/index.md
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
title: Migrating
|
||||
description: Learn how to migrate from popular frameworks to Next.js
|
||||
---
|
||||
7
node_modules/next/dist/docs/01-app/02-guides/multi-tenant.md
generated
vendored
Normal file
7
node_modules/next/dist/docs/01-app/02-guides/multi-tenant.md
generated
vendored
Normal 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
node_modules/next/dist/docs/01-app/02-guides/multi-zones.md
generated
vendored
Normal file
142
node_modules/next/dist/docs/01-app/02-guides/multi-zones.md
generated
vendored
Normal 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
node_modules/next/dist/docs/01-app/02-guides/open-telemetry.md
generated
vendored
Normal file
405
node_modules/next/dist/docs/01-app/02-guides/open-telemetry.md
generated
vendored
Normal 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
node_modules/next/dist/docs/01-app/02-guides/package-bundling.md
generated
vendored
Normal file
311
node_modules/next/dist/docs/01-app/02-guides/package-bundling.md
generated
vendored
Normal 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 it’s 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>
|
||||
114
node_modules/next/dist/docs/01-app/02-guides/ppr-platform-guide.md
generated
vendored
Normal file
114
node_modules/next/dist/docs/01-app/02-guides/ppr-platform-guide.md
generated
vendored
Normal file
@@ -0,0 +1,114 @@
|
||||
---
|
||||
title: Implementing Partial Prerendering on your platform
|
||||
nav_title: PPR Platform Guide
|
||||
description: A guide for platform engineers on implementing PPR support, from basic origin rendering to optimized CDN integration.
|
||||
related:
|
||||
description: Related guides and references.
|
||||
links:
|
||||
- app/guides/rendering-philosophy
|
||||
- app/guides/streaming
|
||||
- app/guides/deploying-to-platforms
|
||||
- app/api-reference/config/next-config-js/adapterPath
|
||||
---
|
||||
|
||||
Partial Prerendering (PPR) combines static and dynamic rendering in a single route. At build time, Next.js generates a static HTML shell and a `postponedState` blob for each PPR-enabled route. At request time, the shell is served immediately and dynamic portions are rendered and streamed to the client.
|
||||
|
||||
This page explains how platforms can implement PPR support at different levels of sophistication.
|
||||
|
||||
## How PPR Works
|
||||
|
||||
### Build time
|
||||
|
||||
For each PPR route, Next.js produces:
|
||||
|
||||
- A **static HTML shell** containing all the content that can be prerendered, with [Suspense](/docs/app/guides/streaming#what-is-streaming) fallbacks where dynamic content will appear.
|
||||
- A **`postponedState`** value: a serialized string. Treat it as opaque: pass it through without parsing or modifying it. Altering `postponedState` produces incorrect dynamic rendering output.
|
||||
- An **RSC payload** for the static portions of the page.
|
||||
|
||||
### Request time
|
||||
|
||||
When a request arrives for a PPR route:
|
||||
|
||||
1. The server sends the static HTML shell to the client immediately.
|
||||
2. The server resumes rendering the dynamic portions using the postponed state.
|
||||
3. Dynamic content is streamed to the client, allowing React to hydrate the deferred Suspense boundaries.
|
||||
|
||||
The client sees the static shell instantly, then dynamic content appears as it resolves.
|
||||
|
||||
## Storing PPR Artifacts
|
||||
|
||||
Each PPR route requires two artifacts to be stored together:
|
||||
|
||||
1. The static HTML shell.
|
||||
2. The `postponedState` blob.
|
||||
|
||||
These must be stored and updated atomically. When a PPR route is revalidated (via [time-based](/docs/app/guides/incremental-static-regeneration) or [on-demand revalidation](/docs/app/api-reference/functions/revalidateTag)), Next.js regenerates both the shell and the postponed state together. Serving a new shell with an old postponed state, or vice versa, will produce incorrect dynamic content.
|
||||
|
||||
Use [`requestMeta.onCacheEntryV2`](/docs/app/api-reference/adapters/implementing-ppr-in-an-adapter) in your adapter to observe cache updates and propagate them to your storage backend.
|
||||
|
||||
## Origin-Only Implementation
|
||||
|
||||
**This is the simplest approach and works on every platform that supports streaming HTTP responses.**
|
||||
|
||||
All requests go directly to the Next.js server. The server reads the shell from its local cache, sends it, then renders and streams the dynamic content. This is what `next start` does by default.
|
||||
|
||||
No additional infrastructure is needed. If your platform supports streaming HTTP responses, it supports PPR.
|
||||
|
||||
## CDN Shell + Origin Compute
|
||||
|
||||
For better TTFB, the static HTML shell can be cached at the CDN edge. When a request arrives:
|
||||
|
||||
1. The CDN serves the cached shell immediately (edge latency).
|
||||
2. The CDN sends a resume request to the origin server (ideally in parallel with streaming the shell).
|
||||
3. The origin server renders only the dynamic portions and streams them back.
|
||||
4. The CDN concatenates the shell and dynamic content into a single streaming response to the client.
|
||||
|
||||
This requires the CDN to support a mechanism for combining cached and dynamic content in a single streaming response. The static shell TTFB drops to edge latency while dynamic content still streams from origin.
|
||||
|
||||
For the lowest possible latency, the shell can be served from edge storage (for example, a KV store populated during `onBuildComplete`) rather than from a CDN cache. This is a platform architecture decision and does not require any changes to the Next.js application.
|
||||
|
||||
## The Resume Protocol
|
||||
|
||||
The **resume protocol** tells the Next.js handler to skip the shell and render only the dynamic portions. It is used by CDN-to-origin architectures and adapter-based deployments that serve the shell separately.
|
||||
|
||||
In standard `next start`, the server handles both the shell and dynamic render in a single pass automatically.
|
||||
|
||||
### CDN-to-origin
|
||||
|
||||
When the CDN makes an HTTP request to a separate Next.js origin:
|
||||
|
||||
- Send a **POST** request to the route with the header `next-resume: 1`.
|
||||
- Include the `postponedState` blob as the **request body**.
|
||||
- The server will render only the deferred Suspense boundaries and stream the result.
|
||||
|
||||
> **Good to know:** When a POST request combines a Server Action with a PPR resume, the request body contains the postponed state followed by the action body. The `x-next-resume-state-length` header carries the byte length of the postponed state prefix so the handler can separate the two. For a pure PPR resume (the common case), the entire request body is the postponed state and this header is not needed.
|
||||
|
||||
### Adapter-based
|
||||
|
||||
When the platform invokes the handler function directly:
|
||||
|
||||
- Call the entrypoint handler with `req.method` set to `'POST'`, the `next-resume: 1` header on the request, and the `postponedState` as the request body. (You can also pass `requestMeta: { postponed: postponedState }` as the third argument to the handler invocation, which is equivalent but bypasses the HTTP layer entirely.)
|
||||
- The handler renders only the deferred Suspense boundaries and streams the result to `res`.
|
||||
- No HTTP round-trip is needed: the handler is invoked in-process.
|
||||
|
||||
### Finding PPR routes in build output
|
||||
|
||||
In the [adapter output](/docs/app/api-reference/adapters/output-types), PPR routes are identified by `renderingMode: 'PARTIALLY_STATIC'` in the prerenders array. Iterate `outputs.prerenders` to find these entries and read `fallback.postponedState`.
|
||||
|
||||
`pprChain.headers` contains the headers needed for the resume protocol: `{ 'next-resume': '1' }`.
|
||||
|
||||
For detailed implementation with code examples, see [Implementing PPR in an Adapter](/docs/app/api-reference/adapters/implementing-ppr-in-an-adapter).
|
||||
|
||||
## Implementation Checklist
|
||||
|
||||
1. **Read PPR outputs at build time.** In your adapter's `onBuildComplete`, identify prerenders with `renderingMode: 'PARTIALLY_STATIC'`. Store the shell HTML and `postponedState` in your cache.
|
||||
|
||||
2. **Serve the shell at request time.** For incoming requests to PPR routes, serve the cached shell immediately and begin streaming.
|
||||
|
||||
3. **Resume dynamic rendering.** For CDN-to-origin: send a POST request to the Next.js handler with the `next-resume: 1` header and the postponed state as the body. For adapter-based: call the handler directly with POST method and the postponed state in the request body (or pass `requestMeta: { postponed: postponedState }` to the handler). Stream the response back to the client.
|
||||
|
||||
4. **Handle cache updates.** Use `requestMeta.onCacheEntryV2` to capture new shell + postponed state pairs after revalidation, and update your cache atomically.
|
||||
|
||||
5. **Support graceful degradation.** If the postponed state is unavailable or stale, fall back to a full server render. The user gets a complete page without the shell-first optimization.
|
||||
|
||||
For the complete adapter API reference and implementation examples, see the [Deployment Adapter API](/docs/app/api-reference/config/next-config-js/adapterPath).
|
||||
368
node_modules/next/dist/docs/01-app/02-guides/prefetching.md
generated
vendored
Normal file
368
node_modules/next/dist/docs/01-app/02-guides/prefetching.md
generated
vendored
Normal 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
node_modules/next/dist/docs/01-app/02-guides/preserving-ui-state.md
generated
vendored
Normal file
503
node_modules/next/dist/docs/01-app/02-guides/preserving-ui-state.md
generated
vendored
Normal 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.labs.vercel.dev) ([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
node_modules/next/dist/docs/01-app/02-guides/production-checklist.md
generated
vendored
Normal file
152
node_modules/next/dist/docs/01-app/02-guides/production-checklist.md
generated
vendored
Normal 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
node_modules/next/dist/docs/01-app/02-guides/progressive-web-apps.md
generated
vendored
Normal file
670
node_modules/next/dist/docs/01-app/02-guides/progressive-web-apps.md
generated
vendored
Normal 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, we’ll add some of the imports and utilities we’ll need. It’s 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
|
||||
}
|
||||
```
|
||||
|
||||
Let’s 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, let’s 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, let’s 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'",
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Let’s 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
node_modules/next/dist/docs/01-app/02-guides/public-static-pages.md
generated
vendored
Normal file
253
node_modules/next/dist/docs/01-app/02-guides/public-static-pages.md
generated
vendored
Normal 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
node_modules/next/dist/docs/01-app/02-guides/redirecting.md
generated
vendored
Normal file
647
node_modules/next/dist/docs/01-app/02-guides/redirecting.md
generated
vendored
Normal 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.
|
||||
75
node_modules/next/dist/docs/01-app/02-guides/rendering-philosophy.md
generated
vendored
Normal file
75
node_modules/next/dist/docs/01-app/02-guides/rendering-philosophy.md
generated
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
---
|
||||
title: Next.js Rendering Philosophy
|
||||
nav_title: Rendering Philosophy
|
||||
description: Learn how Next.js treats static and dynamic rendering as a spectrum at the component level, and what this means for deployment.
|
||||
related:
|
||||
description: Learn more about the features discussed on this page.
|
||||
links:
|
||||
- app/getting-started/caching
|
||||
- app/guides/streaming
|
||||
- app/guides/self-hosting
|
||||
- app/guides/deploying-to-platforms
|
||||
---
|
||||
|
||||
## Static and Dynamic as a Spectrum
|
||||
|
||||
Most web frameworks draw a hard line between static and dynamic at the route level. A page is either prerendered at build time or server-rendered at request time. This model is simple to understand and simple to deploy: you upload static files to a CDN and point dynamic routes at a server.
|
||||
|
||||
Next.js takes a different approach: **the boundary between static and dynamic is at the component level, not the route level.** A single page can have a static shell that loads instantly and dynamic sections that stream in as they resolve. A cached function can live inside a dynamic route. A static page can be updated without a redeploy.
|
||||
|
||||
This is what Partial Prerendering, [Cache Components](/docs/app/getting-started/caching) (`use cache`), and [on-demand revalidation](/docs/app/api-reference/functions/revalidateTag) enable. They are not incremental features: they represent a rendering model that treats static and dynamic as a spectrum rather than a binary choice.
|
||||
|
||||
## What This Enables
|
||||
|
||||
This model benefits developers and users in concrete ways:
|
||||
|
||||
- **Faster perceived load times.** The static shell renders immediately while dynamic content streams in. Users see useful content right away instead of waiting for the entire page to render.
|
||||
- **Incremental caching.** Developers can add caching and revalidation incrementally, without deciding upfront at build time whether a route is static or dynamic. Any page can be revalidated on demand, and any function can be cached with [`use cache`](/docs/app/api-reference/directives/use-cache).
|
||||
- **Granular caching.** Cache a function with [`use cache`](/docs/app/api-reference/directives/use-cache), not a route. Revalidate a [tag](/docs/app/api-reference/functions/revalidateTag), not a deployment. This means an expensive database query can be cached independently of the rest of the page.
|
||||
|
||||
## The Trade-Off
|
||||
|
||||
Web frameworks differ in where they place the boundary between static and dynamic content. Each approach makes a different trade-off between developer flexibility and infrastructure complexity.
|
||||
|
||||
### Build-time prerendering
|
||||
|
||||
Every page is generated at build time. The output is static files that can be served from any CDN or file server with zero runtime infrastructure. Dynamic content, if any, requires client-side fetching after the page loads. This is the simplest model to deploy, but every content change requires a rebuild and redeploy.
|
||||
|
||||
### Route-level boundaries
|
||||
|
||||
Each route chooses whether it is static or dynamic. Static routes are prerendered at build time, dynamic routes are server-rendered per request. The infrastructure splits cleanly: static files go to a CDN, dynamic routes go to a server. This is straightforward to reason about but the choice is all-or-nothing per route. A mostly-static page with one dynamic element (a user greeting, a live price) must either be fully dynamic or fetch that element on the client after load.
|
||||
|
||||
### Component-level boundaries
|
||||
|
||||
This is the approach Next.js takes. Static and dynamic content coexist within a single streaming response. A page can have a static shell that loads instantly, a cached function that revalidates independently, and a dynamic section that streams in as it resolves, all without the developer splitting anything into separate routes or client-side fetches.
|
||||
|
||||
The trade-off is infrastructure complexity. A finer-grained rendering boundary transfers complexity from application code into the hosting platform. The infrastructure requirements described below exist because of this choice.
|
||||
|
||||
## Infrastructure Implications
|
||||
|
||||
The component-level rendering model has direct implications for platforms hosting Next.js applications:
|
||||
|
||||
- **Streaming** is required because static and dynamic content are served in a single response. The server sends initial content first, then streams dynamic portions as they resolve. See [Streaming](/docs/app/guides/streaming) for details.
|
||||
- **Cache coordination** is required when running multiple instances because any cached content can be invalidated on demand via [`revalidateTag()`](/docs/app/api-reference/functions/revalidateTag) or [`revalidatePath()`](/docs/app/api-reference/functions/revalidatePath). See [How Revalidation Works](/docs/app/guides/how-revalidation-works) for the architecture.
|
||||
- **Cache consistency** matters because revalidation regenerates both the HTML response and the RSC payload (the serialized React Server Components data used for client-side navigation). If these get out of sync, users may see inconsistent data during navigation. See [How Revalidation Works](/docs/app/guides/how-revalidation-works) for consistency requirements.
|
||||
- **PPR shell delivery at CDN latency** can require additional platform integration to store the static shell separately and resume dynamic rendering correctly. See [PPR Platform Guide](/docs/app/guides/ppr-platform-guide) for implementation details.
|
||||
|
||||
Each of these infrastructure requirements maps directly to a capability: streaming enables progressive delivery, cache coordination propagates invalidations across instances, cache consistency keeps HTML and RSC aligned, and PPR-at-edge often requires extra shell/resume integration.
|
||||
|
||||
## Portability and Fidelity
|
||||
|
||||
Next.js runs as a Node.js server process, and a single process handles every feature correctly. Streaming support enables progressive delivery of Server Components and PPR; without it, responses are buffered but features still work. Additional infrastructure investments (CDN caching, edge compute, shared cache) improve performance and, in multi-instance deployments, reduce consistency gaps.
|
||||
|
||||
To make this concrete, we distinguish between two types of platform support:
|
||||
|
||||
**Functional fidelity** means every Next.js feature works correctly on the platform. The [adapter test suite](/docs/app/api-reference/adapters/testing-adapters) is the contract: if a platform's adapter passes the tests, it has full functional fidelity. This is binary: it passes or it doesn't. The test suite is open to contributions from platform partners to ensure it is fair and complete.
|
||||
|
||||
**Performance fidelity** means features achieve their optimal performance characteristics. For example, PPR's static shell served at CDN latency rather than origin latency, or ISR serving stale content instantly while revalidating in the background with sub-second propagation. Performance fidelity is a spectrum: every platform will achieve different levels based on their architecture, and platforms will improve over time.
|
||||
|
||||
A platform that achieves functional fidelity is a fully supported deployment target for Next.js. Performance fidelity is how platforms differentiate. See [Deploying to Platforms](/docs/app/guides/deploying-to-platforms) for the full feature compatibility matrix.
|
||||
|
||||
## CDN Feature Compatibility
|
||||
|
||||
Many CDNs have useful primitives for deeper Next.js integration (edge compute, key-value storage, blob storage), but end-to-end PPR resume support is still emerging and may require bespoke platform work. Most community adapters today deploy Next.js as a Node.js server without leveraging these CDN-specific primitives. See the [Deploying](/docs/app/getting-started/deploying#adapters) page for the current list of adapters.
|
||||
|
||||
See [Deploying to Platforms](/docs/app/guides/deploying-to-platforms#cdn-infrastructure-compatibility) for the full CDN compatibility table and [CDN Caching](/docs/app/guides/cdn-caching) for caching behavior details.
|
||||
136
node_modules/next/dist/docs/01-app/02-guides/sass.md
generated
vendored
Normal file
136
node_modules/next/dist/docs/01-app/02-guides/sass.md
generated
vendored
Normal 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
node_modules/next/dist/docs/01-app/02-guides/scripts.md
generated
vendored
Normal file
434
node_modules/next/dist/docs/01-app/02-guides/scripts.md
generated
vendored
Normal 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>
|
||||
336
node_modules/next/dist/docs/01-app/02-guides/self-hosting.md
generated
vendored
Normal file
336
node_modules/next/dist/docs/01-app/02-guides/self-hosting.md
generated
vendored
Normal file
@@ -0,0 +1,336 @@
|
||||
---
|
||||
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 Next.js server cache**. By default, this cache is stored on the local filesystem (on disk) of each Next.js server instance.
|
||||
|
||||
This works automatically for a single self-hosted `next start` instance with persistent local disk. If you run multiple instances, use ephemeral compute, or place a CDN/reverse proxy in front of Next.js, also review [Configuring Caching](#configuring-caching), [Multi-Instance Cache Coordination](#multi-instance-cache-coordination), and [Usage with CDNs](#usage-with-cdns).
|
||||
|
||||
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. To leverage this at the CDN layer, your CDN/reverse proxy must respect these directives and cache-key variability ([CDN Caching](/docs/app/guides/cdn-caching)); otherwise, responses may bypass CDN caching or serve stale/mismatched variants during client-side navigation.
|
||||
- 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. On ephemeral compute platforms (common serverless setups), local disk is often non-persistent or unavailable, so this cache is effectively short-lived and per-instance. 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:
|
||||
|
||||
For production deployments, use this as a starting point and extend it with durable storage, eviction policies, error handling, and distributed tag coordination. See [Custom Next.js Cache Handler](/docs/app/api-reference/config/next-config-js/incrementalCacheHandlerPath) and the [Redis `cacheHandler` example](https://github.com/vercel/next.js/tree/canary/examples/cache-handler-redis). If you are configuring backends for `'use cache'` directives, use [`cacheHandlers`](/docs/app/api-reference/config/next-config-js/cacheHandlers).
|
||||
|
||||
```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',
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Beyond nginx, ensure that your entire infrastructure supports streaming end-to-end:
|
||||
|
||||
- **Load balancers** must support chunked transfer encoding or HTTP/2 streaming. Some cloud load balancers (for example, AWS ALB with Lambda integration) may buffer responses by default.
|
||||
- **Reverse proxies** between the load balancer and Next.js must also pass through chunked responses without buffering.
|
||||
- If using [Partial Prerendering](/docs/app/guides/ppr-platform-guide), streaming support is required. Without it, the static shell and dynamic content are delivered together after the full render completes, eliminating PPR's time-to-first-byte advantage.
|
||||
|
||||
## Multi-Instance Cache Coordination
|
||||
|
||||
In addition to the [multi-server configuration](/docs/app/guides/self-hosting#multi-server-deployments) above (encryption key, deployment ID, shared cache), App Router deployments with multiple instances need cache tag coordination.
|
||||
|
||||
By default, calling [`revalidateTag()`](/docs/app/api-reference/functions/revalidateTag) on one instance only invalidates the cache on that instance. Other instances continue serving stale content until they independently discover the invalidation.
|
||||
|
||||
To coordinate tag invalidation across instances, implement the [`refreshTags()`](/docs/app/api-reference/config/next-config-js/cacheHandlers#refreshtags) method in your [custom cache handler](/docs/app/api-reference/config/next-config-js/cacheHandlers). This method is called before each request and should sync tag state from shared storage (like Redis) so all instances learn about invalidations promptly.
|
||||
|
||||
For a detailed explanation of the tag architecture, see [How Revalidation Works](/docs/app/guides/how-revalidation-works).
|
||||
|
||||
## 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.
|
||||
|
||||
For detailed guidance on CDN caching behavior, graceful degradation, and cache variability, see [CDN Caching](/docs/app/guides/cdn-caching). For Partial Prerendering support on different platforms, see the [PPR Platform Guide](/docs/app/guides/ppr-platform-guide) and the [Deployment Adapter API](/docs/app/api-reference/config/next-config-js/adapterPath).
|
||||
|
||||
</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. The Next.js server will finish in-flight requests and execute any pending `after()` callbacks before exiting. Platforms should allow a configurable drain period (10-30 seconds is recommended) to ensure all background work completes.
|
||||
|
||||
</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>
|
||||
424
node_modules/next/dist/docs/01-app/02-guides/single-page-applications.md
generated
vendored
Normal file
424
node_modules/next/dist/docs/01-app/02-guides/single-page-applications.md
generated
vendored
Normal file
@@ -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. We’ll 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 page’s 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 React’s `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 React’s [`use` API](https://react.dev/reference/react/use).
|
||||
|
||||
Next.js can start data fetching early on the server. In this example, that’s 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`. SWR’s 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
node_modules/next/dist/docs/01-app/02-guides/static-exports.md
generated
vendored
Normal file
367
node_modules/next/dist/docs/01-app/02-guides/static-exports.md
generated
vendored
Normal 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"` |
|
||||
790
node_modules/next/dist/docs/01-app/02-guides/streaming.md
generated
vendored
Normal file
790
node_modules/next/dist/docs/01-app/02-guides/streaming.md
generated
vendored
Normal file
@@ -0,0 +1,790 @@
|
||||
---
|
||||
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
|
||||
- app/guides/rendering-philosophy
|
||||
---
|
||||
|
||||
{/* 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
node_modules/next/dist/docs/01-app/02-guides/tailwind-v3-css.md
generated
vendored
Normal file
173
node_modules/next/dist/docs/01-app/02-guides/tailwind-v3-css.md
generated
vendored
Normal 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
node_modules/next/dist/docs/01-app/02-guides/testing/cypress.md
generated
vendored
Normal file
319
node_modules/next/dist/docs/01-app/02-guides/testing/cypress.md
generated
vendored
Normal 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
node_modules/next/dist/docs/01-app/02-guides/testing/index.md
generated
vendored
Normal file
26
node_modules/next/dist/docs/01-app/02-guides/testing/index.md
generated
vendored
Normal 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
node_modules/next/dist/docs/01-app/02-guides/testing/jest.md
generated
vendored
Normal file
423
node_modules/next/dist/docs/01-app/02-guides/testing/jest.md
generated
vendored
Normal 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
node_modules/next/dist/docs/01-app/02-guides/testing/playwright.md
generated
vendored
Normal file
150
node_modules/next/dist/docs/01-app/02-guides/testing/playwright.md
generated
vendored
Normal 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
node_modules/next/dist/docs/01-app/02-guides/testing/vitest.md
generated
vendored
Normal file
243
node_modules/next/dist/docs/01-app/02-guides/testing/vitest.md
generated
vendored
Normal 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
node_modules/next/dist/docs/01-app/02-guides/third-party-libraries.md
generated
vendored
Normal file
478
node_modules/next/dist/docs/01-app/02-guides/third-party-libraries.md
generated
vendored
Normal 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
node_modules/next/dist/docs/01-app/02-guides/upgrading/codemods.md
generated
vendored
Normal file
721
node_modules/next/dist/docs/01-app/02-guides/upgrading/codemods.md
generated
vendored
Normal 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
node_modules/next/dist/docs/01-app/02-guides/upgrading/index.md
generated
vendored
Normal file
7
node_modules/next/dist/docs/01-app/02-guides/upgrading/index.md
generated
vendored
Normal 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
node_modules/next/dist/docs/01-app/02-guides/upgrading/version-14.md
generated
vendored
Normal file
37
node_modules/next/dist/docs/01-app/02-guides/upgrading/version-14.md
generated
vendored
Normal 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
node_modules/next/dist/docs/01-app/02-guides/upgrading/version-15.md
generated
vendored
Normal file
627
node_modules/next/dist/docs/01-app/02-guides/upgrading/version-15.md
generated
vendored
Normal 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)
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
1237
node_modules/next/dist/docs/01-app/02-guides/upgrading/version-16.md
generated
vendored
Normal file
1237
node_modules/next/dist/docs/01-app/02-guides/upgrading/version-16.md
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
270
node_modules/next/dist/docs/01-app/02-guides/videos.md
generated
vendored
Normal file
270
node_modules/next/dist/docs/01-app/02-guides/videos.md
generated
vendored
Normal 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.
|
||||
393
node_modules/next/dist/docs/01-app/02-guides/view-transitions.md
generated
vendored
Normal file
393
node_modules/next/dist/docs/01-app/02-guides/view-transitions.md
generated
vendored
Normal file
@@ -0,0 +1,393 @@
|
||||
---
|
||||
title: Designing view transitions
|
||||
description: Learn how to use view transitions to communicate meaning during navigation, loading, and content changes in a Next.js app.
|
||||
nav_title: View transitions
|
||||
---
|
||||
|
||||
In web apps, route changes replace the entire page at once. One set of elements disappears, another appears, with no visual connection between them. A user selects a photo thumbnail to view it in detail on another page. They are the same image, but nothing on screen communicates that.
|
||||
|
||||
Apps that need these transitions typically rely on complex animation libraries that manage mount/unmount lifecycles, track element positions across routes, and coordinate timing manually, to animate how elements enter, exit, and move between states.
|
||||
|
||||
React's `<ViewTransition>` component integrates with the browser's [View Transitions API](https://developer.mozilla.org/en-US/docs/Web/API/View_Transition_API) to handle this declaratively. You name the elements that should persist, and the browser animates between their old and new positions.
|
||||
|
||||
This guide walks through four patterns that cover the most common cases: morphing shared elements, animating loading states, adding directional navigation, and crossfading content within the same route.
|
||||
|
||||
## Example
|
||||
|
||||
As an example, we'll build a photography gallery called _Frames_.
|
||||
|
||||
We'll start by morphing a thumbnail into a hero image (shared elements), then animate the loading skeleton into real content (Suspense reveals), add directional slides for forward and back navigation (route transitions), and finish with crossfades for switching between photographer tabs (same-route transitions).
|
||||
|
||||
You can find the resources used in this example here:
|
||||
|
||||
- [Demo](https://react-view-transitions-demo.labs.vercel.dev)
|
||||
- [Code](https://github.com/vercel-labs/react-view-transitions-demo)
|
||||
|
||||
Before starting, enable view transitions in your Next.js config:
|
||||
|
||||
```ts filename="next.config.ts"
|
||||
import type { NextConfig } from 'next'
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
experimental: {
|
||||
viewTransition: true,
|
||||
},
|
||||
}
|
||||
|
||||
export default nextConfig
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> The View Transitions API is supported in all major browsers, though some animations may behave differently in Safari. Without browser support, your application works normally, the transitions simply do not animate.
|
||||
|
||||
Then import the `ViewTransition` component from React:
|
||||
|
||||
```tsx
|
||||
import { ViewTransition } from 'react'
|
||||
```
|
||||
|
||||
`<ViewTransition>` animations are activated by [Transitions](https://react.dev/reference/react/useTransition), [`<Suspense>`](https://react.dev/reference/react/Suspense), and [`useDeferredValue`](https://react.dev/reference/react/useDeferredValue). Regular `setState` calls do not trigger them. In Next.js, route navigations are transitions, so `<ViewTransition>` animations activate automatically during navigation.
|
||||
|
||||
### Step 1: Morph a thumbnail into a hero image
|
||||
|
||||
The gallery displays photos in a grid. Clicking a photo opens a detail page with a larger version of the same image. Without transitions, the thumbnail disappears and the hero appears. Nothing connects them visually. The user has to scan the detail page to confirm they clicked the right photo.
|
||||
|
||||
In motion design, when an object persists across a cut, it communicates continuity. The viewer understands they are looking at the same thing, not a replacement. This is the most important transition pattern: **shared element morphing**.
|
||||
|
||||
Wrap both the grid thumbnail and the detail hero in `<ViewTransition>` with the same `name`:
|
||||
|
||||
```tsx filename="components/photo-grid.tsx"
|
||||
import { ViewTransition } from 'react'
|
||||
import Image from 'next/image'
|
||||
import Link from 'next/link'
|
||||
|
||||
function PhotoGrid({ photos }) {
|
||||
return (
|
||||
<div className="grid grid-cols-3 gap-3">
|
||||
{photos.map((photo) => (
|
||||
<Link key={photo.id} href={`/photo/${photo.id}`}>
|
||||
<ViewTransition name={`photo-${photo.id}`}>
|
||||
<Image src={photo.src} alt={photo.title} />
|
||||
</ViewTransition>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```tsx filename="app/photo/[id]/photo-content.tsx"
|
||||
import { ViewTransition } from 'react'
|
||||
import Image from 'next/image'
|
||||
|
||||
async function PhotoContent({ id }) {
|
||||
const photo = await getPhoto(id)
|
||||
|
||||
return (
|
||||
<ViewTransition name={`photo-${photo.id}`}>
|
||||
<Image src={photo.src} alt={photo.title} fill />
|
||||
</ViewTransition>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
The `name` prop creates identity. React finds elements with the same name on the old and new pages, then animates between their size and position automatically. No additional props are needed for the morph to work.
|
||||
|
||||
If we click a thumbnail now, the image scales and repositions from its grid cell to the hero slot. Navigating back reverses the morph. The user sees one object moving, not two objects swapping.
|
||||
|
||||
#### Customizing the morph animation
|
||||
|
||||
To control the morph CSS, add `share="morph"`. This assigns the `morph` class to the view transition, which you can target with CSS pseudo-elements. For example, to soften the morph mid-flight with a [`blur`](https://developer.mozilla.org/en-US/docs/Web/CSS/filter-function/blur) keyframe:
|
||||
|
||||
```tsx
|
||||
<ViewTransition name={`photo-${photo.id}`} share="morph">
|
||||
<Image src={photo.src} alt={photo.title} />
|
||||
</ViewTransition>
|
||||
```
|
||||
|
||||
```css filename="app/globals.css"
|
||||
::view-transition-group(.morph) {
|
||||
animation-duration: 400ms;
|
||||
}
|
||||
::view-transition-image-pair(.morph) {
|
||||
animation-name: via-blur;
|
||||
}
|
||||
@keyframes via-blur {
|
||||
30% {
|
||||
filter: blur(3px);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The blur hides pixel-level interpolation artifacts during the transition. At 400ms, the morph is slow enough to register but fast enough to feel direct.
|
||||
|
||||
### Step 2: Animate loading states with Suspense reveals
|
||||
|
||||
The photo detail page loads its content asynchronously. While data is in flight, a Suspense boundary shows a skeleton. When the data resolves, the skeleton is replaced by the real content.
|
||||
|
||||
Without a transition, the swap is instant. The skeleton vanishes and the content pops in.
|
||||
|
||||
In motion design, vertical direction encodes hierarchy. Content sliding up communicates arrival. Content sliding down communicates departure. The pair together creates a handoff: the placeholder yields to the real thing.
|
||||
|
||||
Wrap the Suspense fallback in a `ViewTransition` with an exit animation, and the content in a `ViewTransition` with an enter animation:
|
||||
|
||||
```tsx filename="app/photo/[id]/page.tsx"
|
||||
import { Suspense, ViewTransition } from 'react'
|
||||
|
||||
export default async function PhotoPage({ params }) {
|
||||
const { id } = await params
|
||||
|
||||
return (
|
||||
<Suspense
|
||||
fallback={
|
||||
<ViewTransition exit="slide-down">
|
||||
<PhotoContentSkeleton />
|
||||
</ViewTransition>
|
||||
}
|
||||
>
|
||||
<ViewTransition enter="slide-up" default="none">
|
||||
<PhotoContent id={id} />
|
||||
</ViewTransition>
|
||||
</Suspense>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
The `default="none"` prop prevents this `ViewTransition` from animating during unrelated transitions, like the shared element morph from Step 1. Without it, every transition on the page would trigger every `ViewTransition`'s animation.
|
||||
|
||||
The CSS animations use asymmetric timing. The exit is fast (150ms). The enter is slower (210ms) and delayed until the exit completes:
|
||||
|
||||
```css filename="app/globals.css"
|
||||
:root {
|
||||
--duration-exit: 150ms;
|
||||
--duration-enter: 210ms;
|
||||
}
|
||||
|
||||
::view-transition-old(.slide-down) {
|
||||
animation:
|
||||
var(--duration-exit) ease-out both fade reverse,
|
||||
var(--duration-exit) ease-out both slide-y reverse;
|
||||
}
|
||||
::view-transition-new(.slide-up) {
|
||||
animation:
|
||||
var(--duration-enter) ease-in var(--duration-exit) both fade,
|
||||
400ms ease-in both slide-y;
|
||||
}
|
||||
|
||||
@keyframes fade {
|
||||
from {
|
||||
filter: blur(3px);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
filter: blur(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
@keyframes slide-y {
|
||||
from {
|
||||
transform: translateY(10px);
|
||||
}
|
||||
to {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The asymmetry is deliberate. Old content should leave quickly so it does not compete for attention. New content should arrive more gently so the user has time to register it. The `var(--duration-exit)` delay on the enter animation means the new content waits for the old content to finish leaving before it appears.
|
||||
|
||||
If we refresh the page, the skeleton slides down and fades out, and a moment later the real content slides up and fades in.
|
||||
|
||||
### Step 3: Add directional motion for navigation
|
||||
|
||||
The gallery now has morphing images and animated loading states. But navigating between pages still has no directional signal. Forward and back navigations look identical. The user cannot tell from the animation whether they moved deeper into the app or returned to a previous page.
|
||||
|
||||
In film and animation, horizontal direction encodes spatial position. Moving left means progressing forward (like turning a page in a left-to-right language). Moving right means going back. This convention is so ingrained that violating it feels disorienting.
|
||||
|
||||
Use the `transitionTypes` prop on `<Link>` to tag forward navigations:
|
||||
|
||||
```tsx filename="components/photo-grid.tsx"
|
||||
<Link href={`/photo/${photo.id}`} transitionTypes={['nav-forward']}>
|
||||
{/* photo thumbnail */}
|
||||
</Link>
|
||||
```
|
||||
|
||||
The same pattern works for any navigation within the app. For example, previous/next arrows on a photo detail page can use `nav-back` and `nav-forward` to animate in the corresponding direction.
|
||||
|
||||
For links that return the user to a previous page, use `nav-back`:
|
||||
|
||||
```tsx filename="app/photo/[id]/page.tsx"
|
||||
<Link href="/" transitionTypes={['nav-back']}>
|
||||
← Gallery
|
||||
</Link>
|
||||
```
|
||||
|
||||
The transition type is not automatic. You decide which links are "forward" and which are "back" based on your app's navigation hierarchy.
|
||||
|
||||
Then wrap page content in a `ViewTransition` that maps transition types to directional animations:
|
||||
|
||||
```tsx filename="app/photo/[id]/page.tsx"
|
||||
<ViewTransition
|
||||
enter={{
|
||||
'nav-forward': 'nav-forward',
|
||||
'nav-back': 'nav-back',
|
||||
default: 'none',
|
||||
}}
|
||||
exit={{
|
||||
'nav-forward': 'nav-forward',
|
||||
'nav-back': 'nav-back',
|
||||
default: 'none',
|
||||
}}
|
||||
default="none"
|
||||
>
|
||||
{/* page content */}
|
||||
</ViewTransition>
|
||||
```
|
||||
|
||||
The `enter` and `exit` props accept an object keyed by transition type. When a navigation carries the `nav-forward` type, the exit animation slides old content left and the enter animation slides new content in from the right. The `default: "none"` ensures that transitions without a type (like initial page loads) produce no animation.
|
||||
|
||||
The CSS for directional slides:
|
||||
|
||||
```css filename="app/globals.css"
|
||||
::view-transition-old(.nav-forward) {
|
||||
--slide-offset: -60px;
|
||||
animation:
|
||||
150ms ease-in both fade reverse,
|
||||
400ms ease-in-out both slide reverse;
|
||||
}
|
||||
::view-transition-new(.nav-forward) {
|
||||
--slide-offset: 60px;
|
||||
animation:
|
||||
210ms ease-out 150ms both fade,
|
||||
400ms ease-in-out both slide;
|
||||
}
|
||||
|
||||
::view-transition-old(.nav-back) {
|
||||
--slide-offset: 60px;
|
||||
animation:
|
||||
150ms ease-in both fade reverse,
|
||||
400ms ease-in-out both slide reverse;
|
||||
}
|
||||
::view-transition-new(.nav-back) {
|
||||
--slide-offset: -60px;
|
||||
animation:
|
||||
210ms ease-out 150ms both fade,
|
||||
400ms ease-in-out both slide;
|
||||
}
|
||||
|
||||
@keyframes slide {
|
||||
from {
|
||||
translate: var(--slide-offset);
|
||||
}
|
||||
to {
|
||||
translate: 0;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The 60px offset is enough to communicate direction without making the user track a fast-moving element across the screen.
|
||||
|
||||
#### Anchoring the header
|
||||
|
||||
During directional slides, the header should not move. A sliding header breaks the user's spatial anchor. They need one fixed reference point to understand that the _content_ moved, not the entire viewport.
|
||||
|
||||
Assign the header a `viewTransitionName` and suppress its animation in CSS:
|
||||
|
||||
```tsx filename="components/header.tsx"
|
||||
<header style={{ viewTransitionName: 'site-header' }}>
|
||||
{/* navigation links */}
|
||||
</header>
|
||||
```
|
||||
|
||||
```css filename="app/globals.css"
|
||||
::view-transition-group(site-header) {
|
||||
animation: none;
|
||||
z-index: 100;
|
||||
}
|
||||
::view-transition-old(site-header) {
|
||||
display: none;
|
||||
}
|
||||
::view-transition-new(site-header) {
|
||||
animation: none;
|
||||
}
|
||||
```
|
||||
|
||||
The `display: none` on the old snapshot prevents a flash where both old and new headers are briefly visible. The `z-index: 100` ensures the header renders above the sliding content.
|
||||
|
||||
If we navigate forward to a photo, content slides left. If we click the "← Gallery" link (tagged with `nav-back`), content slides right. The header stays fixed throughout both transitions.
|
||||
|
||||
Browser-initiated back navigations (the back button or swipe gestures) do not carry a transition type, so the directional slide does not play. The shared element morph from Step 1 still applies if both pages have matching `name` props.
|
||||
|
||||
#### Respecting reduced motion
|
||||
|
||||
Directional slides simulate physical movement across the viewport. This is the most common trigger for motion sensitivity. Morphs, reveals, and crossfades carry less risk since they affect smaller areas or rely on opacity rather than position.
|
||||
|
||||
The simplest approach is to disable all animation durations:
|
||||
|
||||
```css filename="app/globals.css"
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
::view-transition-old(*),
|
||||
::view-transition-new(*),
|
||||
::view-transition-group(*) {
|
||||
animation-duration: 0s !important;
|
||||
animation-delay: 0s !important;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Without animation, content swaps instantly, which is the browser's default behavior. A more refined approach would preserve crossfades and opacity transitions while removing positional movement. See ["No Motion Isn't Always prefers-reduced-motion"](https://css-tricks.com/nuking-motion-with-prefers-reduced-motion/) for more on this.
|
||||
|
||||
### Step 4: Crossfade content within the same route
|
||||
|
||||
The gallery has a photographer section with tabs. Each tab shows a different photographer's photos, but the route structure is the same: `/collection/[slug]`. Clicking between tabs does not feel like navigating to a new page. It feels like switching content within the same container.
|
||||
|
||||
A directional slide would be wrong here. Slides communicate "going to a new place." A crossfade communicates "same place, different content." The container persists (continuity), only the grid inside changes (swap).
|
||||
|
||||
Use a `ViewTransition` with `key` set to the current slug. When the key changes, React triggers a transition between the old and new content:
|
||||
|
||||
```tsx filename="app/collection/[slug]/page.tsx"
|
||||
import { Suspense, ViewTransition } from 'react'
|
||||
|
||||
export default async function CollectionPage({ params }) {
|
||||
const { slug } = await params
|
||||
|
||||
return (
|
||||
<Suspense fallback={<CollectionGridSkeleton />}>
|
||||
<ViewTransition
|
||||
key={slug}
|
||||
name="collection-content"
|
||||
share="auto"
|
||||
enter="auto"
|
||||
default="none"
|
||||
>
|
||||
<CollectionGrid slug={slug} />
|
||||
</ViewTransition>
|
||||
</Suspense>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
The `share="auto"` and `enter="auto"` props tell React to use its default crossfade animation. The `name` prop gives the container an identity so React knows what to animate. The `key={slug}` change is what triggers the transition.
|
||||
|
||||
If we click between photographer tabs, the grid crossfades. The tab bar and surrounding layout do not move. Only the photo grid transitions between states.
|
||||
|
||||
## Next steps
|
||||
|
||||
You now know how to use view transitions to communicate meaning during navigation. Shared elements communicate continuity across routes. Suspense reveals animate loading handoffs. Directional slides encode navigation history. Crossfades signal content changes within the same location.
|
||||
|
||||
Each pattern answers a different question for the user:
|
||||
|
||||
| Pattern | What it communicates |
|
||||
| ---------------------- | ------------------------------- |
|
||||
| Shared element (morph) | "Same thing, going deeper" |
|
||||
| Suspense reveal | "Data loaded" |
|
||||
| Directional slide | "Going forward / coming back" |
|
||||
| Same-route crossfade | "Same place, different content" |
|
||||
|
||||
For API details and more patterns:
|
||||
|
||||
- [View transition configuration](/docs/app/api-reference/config/next-config-js/viewTransition)
|
||||
- [Link `transitionTypes` prop](/docs/app/api-reference/components/link#transitiontypes)
|
||||
- [`useRouter`](/docs/app/api-reference/functions/use-router) — also supports `transitionTypes` in `push()` and `replace()`
|
||||
- [React `ViewTransition` component](https://react.dev/reference/react/ViewTransition)
|
||||
- [Complete CSS from this guide](https://github.com/vercel-labs/react-view-transitions-demo/blob/main/src/app/globals.css) — all keyframes and view transition rules in one file
|
||||
6
node_modules/next/dist/docs/01-app/03-api-reference/01-directives/index.md
generated
vendored
Normal file
6
node_modules/next/dist/docs/01-app/03-api-reference/01-directives/index.md
generated
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
title: Directives
|
||||
description: Directives are used to modify the behavior of your Next.js application.
|
||||
---
|
||||
|
||||
The following directives are available:
|
||||
176
node_modules/next/dist/docs/01-app/03-api-reference/01-directives/use-cache-private.md
generated
vendored
Normal file
176
node_modules/next/dist/docs/01-app/03-api-reference/01-directives/use-cache-private.md
generated
vendored
Normal file
@@ -0,0 +1,176 @@
|
||||
---
|
||||
title: 'use cache: private'
|
||||
description: 'Learn how to use the "use cache: private" directive to cache functions that access runtime request APIs.'
|
||||
version: experimental
|
||||
related:
|
||||
title: Related
|
||||
description: View related API references.
|
||||
links:
|
||||
- app/api-reference/directives/use-cache
|
||||
- app/api-reference/config/next-config-js/cacheComponents
|
||||
- app/api-reference/functions/cacheLife
|
||||
- app/api-reference/functions/cacheTag
|
||||
---
|
||||
|
||||
The `'use cache: private'` directive allows functions to access runtime request APIs like `cookies()`, `headers()`, and `searchParams` within a cached scope. However, results are **never stored on the server**, they're cached only in the browser's memory and do not persist across page reloads.
|
||||
|
||||
Reach for `'use cache: private'` when:
|
||||
|
||||
- You want to cache a function that already accesses runtime data, and refactoring to [move the runtime access outside and pass values as arguments](/docs/app/getting-started/caching#working-with-runtime-apis) is not practical.
|
||||
- Compliance requirements prevent storing certain data on the server, even temporarily
|
||||
|
||||
Because this directive accesses runtime data, the function executes on every server render and is excluded from running during [static shell](/docs/app/getting-started/caching#how-rendering-works) generation.
|
||||
|
||||
It is **not** possible to configure custom cache handlers for `'use cache: private'`.
|
||||
|
||||
For a comparison of the different cache directives, see [How `use cache: remote` differs from `use cache` and `use cache: private`](/docs/app/api-reference/directives/use-cache-remote#how-use-cache-remote-differs-from-use-cache-and-use-cache-private).
|
||||
|
||||
> **Good to know**: This directive is marked as `experimental` because it depends on runtime prefetching, which is not yet stable. Runtime prefetching is an upcoming feature that will let the router prefetch past the [static shell](/docs/app/getting-started/caching#how-rendering-works) into **any** cached scope, not just private caches.
|
||||
|
||||
## Usage
|
||||
|
||||
To use `'use cache: private'`, enable the [`cacheComponents`](/docs/app/api-reference/config/next-config-js/cacheComponents) flag in your `next.config.ts` file:
|
||||
|
||||
```tsx filename="next.config.ts" switcher
|
||||
import type { NextConfig } from 'next'
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
cacheComponents: true,
|
||||
}
|
||||
|
||||
export default nextConfig
|
||||
```
|
||||
|
||||
```jsx filename="next.config.js" switcher
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
cacheComponents: true,
|
||||
}
|
||||
|
||||
export default nextConfig
|
||||
```
|
||||
|
||||
Then add `'use cache: private'` to your function along with a `cacheLife` configuration.
|
||||
|
||||
> **Good to know**: This directive is not available in Route Handlers.
|
||||
|
||||
### Basic example
|
||||
|
||||
In this example, we demonstrate that you can access cookies within a `'use cache: private'` scope:
|
||||
|
||||
```tsx filename="app/product/[id]/page.tsx" switcher
|
||||
import { Suspense } from 'react'
|
||||
import { cookies } from 'next/headers'
|
||||
import { cacheLife, cacheTag } from 'next/cache'
|
||||
|
||||
export async function generateStaticParams() {
|
||||
return [{ id: '1' }]
|
||||
}
|
||||
|
||||
export default async function ProductPage({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ id: string }>
|
||||
}) {
|
||||
const { id } = await params
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ProductDetails id={id} />
|
||||
<Suspense fallback={<div>Loading recommendations...</div>}>
|
||||
<Recommendations productId={id} />
|
||||
</Suspense>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
async function Recommendations({ productId }: { productId: string }) {
|
||||
const recommendations = await getRecommendations(productId)
|
||||
|
||||
return (
|
||||
<div>
|
||||
{recommendations.map((rec) => (
|
||||
<ProductCard key={rec.id} product={rec} />
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
async function getRecommendations(productId: string) {
|
||||
'use cache: private'
|
||||
cacheTag(`recommendations-${productId}`)
|
||||
cacheLife({ stale: 60 })
|
||||
|
||||
// Access cookies within private cache functions
|
||||
const sessionId = (await cookies()).get('session-id')?.value || 'guest'
|
||||
|
||||
return getPersonalizedRecommendations(productId, sessionId)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/product/[id]/page.js" switcher
|
||||
import { Suspense } from 'react'
|
||||
import { cookies } from 'next/headers'
|
||||
import { cacheLife, cacheTag } from 'next/cache'
|
||||
|
||||
export async function generateStaticParams() {
|
||||
return [{ id: '1' }]
|
||||
}
|
||||
|
||||
export default async function ProductPage({ params }) {
|
||||
const { id } = await params
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ProductDetails id={id} />
|
||||
<Suspense fallback={<div>Loading recommendations...</div>}>
|
||||
<Recommendations productId={id} />
|
||||
</Suspense>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
async function Recommendations({ productId }) {
|
||||
const recommendations = await getRecommendations(productId)
|
||||
|
||||
return (
|
||||
<div>
|
||||
{recommendations.map((rec) => (
|
||||
<ProductCard key={rec.id} product={rec} />
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
async function getRecommendations(productId) {
|
||||
'use cache: private'
|
||||
cacheTag(`recommendations-${productId}`)
|
||||
cacheLife({ stale: 60 })
|
||||
|
||||
// Access cookies within private cache functions
|
||||
const sessionId = (await cookies()).get('session-id')?.value || 'guest'
|
||||
|
||||
return getPersonalizedRecommendations(productId, sessionId)
|
||||
}
|
||||
```
|
||||
|
||||
> **Good to know**: The `stale` time must be at least 30 seconds for runtime prefetching to work. See [`cacheLife` client cache behavior](/docs/app/api-reference/functions/cacheLife#client-cache-behavior) for details.
|
||||
|
||||
## Request APIs allowed in private caches
|
||||
|
||||
The following request-specific APIs can be used inside `'use cache: private'` functions:
|
||||
|
||||
| API | Allowed in `use cache` | Allowed in `'use cache: private'` |
|
||||
| -------------- | ---------------------- | --------------------------------- |
|
||||
| `cookies()` | No | Yes |
|
||||
| `headers()` | No | Yes |
|
||||
| `searchParams` | No | Yes |
|
||||
| `connection()` | No | No |
|
||||
|
||||
> **Note:** The [`connection()`](https://nextjs.org/docs/app/api-reference/functions/connection) API is prohibited in both `use cache` and `'use cache: private'` as it provides connection-specific information that cannot be safely cached.
|
||||
|
||||
## Version History
|
||||
|
||||
| Version | Changes |
|
||||
| --------- | -------------------------------------------------------------------- |
|
||||
| `v16.0.0` | `"use cache: private"` is enabled with the Cache Components feature. |
|
||||
610
node_modules/next/dist/docs/01-app/03-api-reference/01-directives/use-cache-remote.md
generated
vendored
Normal file
610
node_modules/next/dist/docs/01-app/03-api-reference/01-directives/use-cache-remote.md
generated
vendored
Normal file
@@ -0,0 +1,610 @@
|
||||
---
|
||||
title: 'use cache: remote'
|
||||
description: 'Learn how to use the "use cache: remote" directive for persistent, shared caching using remote cache handlers.'
|
||||
related:
|
||||
title: Related
|
||||
description: View related API references.
|
||||
links:
|
||||
- app/api-reference/directives/use-cache
|
||||
- app/api-reference/directives/use-cache-private
|
||||
- app/api-reference/config/next-config-js/cacheComponents
|
||||
- app/api-reference/config/next-config-js/cacheHandlers
|
||||
- app/api-reference/functions/cacheLife
|
||||
- app/api-reference/functions/cacheTag
|
||||
- app/api-reference/functions/connection
|
||||
---
|
||||
|
||||
While the `use cache` directive is sufficient for most application needs, you might notice that cached operations are re-running more often than expected, or that your upstream services (CMS, databases, external APIs) are getting more hits than you'd expect. This can happen because `use cache` stores entries in-memory, which has inherent limitations:
|
||||
|
||||
- Cache entries being evicted to make room for new ones
|
||||
- Memory constraints in your deployment environment
|
||||
- Cache not persisting across requests or server restarts
|
||||
|
||||
Note that `use cache` still provides value beyond server-side caching: it informs Next.js what can be prefetched and defines stale times for client-side navigation.
|
||||
|
||||
The `'use cache: remote'` directive lets you declaratively specify that a cached output should be stored in a remote cache instead of in-memory, providing durable caching shared across all server instances. This comes with tradeoffs: infrastructure cost and network latency during cache lookups.
|
||||
|
||||
## Usage
|
||||
|
||||
To use `'use cache: remote'`, enable the [`cacheComponents`](/docs/app/api-reference/config/next-config-js/cacheComponents) flag in your `next.config.ts` file:
|
||||
|
||||
```ts filename="next.config.ts" switcher
|
||||
import type { NextConfig } from 'next'
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
cacheComponents: true,
|
||||
}
|
||||
|
||||
export default nextConfig
|
||||
```
|
||||
|
||||
```js filename="next.config.js" switcher
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
cacheComponents: true,
|
||||
}
|
||||
|
||||
export default nextConfig
|
||||
```
|
||||
|
||||
Then add `'use cache: remote'` to the functions or components where you've determined remote caching is justified. The handler implementation is configured via [`cacheHandlers`](/docs/app/api-reference/config/next-config-js/cacheHandlers), though hosting providers should typically provide this automatically. If you're self-hosting, see the `cacheHandlers` configuration reference to set up your cache storage.
|
||||
|
||||
### When to avoid remote caching
|
||||
|
||||
- If you already have a server-side cache key-value store wrapping your data layer, `use cache` may be sufficient to include data in the static shell without adding another caching layer
|
||||
- If operations are already fast (< 50ms) due to proximity or local access, the remote cache lookup might not improve performance
|
||||
- If cache keys have mostly unique values per request (search filters, price ranges, user-specific parameters), cache utilization will be near-zero
|
||||
- If data changes frequently (seconds to minutes), cache hits will quickly go stale, leading to frequent misses and waiting for upstream revalidation
|
||||
|
||||
### When remote caching makes sense
|
||||
|
||||
Remote caching provides the most value when content is deferred to request time (outside the static shell). This typically happens when a component accesses request values like [`cookies()`](/docs/app/api-reference/functions/cookies), [`headers()`](/docs/app/api-reference/functions/headers), or [`searchParams`](/docs/app/api-reference/file-conventions/page#searchparams-optional), placing it inside a Suspense boundary. In this context:
|
||||
|
||||
- Each request executes the component and looks up the cache
|
||||
- In serverless environments, each instance has its own ephemeral memory with low cache hit rates
|
||||
- Remote caching provides a shared cache across all instances, improving hit rates and reducing backend load
|
||||
|
||||
Compelling scenarios for `'use cache: remote'`:
|
||||
|
||||
- **Rate-limited APIs**: Your upstream service has rate limits or request quotas that you risk hitting
|
||||
- **Protecting slow backends**: Your database or API becomes a bottleneck under high traffic
|
||||
- **Expensive operations**: Database queries or computations that are costly to run repeatedly
|
||||
- **Flaky or unreliable services**: External services that occasionally fail or have availability issues
|
||||
|
||||
In these cases, the cost and latency of remote caching is justified by avoiding worse outcomes (rate limit errors, backend overload, high compute bills, or degraded user experience).
|
||||
|
||||
For static shell content, `use cache` is usually sufficient. If your upstream source can't handle concurrent revalidation requests (like a rate-limited CMS), `use cache: remote` acts as a shared cache layer. This is the same pattern as putting a key-value store in front of a database, but declared in code.
|
||||
|
||||
### How `use cache: remote` differs from `use cache` and `use cache: private`
|
||||
|
||||
Next.js provides three caching directives, each designed for different use cases:
|
||||
|
||||
| Feature | `use cache` | `'use cache: remote'` | `'use cache: private'` |
|
||||
| --------------------------------------- | ------------------------------- | --------------------------------- | ---------------------- |
|
||||
| **Server-side caching** | In-memory or cache handler | Remote cache handler | None |
|
||||
| **Cache scope** | Shared across all users | Shared across all users | Per-client (browser) |
|
||||
| **Can access cookies/headers directly** | No (must pass as arguments) | No (must pass as arguments) | Yes |
|
||||
| **Server cache utilization** | May be low outside static shell | High (shared across instances) | N/A |
|
||||
| **Additional costs** | None | Infrastructure (storage, network) | None |
|
||||
| **Latency impact** | None | Cache handler lookup | None |
|
||||
|
||||
### Caching with runtime data
|
||||
|
||||
Both `use cache` and `'use cache: remote'` can't access runtime values like cookies or search params directly. You can extract these values and pass them as arguments to cached functions. See [with runtime data](/docs/app/getting-started/caching#working-with-runtime-apis) for this pattern.
|
||||
|
||||
> **Good to know**: `use cache` stores entries in-memory. In serverless environments, memory is not shared between instances and is typically destroyed after serving a request, leading to frequent cache misses for runtime caching.
|
||||
|
||||
### Cache key considerations
|
||||
|
||||
Be thoughtful about which values you include in cache keys. Each unique value creates a separate cache entry, reducing cache utilization. Consider this example with search filters:
|
||||
|
||||
```tsx filename="app/products/[category]/page.tsx"
|
||||
import { Suspense } from 'react'
|
||||
|
||||
export default async function ProductsPage({
|
||||
params,
|
||||
searchParams,
|
||||
}: {
|
||||
params: Promise<{ category: string }>
|
||||
searchParams: Promise<{ minPrice?: string }>
|
||||
}) {
|
||||
return (
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
<ProductList params={params} searchParams={searchParams} />
|
||||
</Suspense>
|
||||
)
|
||||
}
|
||||
|
||||
async function ProductList({
|
||||
params,
|
||||
searchParams,
|
||||
}: {
|
||||
params: Promise<{ category: string }>
|
||||
searchParams: Promise<{ minPrice?: string }>
|
||||
}) {
|
||||
const { category } = await params
|
||||
|
||||
const { minPrice } = await searchParams
|
||||
|
||||
// Cache only on category (few unique values)
|
||||
// Don't include price filter (many unique values)
|
||||
const products = await getProductsByCategory(category)
|
||||
|
||||
// Filter price in memory instead of creating cache entries
|
||||
// for every price value
|
||||
const filtered = minPrice
|
||||
? products.filter((p) => p.price >= parseFloat(minPrice))
|
||||
: products
|
||||
|
||||
return <div>{/* render filtered products */}</div>
|
||||
}
|
||||
|
||||
async function getProductsByCategory(category: string) {
|
||||
'use cache: remote'
|
||||
// Only category is part of the cache key
|
||||
// Much better utilization than caching every price filter value
|
||||
return db.products.findByCategory(category)
|
||||
}
|
||||
```
|
||||
|
||||
In this example, the remote handler stores more data per cache entry (all products in a category) to achieve better cache hit rates. This is worth it when the cost of cache misses (hitting your backend) outweighs the storage cost of larger entries.
|
||||
|
||||
The same principle applies to user-specific data. Rather than caching per-user data directly, use user preferences to determine what shared data to cache.
|
||||
|
||||
For example, if users have a language preference in their session, extract that preference and use it to cache shared content:
|
||||
|
||||
- Instead of remote caching `getUserProfile(sessionID)`, which creates one entry per user
|
||||
- Remote cache `getCMSContent(language)` to create one entry per language
|
||||
|
||||
```tsx filename="app/components/welcome-message.tsx"
|
||||
import { cookies } from 'next/headers'
|
||||
import { cacheLife } from 'next/cache'
|
||||
|
||||
export async function WelcomeMessage() {
|
||||
// Extract the language preference (not unique per user)
|
||||
const language = (await cookies()).get('language')?.value || 'en'
|
||||
|
||||
// Cache based on language (few unique values: en, es, fr, de, etc.)
|
||||
// All users who prefer 'en' share the same cache entry
|
||||
const content = await getCMSContent(language)
|
||||
|
||||
return <div>{content.welcomeMessage}</div>
|
||||
}
|
||||
|
||||
async function getCMSContent(language: string) {
|
||||
'use cache: remote'
|
||||
cacheLife({ expire: 3600 })
|
||||
// Creates ~10-50 cache entries (one per language)
|
||||
// instead of thousands (one per user)
|
||||
return cms.getHomeContent(language)
|
||||
}
|
||||
```
|
||||
|
||||
This way all users who prefer the same language share a cache entry, improving cache utilization and reducing load on your CMS.
|
||||
|
||||
The pattern is the same in both examples: find the dimension with fewer unique values (category vs. price, language vs. user ID), cache on that dimension, and filter or select the rest in memory.
|
||||
|
||||
If the service used by `getUserProfile` cannot scale with your frontend load, you may still be able to use the `use cache` directive with a short `cacheLife` for in-memory caching. However, for most user data, you likely want to fetch directly from the source (which might already be wrapped in a key/value store as mentioned in the guidelines above).
|
||||
|
||||
Only use [`'use cache: private'`](/docs/app/api-reference/directives/use-cache-private) if you have compliance requirements or can't refactor to pass runtime data as arguments.
|
||||
|
||||
### Nesting rules
|
||||
|
||||
Remote caches have specific nesting rules:
|
||||
|
||||
- Remote caches **can** be nested inside other remote caches (`'use cache: remote'`)
|
||||
- Remote caches **can** be nested inside regular caches (`'use cache'`)
|
||||
- Remote caches **cannot** be nested inside private caches (`'use cache: private'`)
|
||||
- Private caches **cannot** be nested inside remote caches
|
||||
|
||||
```tsx
|
||||
// VALID: Remote inside remote
|
||||
async function outerRemote() {
|
||||
'use cache: remote'
|
||||
const result = await innerRemote()
|
||||
return result
|
||||
}
|
||||
|
||||
async function innerRemote() {
|
||||
'use cache: remote'
|
||||
return getData()
|
||||
}
|
||||
|
||||
// VALID: Remote inside regular cache
|
||||
async function outerCache() {
|
||||
'use cache'
|
||||
// The inner remote cache will work when deferred to request time
|
||||
const result = await innerRemote()
|
||||
return result
|
||||
}
|
||||
|
||||
async function innerRemote() {
|
||||
'use cache: remote'
|
||||
return getData()
|
||||
}
|
||||
|
||||
// INVALID: Remote inside private
|
||||
async function outerPrivate() {
|
||||
'use cache: private'
|
||||
const result = await innerRemote() // Error!
|
||||
return result
|
||||
}
|
||||
|
||||
async function innerRemote() {
|
||||
'use cache: remote'
|
||||
return getData()
|
||||
}
|
||||
|
||||
// INVALID: Private inside remote
|
||||
async function outerRemote() {
|
||||
'use cache: remote'
|
||||
const result = await innerPrivate() // Error!
|
||||
return result
|
||||
}
|
||||
|
||||
async function innerPrivate() {
|
||||
'use cache: private'
|
||||
return getData()
|
||||
}
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
The following examples demonstrate common patterns for using `'use cache: remote'`. For details about `cacheLife` parameters (`stale`, `revalidate`, `expire`), see the [`cacheLife` API reference](/docs/app/api-reference/functions/cacheLife).
|
||||
|
||||
### With user preferences
|
||||
|
||||
Cache product pricing based on the user's currency preference. Since the currency is stored in a cookie, this component renders at request time. Remote caching is valuable here because all users with the same currency share the cached price, and in serverless environments, all instances share the same remote cache.
|
||||
|
||||
```tsx filename="app/product/[id]/page.tsx" switcher
|
||||
import { Suspense } from 'react'
|
||||
import { cookies } from 'next/headers'
|
||||
import { cacheTag, cacheLife } from 'next/cache'
|
||||
|
||||
export async function generateStaticParams() {
|
||||
return [{ id: '1' }, { id: '2' }, { id: '3' }]
|
||||
}
|
||||
|
||||
export default async function ProductPage({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ id: string }>
|
||||
}) {
|
||||
const { id } = await params
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ProductDetails id={id} />
|
||||
<Suspense fallback={<div>Loading price...</div>}>
|
||||
<ProductPrice productId={id} />
|
||||
</Suspense>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function ProductDetails({ id }: { id: string }) {
|
||||
return <div>Product: {id}</div>
|
||||
}
|
||||
|
||||
async function ProductPrice({ productId }: { productId: string }) {
|
||||
// Reading cookies defers this component to request time
|
||||
const currency = (await cookies()).get('currency')?.value ?? 'USD'
|
||||
|
||||
// Cache the price per product and currency combination
|
||||
// All users with the same currency share this cache entry
|
||||
const price = await getProductPrice(productId, currency)
|
||||
|
||||
return (
|
||||
<div>
|
||||
Price: {price} {currency}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
async function getProductPrice(productId: string, currency: string) {
|
||||
'use cache: remote'
|
||||
cacheTag(`product-price-${productId}`)
|
||||
cacheLife({ expire: 3600 }) // 1 hour
|
||||
|
||||
// Cached per (productId, currency) - few currencies means high cache utilization
|
||||
return db.products.getPrice(productId, currency)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/product/[id]/page.js" switcher
|
||||
import { Suspense } from 'react'
|
||||
import { cookies } from 'next/headers'
|
||||
import { cacheTag, cacheLife } from 'next/cache'
|
||||
|
||||
export async function generateStaticParams() {
|
||||
return [{ id: '1' }, { id: '2' }, { id: '3' }]
|
||||
}
|
||||
|
||||
export default async function ProductPage({ params }) {
|
||||
const { id } = await params
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ProductDetails id={id} />
|
||||
<Suspense fallback={<div>Loading price...</div>}>
|
||||
<ProductPrice productId={id} />
|
||||
</Suspense>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function ProductDetails({ id }) {
|
||||
return <div>Product: {id}</div>
|
||||
}
|
||||
|
||||
async function ProductPrice({ productId }) {
|
||||
// Reading cookies defers this component to request time
|
||||
const currency = (await cookies()).get('currency')?.value ?? 'USD'
|
||||
|
||||
// Cache the price per product and currency combination
|
||||
// All users with the same currency share this cache entry
|
||||
const price = await getProductPrice(productId, currency)
|
||||
|
||||
return (
|
||||
<div>
|
||||
Price: {price} {currency}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
async function getProductPrice(productId, currency) {
|
||||
'use cache: remote'
|
||||
cacheTag(`product-price-${productId}`)
|
||||
cacheLife({ expire: 3600 }) // 1 hour
|
||||
|
||||
// Cached per (productId, currency) - few currencies means high cache utilization
|
||||
return db.products.getPrice(productId, currency)
|
||||
}
|
||||
```
|
||||
|
||||
### Reducing database load
|
||||
|
||||
Cache expensive database queries, reducing load on your database. In this example, we don't access `cookies()`, `headers()`, or `searchParams`. If we had a requirement to not include these stats in the static shell, we could use [`connection()`](/docs/app/api-reference/functions/connection) to explicitly defer to request time:
|
||||
|
||||
```tsx filename="app/dashboard/page.tsx"
|
||||
import { Suspense } from 'react'
|
||||
import { connection } from 'next/server'
|
||||
import { cacheLife, cacheTag } from 'next/cache'
|
||||
|
||||
export default function DashboardPage() {
|
||||
return (
|
||||
<Suspense fallback={<div>Loading stats...</div>}>
|
||||
<DashboardStats />
|
||||
</Suspense>
|
||||
)
|
||||
}
|
||||
|
||||
async function DashboardStats() {
|
||||
// Defer to request time
|
||||
await connection()
|
||||
|
||||
const stats = await getGlobalStats()
|
||||
|
||||
return <StatsDisplay stats={stats} />
|
||||
}
|
||||
|
||||
async function getGlobalStats() {
|
||||
'use cache: remote'
|
||||
cacheTag('global-stats')
|
||||
cacheLife({ expire: 60 }) // 1 minute
|
||||
|
||||
// This expensive database query is cached and shared across all users,
|
||||
// reducing load on your database
|
||||
const stats = await db.analytics.aggregate({
|
||||
total_users: 'count',
|
||||
active_sessions: 'count',
|
||||
revenue: 'sum',
|
||||
})
|
||||
|
||||
return stats
|
||||
}
|
||||
```
|
||||
|
||||
With this setup, your upstream database sees at most one request per minute, regardless of how many users visit the dashboard.
|
||||
|
||||
### API responses in streaming contexts
|
||||
|
||||
Cache API responses that are fetched during streaming or after dynamic operations:
|
||||
|
||||
```tsx filename="app/feed/page.tsx"
|
||||
import { Suspense } from 'react'
|
||||
import { connection } from 'next/server'
|
||||
import { cacheLife, cacheTag } from 'next/cache'
|
||||
|
||||
export default async function FeedPage() {
|
||||
return (
|
||||
<div>
|
||||
<Suspense fallback={<Skeleton />}>
|
||||
<FeedItems />
|
||||
</Suspense>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
async function FeedItems() {
|
||||
// Defer to request time
|
||||
await connection()
|
||||
|
||||
const items = await getFeedItems()
|
||||
|
||||
return items.map((item) => <FeedItem key={item.id} item={item} />)
|
||||
}
|
||||
|
||||
async function getFeedItems() {
|
||||
'use cache: remote'
|
||||
cacheTag('feed-items')
|
||||
cacheLife({ expire: 120 }) // 2 minutes
|
||||
|
||||
// This API call is cached, reducing requests to your external service
|
||||
const response = await fetch('https://api.example.com/feed')
|
||||
return response.json()
|
||||
}
|
||||
```
|
||||
|
||||
### Computed data after dynamic checks
|
||||
|
||||
Cache expensive computations that occur after dynamic security or feature checks:
|
||||
|
||||
```tsx filename="app/reports/page.tsx"
|
||||
import { connection } from 'next/server'
|
||||
import { cacheLife } from 'next/cache'
|
||||
|
||||
export default async function ReportsPage() {
|
||||
// Defer to request time (for security check)
|
||||
await connection()
|
||||
|
||||
const report = await generateReport()
|
||||
|
||||
return <ReportViewer report={report} />
|
||||
}
|
||||
|
||||
async function generateReport() {
|
||||
'use cache: remote'
|
||||
cacheLife({ expire: 3600 }) // 1 hour
|
||||
|
||||
// This expensive computation is cached and shared across all authorized users,
|
||||
// avoiding repeated calculations
|
||||
const data = await db.transactions.findMany()
|
||||
|
||||
return {
|
||||
totalRevenue: calculateRevenue(data),
|
||||
topProducts: analyzeProducts(data),
|
||||
trends: calculateTrends(data),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Mixed caching strategies
|
||||
|
||||
Combine static, remote, and private caching for optimal performance:
|
||||
|
||||
```tsx filename="app/product/[id]/page.tsx"
|
||||
import { Suspense } from 'react'
|
||||
import { connection } from 'next/server'
|
||||
import { cookies } from 'next/headers'
|
||||
import { cacheLife, cacheTag } from 'next/cache'
|
||||
|
||||
// Static product data - prerendered at build time
|
||||
async function getProduct(id: string) {
|
||||
'use cache'
|
||||
cacheTag(`product-${id}`)
|
||||
|
||||
// This is cached at build time and shared across all users
|
||||
return db.products.find({ where: { id } })
|
||||
}
|
||||
|
||||
// Shared pricing data - cached at runtime in remote handler
|
||||
async function getProductPrice(id: string) {
|
||||
'use cache: remote'
|
||||
cacheTag(`product-price-${id}`)
|
||||
cacheLife({ expire: 300 }) // 5 minutes
|
||||
|
||||
// This is cached at runtime and shared across all users
|
||||
return db.products.getPrice({ where: { id } })
|
||||
}
|
||||
|
||||
// User-specific recommendations - private cache per user
|
||||
async function getRecommendations(productId: string) {
|
||||
'use cache: private'
|
||||
cacheLife({ expire: 60 }) // 1 minute
|
||||
|
||||
const sessionId = (await cookies()).get('session-id')?.value
|
||||
|
||||
// This is cached per-user and never shared
|
||||
return db.recommendations.findMany({
|
||||
where: { productId, sessionId },
|
||||
})
|
||||
}
|
||||
|
||||
export default async function ProductPage({ params }) {
|
||||
const { id } = await params
|
||||
|
||||
// Static product data
|
||||
const product = await getProduct(id)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ProductDetails product={product} />
|
||||
|
||||
{/* Dynamic shared price */}
|
||||
<Suspense fallback={<PriceSkeleton />}>
|
||||
<ProductPriceComponent productId={id} />
|
||||
</Suspense>
|
||||
|
||||
{/* Dynamic personalized recommendations */}
|
||||
<Suspense fallback={<RecommendationsSkeleton />}>
|
||||
<ProductRecommendations productId={id} />
|
||||
</Suspense>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function ProductDetails({ product }) {
|
||||
return (
|
||||
<div>
|
||||
<h1>{product.name}</h1>
|
||||
<p>{product.description}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
async function ProductPriceComponent({ productId }) {
|
||||
// Defer to request time
|
||||
await connection()
|
||||
|
||||
const price = await getProductPrice(productId)
|
||||
return <div>Price: ${price}</div>
|
||||
}
|
||||
|
||||
async function ProductRecommendations({ productId }) {
|
||||
const recommendations = await getRecommendations(productId)
|
||||
return <RecommendationsList items={recommendations} />
|
||||
}
|
||||
|
||||
function PriceSkeleton() {
|
||||
return <div>Loading price...</div>
|
||||
}
|
||||
|
||||
function RecommendationsSkeleton() {
|
||||
return <div>Loading recommendations...</div>
|
||||
}
|
||||
|
||||
function RecommendationsList({ items }) {
|
||||
return (
|
||||
<ul>
|
||||
{items.map((item) => (
|
||||
<li key={item.id}>{item.name}</li>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
> **Good to know**:
|
||||
>
|
||||
> - Remote caches are stored in server-side cache handlers and shared across all users
|
||||
> - `'use cache: remote'` works outside the static shell where [`use cache`](/docs/app/api-reference/directives/use-cache) may not provide server-side cache hits
|
||||
> - Use [`cacheTag()`](/docs/app/api-reference/functions/cacheTag) and [`revalidateTag()`](/docs/app/api-reference/functions/revalidateTag) to invalidate remote caches on-demand
|
||||
> - Use [`cacheLife()`](/docs/app/api-reference/functions/cacheLife) to configure cache expiration
|
||||
> - For user-specific data, use [`'use cache: private'`](/docs/app/api-reference/directives/use-cache-private) instead of `'use cache: remote'`
|
||||
> - Remote caches reduce origin load by storing computed or fetched data server-side
|
||||
|
||||
## Platform Support
|
||||
|
||||
| Deployment Option | Supported |
|
||||
| ------------------------------------------------------------------- | --------- |
|
||||
| [Node.js server](/docs/app/getting-started/deploying#nodejs-server) | Yes |
|
||||
| [Docker container](/docs/app/getting-started/deploying#docker) | Yes |
|
||||
| [Static export](/docs/app/getting-started/deploying#static-export) | No |
|
||||
| [Adapters](/docs/app/getting-started/deploying#adapters) | Yes |
|
||||
|
||||
## Version History
|
||||
|
||||
| Version | Changes |
|
||||
| --------- | ------------------------------------------------------------------- |
|
||||
| `v16.0.0` | `"use cache: remote"` is enabled with the Cache Components feature. |
|
||||
663
node_modules/next/dist/docs/01-app/03-api-reference/01-directives/use-cache.md
generated
vendored
Normal file
663
node_modules/next/dist/docs/01-app/03-api-reference/01-directives/use-cache.md
generated
vendored
Normal file
@@ -0,0 +1,663 @@
|
||||
---
|
||||
title: use cache
|
||||
description: Learn how to use the "use cache" directive to cache data in your Next.js application.
|
||||
related:
|
||||
title: Related
|
||||
description: View related API references.
|
||||
links:
|
||||
- app/api-reference/directives/use-cache-private
|
||||
- app/api-reference/config/next-config-js/cacheComponents
|
||||
- app/api-reference/config/next-config-js/cacheLife
|
||||
- app/api-reference/config/next-config-js/cacheHandlers
|
||||
- app/api-reference/functions/cacheTag
|
||||
- app/api-reference/functions/cacheLife
|
||||
- app/api-reference/functions/revalidateTag
|
||||
---
|
||||
|
||||
The `use cache` directive allows you to mark a route, React component, or a function as cacheable. It can be used at the top of a file to indicate that all exports in the file should be cached, or inline at the top of function or component to cache the return value.
|
||||
|
||||
> **Good to know:**
|
||||
>
|
||||
> - To use cookies or headers, read them outside cached scopes and pass values as arguments. This is the preferred pattern.
|
||||
> - If the in-memory cache isn't sufficient for runtime data, [`'use cache: remote'`](/docs/app/api-reference/directives/use-cache-remote) allows platforms to provide a dedicated cache handler, though it requires a network roundtrip to check the cache and typically incurs platform fees.
|
||||
> - For compliance requirements or when you can't refactor to pass runtime data as arguments to a `use cache` scope, see [`'use cache: private'`](/docs/app/api-reference/directives/use-cache-private).
|
||||
|
||||
## Usage
|
||||
|
||||
`use cache` is a Cache Components feature. To enable it, add the [`cacheComponents`](/docs/app/api-reference/config/next-config-js/cacheComponents) option to your `next.config.ts` file:
|
||||
|
||||
```ts filename="next.config.ts" switcher
|
||||
import type { NextConfig } from 'next'
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
cacheComponents: true,
|
||||
}
|
||||
|
||||
export default nextConfig
|
||||
```
|
||||
|
||||
```js filename="next.config.js" switcher
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
cacheComponents: true,
|
||||
}
|
||||
|
||||
module.exports = nextConfig
|
||||
```
|
||||
|
||||
Then, add `use cache` at the file, component, or function level:
|
||||
|
||||
```tsx
|
||||
// File level
|
||||
'use cache'
|
||||
|
||||
export default async function Page() {
|
||||
// ...
|
||||
}
|
||||
|
||||
// Component level
|
||||
export async function MyComponent() {
|
||||
'use cache'
|
||||
return <></>
|
||||
}
|
||||
|
||||
// Function level
|
||||
export async function getData() {
|
||||
'use cache'
|
||||
const data = await fetch('/api/data')
|
||||
return data
|
||||
}
|
||||
```
|
||||
|
||||
> **Good to know**: When used at file level, all function exports must be async functions.
|
||||
|
||||
## How `use cache` works
|
||||
|
||||
### Cache keys
|
||||
|
||||
A cache entry's key is generated using a serialized version of its inputs, which includes:
|
||||
|
||||
1. **Build ID** - Unique per build, changing this invalidates all cache entries
|
||||
2. **Function ID** - A secure hash of the function's location and signature in the codebase
|
||||
3. **Serializable arguments** - Props (for components) or function arguments
|
||||
4. **HMR refresh hash** (development only) - Invalidates cache on hot module replacement
|
||||
|
||||
When a cached function references variables from outer scopes, those variables are automatically captured and bound as arguments, making them part of the cache key.
|
||||
|
||||
```tsx filename="lib/data.ts"
|
||||
async function Component({ userId }: { userId: string }) {
|
||||
const getData = async (filter: string) => {
|
||||
'use cache'
|
||||
// Cache key includes both userId (from closure) and filter (argument)
|
||||
return fetch(`/api/users/${userId}/data?filter=${filter}`)
|
||||
}
|
||||
|
||||
return getData('active')
|
||||
}
|
||||
```
|
||||
|
||||
In the snippet above, `userId` is captured from the outer scope and `filter` is passed as an argument, so both become part of the `getData` function's cache key. This means different user and filter combinations will have separate cache entries.
|
||||
|
||||
## Serialization
|
||||
|
||||
Arguments to cached functions and their return values must be serializable.
|
||||
|
||||
For a complete reference, see:
|
||||
|
||||
- [Serializable arguments](https://react.dev/reference/rsc/use-server#serializable-parameters-and-return-values) - Uses **React Server Components** serialization
|
||||
- [Serializable return types](https://react.dev/reference/rsc/use-client#serializable-types) - Uses **React Client Components** serialization
|
||||
|
||||
> **Good to know:** Arguments and return values use different serialization systems. Server Component serialization (for arguments) is more restrictive than Client Component serialization (for return values). This means you can return JSX elements but cannot accept them as arguments unless using pass-through patterns.
|
||||
|
||||
### Supported types
|
||||
|
||||
**Arguments:**
|
||||
|
||||
- Primitives: `string`, `number`, `boolean`, `null`, `undefined`
|
||||
- Plain objects: `{ key: value }`
|
||||
- Arrays: `[1, 2, 3]`
|
||||
- Dates, Maps, Sets, TypedArrays, ArrayBuffers
|
||||
- React elements (as pass-through only)
|
||||
|
||||
**Return values:**
|
||||
|
||||
- Same as arguments, plus JSX elements
|
||||
|
||||
### Unsupported types
|
||||
|
||||
- Class instances
|
||||
- Functions (except as pass-through)
|
||||
- Symbols, WeakMaps, WeakSets
|
||||
- URL instances
|
||||
|
||||
```tsx filename="app/components/user-card.tsx"
|
||||
// Valid - primitives and plain objects
|
||||
async function UserCard({
|
||||
id,
|
||||
config,
|
||||
}: {
|
||||
id: string
|
||||
config: { theme: string }
|
||||
}) {
|
||||
'use cache'
|
||||
return <div>{id}</div>
|
||||
}
|
||||
|
||||
// Invalid - class instance
|
||||
async function UserProfile({ user }: { user: UserClass }) {
|
||||
'use cache'
|
||||
// Error: Cannot serialize class instance
|
||||
return <div>{user.name}</div>
|
||||
}
|
||||
```
|
||||
|
||||
### Pass-through (non-serializable arguments)
|
||||
|
||||
You can accept non-serializable values **as long as you don't introspect them**. This enables composition patterns with `children` and Server Actions:
|
||||
|
||||
```tsx filename="app/components/cached-wrapper.tsx"
|
||||
async function CachedWrapper({ children }: { children: ReactNode }) {
|
||||
'use cache'
|
||||
// Don't read or modify children - just pass it through
|
||||
return (
|
||||
<div className="wrapper">
|
||||
<header>Cached Header</header>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// Usage: children can be dynamic
|
||||
export default function Page() {
|
||||
return (
|
||||
<CachedWrapper>
|
||||
<DynamicComponent /> {/* Not cached, passed through */}
|
||||
</CachedWrapper>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
You can also pass Server Actions through cached components:
|
||||
|
||||
```tsx filename="app/components/cached-form.tsx"
|
||||
async function CachedForm({ action }: { action: () => Promise<void> }) {
|
||||
'use cache'
|
||||
// Don't call action here - just pass it through
|
||||
return <form action={action}>{/* ... */}</form>
|
||||
}
|
||||
```
|
||||
|
||||
## Constraints
|
||||
|
||||
Cached functions execute in an isolated environment. The following constraints ensure cache behavior remains predictable and secure.
|
||||
|
||||
### Request-time APIs
|
||||
|
||||
Cached functions and components **cannot** directly access runtime APIs like `cookies()`, `headers()`, or `searchParams`. Instead, read these values outside the cached scope and pass them as arguments.
|
||||
|
||||
### Runtime caching considerations
|
||||
|
||||
While `use cache` is designed primarily to include uncached data in the static shell, it can also cache data at runtime using in-memory LRU (Least Recently Used) storage.
|
||||
|
||||
Runtime cache behavior depends on your hosting environment:
|
||||
|
||||
| Environment | Runtime Caching Behavior |
|
||||
| --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **Serverless** | Cache entries typically don't persist across requests (each request can be a different instance). Build-time caching works normally. |
|
||||
| **Self-hosted** | Cache entries persist across requests. Control cache size with [`cacheMaxMemorySize`](/docs/app/api-reference/config/next-config-js/incrementalCacheHandlerPath). |
|
||||
|
||||
If the default in-memory cache isn't enough, consider **[`use cache: remote`](/docs/app/api-reference/directives/use-cache-remote)** which allows platforms to provide a dedicated cache handler (like Redis or KV database). This helps reduce hits against data sources not scaled to your total traffic, though it comes with costs (storage, network latency, platform fees).
|
||||
|
||||
Very rarely, for compliance requirements or when you can't refactor your code to pass runtime data as arguments to a `use cache` scope, you might need [`use cache: private`](/docs/app/api-reference/directives/use-cache-private).
|
||||
|
||||
### React.cache isolation
|
||||
|
||||
[`React.cache`](https://react.dev/reference/react/cache) operates in an isolated scope inside `use cache` boundaries. Values stored via `React.cache` outside a `use cache` function are not visible inside it.
|
||||
|
||||
This means you cannot use `React.cache` to pass data into a `use cache` scope:
|
||||
|
||||
```tsx
|
||||
import { cache } from 'react'
|
||||
|
||||
const store = cache(() => ({ current: null as string | null }))
|
||||
|
||||
function Parent() {
|
||||
const shared = store()
|
||||
shared.current = 'value from parent'
|
||||
return <Child />
|
||||
}
|
||||
|
||||
async function Child() {
|
||||
'use cache'
|
||||
const shared = store()
|
||||
// shared.current is null, not 'value from parent'
|
||||
// use cache has its own isolated React.cache scope
|
||||
return <div>{shared.current}</div>
|
||||
}
|
||||
```
|
||||
|
||||
This isolation ensures cached functions have predictable, self-contained behavior. To pass data into a `use cache` scope, use function arguments instead.
|
||||
|
||||
## `use cache` at runtime
|
||||
|
||||
On the **server**, cache entries are stored in-memory and respect the `revalidate` and `expire` times from your `cacheLife` configuration. You can customize the cache storage by configuring [`cacheHandlers`](/docs/app/api-reference/config/next-config-js/cacheHandlers) in your `next.config.js` file.
|
||||
|
||||
On the **client**, content from the server cache is stored in the browser's memory for the duration defined by the `stale` time. The client router enforces a **minimum 30-second stale time**, regardless of configuration.
|
||||
|
||||
The `x-nextjs-stale-time` response header communicates cache lifetime from server to client, ensuring coordinated behavior.
|
||||
|
||||
## Revalidation
|
||||
|
||||
By default, `use cache` uses the `default` profile with these settings:
|
||||
|
||||
- **stale**: 5 minutes (client-side)
|
||||
- **revalidate**: 15 minutes (server-side)
|
||||
- **expire**: Never expires by time
|
||||
|
||||
```tsx filename="lib/data.ts"
|
||||
async function getData() {
|
||||
'use cache'
|
||||
// Implicitly uses default profile
|
||||
return fetch('/api/data')
|
||||
}
|
||||
```
|
||||
|
||||
### Customizing cache lifetime
|
||||
|
||||
Use the [`cacheLife`](/docs/app/api-reference/functions/cacheLife) function to customize cache duration:
|
||||
|
||||
```tsx filename="lib/data.ts"
|
||||
import { cacheLife } from 'next/cache'
|
||||
|
||||
async function getData() {
|
||||
'use cache'
|
||||
cacheLife('hours') // Use built-in 'hours' profile
|
||||
return fetch('/api/data')
|
||||
}
|
||||
```
|
||||
|
||||
### On-demand revalidation
|
||||
|
||||
Use [`cacheTag`](/docs/app/api-reference/functions/cacheTag), [`updateTag`](/docs/app/api-reference/functions/updateTag), or [`revalidateTag`](/docs/app/api-reference/functions/revalidateTag) for on-demand cache invalidation:
|
||||
|
||||
```tsx filename="lib/data.ts"
|
||||
import { cacheTag } from 'next/cache'
|
||||
|
||||
async function getProducts() {
|
||||
'use cache'
|
||||
cacheTag('products')
|
||||
return fetch('/api/products')
|
||||
}
|
||||
```
|
||||
|
||||
```tsx filename="app/actions.ts"
|
||||
'use server'
|
||||
|
||||
import { updateTag } from 'next/cache'
|
||||
|
||||
export async function updateProduct() {
|
||||
await db.products.update(...)
|
||||
updateTag('products') // Invalidates all 'products' caches
|
||||
}
|
||||
```
|
||||
|
||||
Both `cacheLife` and `cacheTag` integrate across client and server caching layers, meaning you configure your caching semantics in one place and they apply everywhere.
|
||||
|
||||
## Examples
|
||||
|
||||
### Caching an entire route with `use cache`
|
||||
|
||||
To prerender an entire route, add `use cache` to the top of **both** the `layout` and `page` files. Each of these segments are treated as separate entry points in your application, and will be cached independently.
|
||||
|
||||
```tsx filename="app/layout.tsx" switcher
|
||||
'use cache'
|
||||
|
||||
export default async function Layout({ children }: { children: ReactNode }) {
|
||||
return <div>{children}</div>
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/layout.js" switcher
|
||||
'use cache'
|
||||
|
||||
export default async function Layout({ children }) {
|
||||
return <div>{children}</div>
|
||||
}
|
||||
```
|
||||
|
||||
Any components imported and nested in `page` file are part of the cache output associated with the `page`.
|
||||
|
||||
```tsx filename="app/page.tsx" switcher
|
||||
'use cache'
|
||||
|
||||
async function Users() {
|
||||
const users = await fetch('/api/users')
|
||||
// loop through users
|
||||
}
|
||||
|
||||
export default async function Page() {
|
||||
return (
|
||||
<main>
|
||||
<Users />
|
||||
</main>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/page.js" switcher
|
||||
'use cache'
|
||||
|
||||
async function Users() {
|
||||
const users = await fetch('/api/users')
|
||||
// loop through users
|
||||
}
|
||||
|
||||
export default async function Page() {
|
||||
return (
|
||||
<main>
|
||||
<Users />
|
||||
</main>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
> **Good to know**:
|
||||
>
|
||||
> - If `use cache` is added only to the `layout` or the `page`, only that route segment and any components imported into it will be cached.
|
||||
|
||||
### Caching a component's output with `use cache`
|
||||
|
||||
You can use `use cache` at the component level to cache any fetches or computations performed within that component. The cache entry will be reused as long as the serialized props produce the same value in each instance.
|
||||
|
||||
```tsx filename="app/components/bookings.tsx" highlight={2} switcher
|
||||
export async function Bookings({ type = 'haircut' }: BookingsProps) {
|
||||
'use cache'
|
||||
async function getBookingsData() {
|
||||
const data = await fetch(`/api/bookings?type=${encodeURIComponent(type)}`)
|
||||
return data
|
||||
}
|
||||
return //...
|
||||
}
|
||||
|
||||
interface BookingsProps {
|
||||
type: string
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/components/bookings.js" highlight={2} switcher
|
||||
export async function Bookings({ type = 'haircut' }) {
|
||||
'use cache'
|
||||
async function getBookingsData() {
|
||||
const data = await fetch(`/api/bookings?type=${encodeURIComponent(type)}`)
|
||||
return data
|
||||
}
|
||||
return //...
|
||||
}
|
||||
```
|
||||
|
||||
### Caching function output with `use cache`
|
||||
|
||||
Since you can add `use cache` to any asynchronous function, you aren't limited to caching components or routes only. You might want to cache a network request, a database query, or a slow computation.
|
||||
|
||||
```tsx filename="app/actions.ts" highlight={2} switcher
|
||||
export async function getData() {
|
||||
'use cache'
|
||||
|
||||
const data = await fetch('/api/data')
|
||||
return data
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/actions.js" highlight={2} switcher
|
||||
export async function getData() {
|
||||
'use cache'
|
||||
|
||||
const data = await fetch('/api/data')
|
||||
return data
|
||||
}
|
||||
```
|
||||
|
||||
### Interleaving
|
||||
|
||||
In React, composition with `children` or slots is a well-known pattern for building flexible components. When using `use cache`, you can continue to compose your UI in this way. Anything included as `children`, or other compositional slots, in the returned JSX will be passed through the cached component without affecting its cache entry.
|
||||
|
||||
As long as you don't directly reference any of the JSX slots inside the body of the cacheable function itself, their presence in the returned output won't affect the cache entry.
|
||||
|
||||
```tsx filename="app/page.tsx" switcher
|
||||
export default async function Page() {
|
||||
const uncachedData = await getData()
|
||||
return (
|
||||
// Pass compositional slots as props, e.g. header and children
|
||||
<CacheComponent header={<h1>Home</h1>}>
|
||||
{/* DynamicComponent is provided as the children slot */}
|
||||
<DynamicComponent data={uncachedData} />
|
||||
</CacheComponent>
|
||||
)
|
||||
}
|
||||
|
||||
async function CacheComponent({
|
||||
header, // header: a compositional slot, injected as a prop
|
||||
children, // children: another slot for nested composition
|
||||
}: {
|
||||
header: ReactNode
|
||||
children: ReactNode
|
||||
}) {
|
||||
'use cache'
|
||||
const cachedData = await fetch('/api/cached-data')
|
||||
return (
|
||||
<div>
|
||||
{header}
|
||||
<PrerenderedComponent data={cachedData} />
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/page.js" switcher
|
||||
export default async function Page() {
|
||||
const uncachedData = await getData()
|
||||
return (
|
||||
// Pass compositional slots as props, e.g. header and children
|
||||
<CacheComponent header={<h1>Home</h1>}>
|
||||
{/* DynamicComponent is provided as the children slot */}
|
||||
<DynamicComponent data={uncachedData} />
|
||||
</CacheComponent>
|
||||
)
|
||||
}
|
||||
|
||||
async function CacheComponent({
|
||||
header, // header: a compositional slot, injected as a prop
|
||||
children, // children: another slot for nested composition
|
||||
}) {
|
||||
'use cache'
|
||||
const cachedData = await fetch('/api/cached-data')
|
||||
return (
|
||||
<div>
|
||||
{header}
|
||||
<PrerenderedComponent data={cachedData} />
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
You can also pass Server Actions through cached components to Client Components without invoking them inside the cacheable function.
|
||||
|
||||
```tsx filename="app/page.tsx" switcher
|
||||
import ClientComponent from './ClientComponent'
|
||||
|
||||
export default async function Page() {
|
||||
const performUpdate = async () => {
|
||||
'use server'
|
||||
// Perform some server-side update
|
||||
await db.update(...)
|
||||
}
|
||||
|
||||
return <CachedComponent performUpdate={performUpdate} />
|
||||
}
|
||||
|
||||
async function CachedComponent({
|
||||
performUpdate,
|
||||
}: {
|
||||
performUpdate: () => Promise<void>
|
||||
}) {
|
||||
'use cache'
|
||||
// Do not call performUpdate here
|
||||
return <ClientComponent action={performUpdate} />
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/page.js" switcher
|
||||
import ClientComponent from './ClientComponent'
|
||||
|
||||
export default async function Page() {
|
||||
const performUpdate = async () => {
|
||||
'use server'
|
||||
// Perform some server-side update
|
||||
await db.update(...)
|
||||
}
|
||||
|
||||
return <CachedComponent performUpdate={performUpdate} />
|
||||
}
|
||||
|
||||
async function CachedComponent({ performUpdate }) {
|
||||
'use cache'
|
||||
// Do not call performUpdate here
|
||||
return <ClientComponent action={performUpdate} />
|
||||
}
|
||||
```
|
||||
|
||||
```tsx filename="app/ClientComponent.tsx" switcher
|
||||
'use client'
|
||||
|
||||
export default function ClientComponent({
|
||||
action,
|
||||
}: {
|
||||
action: () => Promise<void>
|
||||
}) {
|
||||
return <button onClick={action}>Update</button>
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/ClientComponent.js" switcher
|
||||
'use client'
|
||||
|
||||
export default function ClientComponent({ action }) {
|
||||
return <button onClick={action}>Update</button>
|
||||
}
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Debugging cache behavior
|
||||
|
||||
#### Verbose logging
|
||||
|
||||
Set `NEXT_PRIVATE_DEBUG_CACHE=1` for verbose cache logging:
|
||||
|
||||
```bash
|
||||
NEXT_PRIVATE_DEBUG_CACHE=1 npm run dev
|
||||
# or for production
|
||||
NEXT_PRIVATE_DEBUG_CACHE=1 npm run start
|
||||
```
|
||||
|
||||
> **Good to know:** This environment variable also logs ISR and other caching mechanisms. See [Verifying correct production behavior](/docs/app/guides/incremental-static-regeneration#verifying-correct-production-behavior) for more details.
|
||||
|
||||
#### Console log replays
|
||||
|
||||
In development, console logs from cached functions appear with a `Cache` prefix.
|
||||
|
||||
### Build Hangs (Cache Timeout)
|
||||
|
||||
If your build hangs, you're accessing Promises that resolve to uncached or runtime data, created outside a `use cache` boundary. The cached function waits for data that can't resolve during the build, causing a timeout after 50 seconds.
|
||||
|
||||
When the build timeouts you'll see this error message:
|
||||
|
||||
> Error: Filling a cache during prerender timed out, likely because request-specific arguments such as params, searchParams, cookies() or uncached data were used inside "use cache".
|
||||
|
||||
Common ways this happens: passing such Promises as props, accessing them via closure, or retrieving them from shared storage (Maps).
|
||||
|
||||
> **Good to know:** Directly calling `cookies()` or `headers()` inside `use cache` fails immediately with a [different error](/docs/messages/next-request-in-use-cache), not a timeout.
|
||||
|
||||
**Passing runtime data Promises as props:**
|
||||
|
||||
```tsx filename="app/page.tsx"
|
||||
import { cookies } from 'next/headers'
|
||||
import { Suspense } from 'react'
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
<Dynamic />
|
||||
</Suspense>
|
||||
)
|
||||
}
|
||||
|
||||
async function Dynamic() {
|
||||
const cookieStore = cookies()
|
||||
return <Cached promise={cookieStore} /> // Build hangs
|
||||
}
|
||||
|
||||
async function Cached({ promise }: { promise: Promise<unknown> }) {
|
||||
'use cache'
|
||||
const data = await promise // Waits for runtime data during build
|
||||
return <p>..</p>
|
||||
}
|
||||
```
|
||||
|
||||
Await the `cookies` store in the `Dynamic` component, and pass a cookie value to the `Cached` component.
|
||||
|
||||
**Shared deduplication storage:**
|
||||
|
||||
```tsx filename="app/page.tsx"
|
||||
// Problem: Map stores dynamic Promises, accessed by cached code
|
||||
import { Suspense } from 'react'
|
||||
|
||||
const cache = new Map<string, Promise<string>>()
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<>
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
<Dynamic id="data" />
|
||||
</Suspense>
|
||||
<Cached id="data" />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
async function Dynamic({ id }: { id: string }) {
|
||||
// Stores dynamic Promise in shared Map
|
||||
cache.set(
|
||||
id,
|
||||
fetch(`https://api.example.com/${id}`).then((r) => r.text())
|
||||
)
|
||||
return <p>Dynamic</p>
|
||||
}
|
||||
|
||||
async function Cached({ id }: { id: string }) {
|
||||
'use cache'
|
||||
return <p>{await cache.get(id)}</p> // Build hangs - retrieves dynamic Promise
|
||||
}
|
||||
```
|
||||
|
||||
Use Next.js's built-in `fetch()` deduplication or use separate Maps for cached and uncached contexts.
|
||||
|
||||
## Platform Support
|
||||
|
||||
| Deployment Option | Supported |
|
||||
| ------------------------------------------------------------------- | ----------------- |
|
||||
| [Node.js server](/docs/app/getting-started/deploying#nodejs-server) | Yes |
|
||||
| [Docker container](/docs/app/getting-started/deploying#docker) | Yes |
|
||||
| [Static export](/docs/app/getting-started/deploying#static-export) | No |
|
||||
| [Adapters](/docs/app/getting-started/deploying#adapters) | Platform-specific |
|
||||
|
||||
Learn how to [configure caching](/docs/app/guides/self-hosting#caching-and-isr) when self-hosting Next.js.
|
||||
|
||||
## Version History
|
||||
|
||||
| Version | Changes |
|
||||
| --------- | ----------------------------------------------------------- |
|
||||
| `v16.0.0` | `"use cache"` is enabled with the Cache Components feature. |
|
||||
| `v15.0.0` | `"use cache"` is introduced as an experimental feature. |
|
||||
123
node_modules/next/dist/docs/01-app/03-api-reference/01-directives/use-client.md
generated
vendored
Normal file
123
node_modules/next/dist/docs/01-app/03-api-reference/01-directives/use-client.md
generated
vendored
Normal file
@@ -0,0 +1,123 @@
|
||||
---
|
||||
title: use client
|
||||
description: Learn how to use the use client directive to render a component on the client.
|
||||
---
|
||||
|
||||
The `'use client'` directive declares an entry point for the components to be rendered on the **client side** and should be used when creating interactive user interfaces (UI) that require client-side JavaScript capabilities, such as state management, event handling, and access to browser APIs. This is a React feature.
|
||||
|
||||
> **Good to know:**
|
||||
>
|
||||
> You do not need to add the `'use client'` directive to every file that contains Client Components. You only need to add it to the files whose components you want to render directly within Server Components. The `'use client'` directive defines the client-server [boundary](https://nextjs.org/docs/app/building-your-application/rendering#network-boundary), and the components exported from such a file serve as entry points to the client.
|
||||
|
||||
## Usage
|
||||
|
||||
To declare an entry point for the Client Components, add the `'use client'` directive **at the top of the file**, before any imports:
|
||||
|
||||
```tsx filename="app/components/counter.tsx" highlight={1} switcher
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
|
||||
export default function Counter() {
|
||||
const [count, setCount] = useState(0)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>Count: {count}</p>
|
||||
<button onClick={() => setCount(count + 1)}>Increment</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/components/counter.js" highlight={1} switcher
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
|
||||
export default function Counter() {
|
||||
const [count, setCount] = useState(0)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>Count: {count}</p>
|
||||
<button onClick={() => setCount(count + 1)}>Increment</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
When using the `'use client'` directive, the props of the Client Components must be [serializable](https://react.dev/reference/rsc/use-client#serializable-types). This means the props need to be in a format that React can serialize when sending data from the server to the client.
|
||||
|
||||
```tsx filename="app/components/counter.tsx" highlight={4} switcher
|
||||
'use client'
|
||||
|
||||
export default function Counter({
|
||||
onClick /* ❌ Function is not serializable */,
|
||||
}) {
|
||||
return (
|
||||
<div>
|
||||
<button onClick={onClick}>Increment</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/components/counter.js" highlight={4} switcher
|
||||
'use client'
|
||||
|
||||
export default function Counter({
|
||||
onClick /* ❌ Function is not serializable */,
|
||||
}) {
|
||||
return (
|
||||
<div>
|
||||
<button onClick={onClick}>Increment</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Nesting Client Components within Server Components
|
||||
|
||||
Combining Server and Client Components allows you to build applications that are both performant and interactive:
|
||||
|
||||
1. **Server Components**: Use for static content, data fetching, and SEO-friendly elements.
|
||||
2. **Client Components**: Use for interactive elements that require state, effects, or browser APIs.
|
||||
3. **Component composition**: Nest Client Components within Server Components as needed for a clear separation of server and client logic.
|
||||
|
||||
In the following example:
|
||||
|
||||
- `Header` is a Server Component handling static content.
|
||||
- `Counter` is a Client Component enabling interactivity within the page.
|
||||
|
||||
```tsx filename="app/page.tsx" highlight={2,8} switcher
|
||||
import Header from './header'
|
||||
import Counter from './counter' // This is a Client Component
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<div>
|
||||
<Header />
|
||||
<Counter />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/page.js" highlight={2,8} switcher
|
||||
import Header from './header'
|
||||
import Counter from './counter' // This is a Client Component
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<div>
|
||||
<Header />
|
||||
<Counter />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Reference
|
||||
|
||||
See the [React documentation](https://react.dev/reference/rsc/use-client) for more information on `'use client'`.
|
||||
194
node_modules/next/dist/docs/01-app/03-api-reference/01-directives/use-server.md
generated
vendored
Normal file
194
node_modules/next/dist/docs/01-app/03-api-reference/01-directives/use-server.md
generated
vendored
Normal file
@@ -0,0 +1,194 @@
|
||||
---
|
||||
title: use server
|
||||
description: Learn how to use the use server directive to execute code on the server.
|
||||
---
|
||||
|
||||
The `use server` directive designates a function or file to be executed on the **server side**. It can be used at the top of a file to indicate that all functions in the file are server-side, or inline at the top of a function to mark the function as a [Server Function](https://19.react.dev/reference/rsc/server-functions). This is a React feature.
|
||||
|
||||
## Using `use server` at the top of a file
|
||||
|
||||
The following example shows a file with a `use server` directive at the top. All functions in the file are executed on the server.
|
||||
|
||||
```tsx filename="app/actions.ts" highlight={1} switcher
|
||||
'use server'
|
||||
import { db } from '@/lib/db' // Your database client
|
||||
import { auth } from '@/lib/auth'
|
||||
|
||||
export async function createUser(data: { name: string; email: string }) {
|
||||
const session = await auth()
|
||||
if (!session?.user) {
|
||||
throw new Error('Unauthorized')
|
||||
}
|
||||
|
||||
const user = await db.user.create({ data })
|
||||
return { id: user.id, name: user.name }
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/actions.js" highlight={1} switcher
|
||||
'use server'
|
||||
import { db } from '@/lib/db' // Your database client
|
||||
import { auth } from '@/lib/auth'
|
||||
|
||||
export async function createUser(data) {
|
||||
const session = await auth()
|
||||
if (!session?.user) {
|
||||
throw new Error('Unauthorized')
|
||||
}
|
||||
|
||||
const user = await db.user.create({ data })
|
||||
return { id: user.id, name: user.name }
|
||||
}
|
||||
```
|
||||
|
||||
### Using Server Functions in a Client Component
|
||||
|
||||
To use Server Functions in Client Components you need to create your Server Functions in a dedicated file using the `use server` directive at the top of the file. These Server Functions can then be imported into Client and Server Components and executed.
|
||||
|
||||
Assuming you have a `fetchUsers` Server Function in `actions.ts`:
|
||||
|
||||
```tsx filename="app/actions.ts" highlight={1} switcher
|
||||
'use server'
|
||||
import { db } from '@/lib/db' // Your database client
|
||||
import { auth } from '@/lib/auth'
|
||||
|
||||
export async function fetchUsers() {
|
||||
const session = await auth()
|
||||
if (!session?.user) {
|
||||
throw new Error('Unauthorized')
|
||||
}
|
||||
|
||||
const users = await db.user.findMany({
|
||||
select: { id: true, name: true, email: true },
|
||||
})
|
||||
return users
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/actions.js" highlight={1} switcher
|
||||
'use server'
|
||||
import { db } from '@/lib/db' // Your database client
|
||||
import { auth } from '@/lib/auth'
|
||||
|
||||
export async function fetchUsers() {
|
||||
const session = await auth()
|
||||
if (!session?.user) {
|
||||
throw new Error('Unauthorized')
|
||||
}
|
||||
|
||||
const users = await db.user.findMany({
|
||||
select: { id: true, name: true, email: true },
|
||||
})
|
||||
return users
|
||||
}
|
||||
```
|
||||
|
||||
Then you can import the `fetchUsers` Server Function into a Client Component and execute it on the client-side.
|
||||
|
||||
```tsx filename="app/components/my-button.tsx" highlight={1,2,5} switcher
|
||||
'use client'
|
||||
import { fetchUsers } from '../actions'
|
||||
|
||||
export default function MyButton() {
|
||||
return <button onClick={() => fetchUsers()}>Fetch Users</button>
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/components/my-button.js" highlight={1,2,5} switcher
|
||||
'use client'
|
||||
import { fetchUsers } from '../actions'
|
||||
|
||||
export default function MyButton() {
|
||||
return <button onClick={() => fetchUsers()}>Fetch Users</button>
|
||||
}
|
||||
```
|
||||
|
||||
## Using `use server` inline
|
||||
|
||||
In the following example, `use server` is used inline at the top of a function to mark it as a [Server Function](https://19.react.dev/reference/rsc/server-functions):
|
||||
|
||||
```tsx filename="app/posts/[id]/page.tsx" switcher highlight={8}
|
||||
import { EditPost } from './edit-post'
|
||||
import { revalidatePath } from 'next/cache'
|
||||
|
||||
export default async function PostPage({ params }: { params: { id: string } }) {
|
||||
const post = await getPost(params.id)
|
||||
|
||||
async function updatePost(formData: FormData) {
|
||||
'use server'
|
||||
// Verify auth before saving (e.g. inside savePost)
|
||||
await savePost(params.id, formData)
|
||||
revalidatePath(`/posts/${params.id}`)
|
||||
}
|
||||
|
||||
return <EditPost updatePostAction={updatePost} post={post} />
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/posts/[id]/page.js" switcher highlight={8}
|
||||
import { EditPost } from './edit-post'
|
||||
import { revalidatePath } from 'next/cache'
|
||||
|
||||
export default async function PostPage({ params }) {
|
||||
const post = await getPost(params.id)
|
||||
|
||||
async function updatePost(formData) {
|
||||
'use server'
|
||||
// Verify auth before saving (e.g. inside savePost)
|
||||
await savePost(params.id, formData)
|
||||
revalidatePath(`/posts/${params.id}`)
|
||||
}
|
||||
|
||||
return <EditPost updatePostAction={updatePost} post={post} />
|
||||
}
|
||||
```
|
||||
|
||||
## Security considerations
|
||||
|
||||
Design your data access functions as secure primitives: validate inputs, check authentication and authorization, and constrain return types to only what the caller needs. When Server Functions delegate to a [Data Access Layer](/docs/app/guides/data-security#using-a-data-access-layer-for-mutations), these guarantees live in one place and apply consistently.
|
||||
|
||||
{/* TODO: showcase input validation */}
|
||||
|
||||
### Authentication and authorization
|
||||
|
||||
Always authenticate and authorize users before performing sensitive server-side operations. Read authentication from cookies or headers rather than accepting tokens as function parameters.
|
||||
|
||||
```tsx filename="app/actions.ts" highlight={1,7,8,9,10} switcher
|
||||
'use server'
|
||||
|
||||
import { db } from '@/lib/db' // Your database client
|
||||
import { auth } from '@/lib/auth' // Your authentication library
|
||||
|
||||
export async function createUser(data: { name: string; email: string }) {
|
||||
const session = await auth()
|
||||
if (!session?.user) {
|
||||
throw new Error('Unauthorized')
|
||||
}
|
||||
const newUser = await db.user.create({ data })
|
||||
return { id: newUser.id, name: newUser.name }
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/actions.js" highlight={1,7,8,9,10} switcher
|
||||
'use server'
|
||||
|
||||
import { db } from '@/lib/db' // Your database client
|
||||
import { auth } from '@/lib/auth' // Your authentication library
|
||||
|
||||
export async function createUser(data) {
|
||||
const session = await auth()
|
||||
if (!session?.user) {
|
||||
throw new Error('Unauthorized')
|
||||
}
|
||||
const newUser = await db.user.create({ data })
|
||||
return { id: newUser.id, name: newUser.name }
|
||||
}
|
||||
```
|
||||
|
||||
### Return values
|
||||
|
||||
Server Function return values are serialized and sent to the client. Only return data the UI needs, not raw database records. See the [Data Security guide](/docs/app/guides/data-security#controlling-return-values) for more details.
|
||||
|
||||
## Reference
|
||||
|
||||
See the [React documentation](https://react.dev/reference/rsc/use-server) for more information on `use server`.
|
||||
1068
node_modules/next/dist/docs/01-app/03-api-reference/02-components/font.md
generated
vendored
Normal file
1068
node_modules/next/dist/docs/01-app/03-api-reference/02-components/font.md
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
406
node_modules/next/dist/docs/01-app/03-api-reference/02-components/form.md
generated
vendored
Normal file
406
node_modules/next/dist/docs/01-app/03-api-reference/02-components/form.md
generated
vendored
Normal file
@@ -0,0 +1,406 @@
|
||||
---
|
||||
title: Form Component
|
||||
description: Learn how to use the `<Form>` component to handle form submissions and search params updates with client-side navigation.
|
||||
---
|
||||
|
||||
The `<Form>` component extends the HTML `<form>` element to provide <AppOnly>[**prefetching**](/docs/app/getting-started/linking-and-navigating#prefetching) of [loading UI](/docs/app/api-reference/file-conventions/loading),</AppOnly> **client-side navigation** on submission, and **progressive enhancement**.
|
||||
|
||||
It's useful for forms that update URL search params as it reduces the boilerplate code needed to achieve the above.
|
||||
|
||||
Basic usage:
|
||||
|
||||
<AppOnly>
|
||||
|
||||
```tsx filename="/app/ui/search.tsx" switcher
|
||||
import Form from 'next/form'
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<Form action="/search">
|
||||
{/* On submission, the input value will be appended to
|
||||
the URL, e.g. /search?query=abc */}
|
||||
<input name="query" />
|
||||
<button type="submit">Submit</button>
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="/app/ui/search.js" switcher
|
||||
import Form from 'next/form'
|
||||
|
||||
export default function Search() {
|
||||
return (
|
||||
<Form action="/search">
|
||||
{/* On submission, the input value will be appended to
|
||||
the URL, e.g. /search?query=abc */}
|
||||
<input name="query" />
|
||||
<button type="submit">Submit</button>
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
</AppOnly>
|
||||
|
||||
<PagesOnly>
|
||||
|
||||
```tsx filename="/ui/search.js" switcher
|
||||
import Form from 'next/form'
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<Form action="/search">
|
||||
{/* On submission, the input value will be appended to
|
||||
the URL, e.g. /search?query=abc */}
|
||||
<input name="query" />
|
||||
<button type="submit">Submit</button>
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="/ui/search.js" switcher
|
||||
import Form from 'next/form'
|
||||
|
||||
export default function Search() {
|
||||
return (
|
||||
<Form action="/search">
|
||||
{/* On submission, the input value will be appended to
|
||||
the URL, e.g. /search?query=abc */}
|
||||
<input name="query" />
|
||||
<button type="submit">Submit</button>
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
</PagesOnly>
|
||||
|
||||
## Reference
|
||||
|
||||
The behavior of the `<Form>` component depends on whether the `action` prop is passed a `string` or `function`.
|
||||
|
||||
<AppOnly>
|
||||
|
||||
- When `action` is a **string**, the `<Form>` behaves like a native HTML form that uses a **`GET`** method. The form data is encoded into the URL as search params, and when the form is submitted, it navigates to the specified URL. In addition, Next.js:
|
||||
- [Prefetches](/docs/app/getting-started/linking-and-navigating#prefetching) the path when the form becomes visible, this preloads shared UI (e.g. `layout.js` and `loading.js`), resulting in faster navigation.
|
||||
- Performs a [client-side navigation](/docs/app/getting-started/linking-and-navigating#client-side-transitions) instead of a full page reload when the form is submitted. This retains shared UI and client-side state.
|
||||
- When `action` is a **function** (Server Action), `<Form>` behaves like a [React form](https://react.dev/reference/react-dom/components/form), executing the action when the form is submitted.
|
||||
|
||||
</AppOnly>
|
||||
|
||||
<PagesOnly>
|
||||
|
||||
- When `action` is a **string**, the `<Form>` behaves like a native HTML form that uses a **`GET`** method. The form data is encoded into the URL as search params, and when the form is submitted, it navigates to the specified URL. In addition, Next.js:
|
||||
- Performs a [client-side navigation](/docs/app/getting-started/linking-and-navigating#client-side-transitions) instead of a full page reload when the form is submitted. This retains shared UI and client-side state.
|
||||
|
||||
</PagesOnly>
|
||||
|
||||
### `action` (string) Props
|
||||
|
||||
<PagesOnly>
|
||||
|
||||
When `action` is a string, the `<Form>` component supports the following props:
|
||||
|
||||
| Prop | Example | Type | Required |
|
||||
| --------- | ------------------ | ------------------------------- | -------- |
|
||||
| `action` | `action="/search"` | `string` (URL or relative path) | Yes |
|
||||
| `replace` | `replace={false}` | `boolean` | - |
|
||||
| `scroll` | `scroll={true}` | `boolean` | - |
|
||||
|
||||
- **`action`**: The URL or path to navigate to when the form is submitted.
|
||||
- An empty string `""` will navigate to the same route with updated search params.
|
||||
- **`replace`**: Replaces the current history state instead of pushing a new one to the [browser's history](https://developer.mozilla.org/en-US/docs/Web/API/History_API) stack. Default is `false`.
|
||||
- **`scroll`**: Controls the scroll behavior during navigation. Defaults to `true`, this means it will scroll to the top of the new route, and maintain the scroll position for backwards and forwards navigation.
|
||||
|
||||
</PagesOnly>
|
||||
|
||||
<AppOnly>
|
||||
|
||||
When `action` is a string, the `<Form>` component supports the following props:
|
||||
|
||||
| Prop | Example | Type | Required |
|
||||
| ---------- | ------------------ | ------------------------------- | -------- |
|
||||
| `action` | `action="/search"` | `string` (URL or relative path) | Yes |
|
||||
| `replace` | `replace={false}` | `boolean` | - |
|
||||
| `scroll` | `scroll={true}` | `boolean` | - |
|
||||
| `prefetch` | `prefetch={true}` | `boolean` | - |
|
||||
|
||||
- **`action`**: The URL or path to navigate to when the form is submitted.
|
||||
- An empty string `""` will navigate to the same route with updated search params.
|
||||
- **`replace`**: Replaces the current history state instead of pushing a new one to the [browser's history](https://developer.mozilla.org/en-US/docs/Web/API/History_API) stack. Default is `false`.
|
||||
- **`scroll`**: Controls the scroll behavior during navigation. Defaults to `true`, this means it will scroll to the top of the new route, and maintain the scroll position for backwards and forwards navigation.
|
||||
- **`prefetch`**: Controls whether the path should be prefetched when the form becomes visible in the user's viewport. Defaults to `true`.
|
||||
|
||||
### `action` (function) Props
|
||||
|
||||
When `action` is a function, the `<Form>` component supports the following prop:
|
||||
|
||||
| Prop | Example | Type | Required |
|
||||
| -------- | ------------------- | -------------------------- | -------- |
|
||||
| `action` | `action={myAction}` | `function` (Server Action) | Yes |
|
||||
|
||||
- **`action`**: The Server Action to be called when the form is submitted. See the [React docs](https://react.dev/reference/react-dom/components/form#props) for more.
|
||||
|
||||
> **Good to know**: When `action` is a function, the `replace` and `scroll` props are ignored.
|
||||
|
||||
</AppOnly>
|
||||
|
||||
### Caveats
|
||||
|
||||
<AppOnly>
|
||||
|
||||
- **`formAction`**: Can be used in a `<button>` or `<input type="submit">` fields to override the `action` prop. Next.js will perform a client-side navigation, however, this approach doesn't support prefetching.
|
||||
- When using [`basePath`](/docs/app/api-reference/config/next-config-js/basePath), you must also include it in the `formAction` path. e.g. `formAction="/base-path/search"`.
|
||||
- **`key`**: Passing a `key` prop to a string `action` is not supported. If you'd like to trigger a re-render or perform a mutation, consider using a function `action` instead.
|
||||
|
||||
</AppOnly>
|
||||
|
||||
- **`onSubmit`**: Can be used to handle form submission logic. However, calling `event.preventDefault()` will override `<Form>` behavior such as navigating to the specified URL.
|
||||
- **[`method`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form#method), [`encType`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form#enctype), [`target`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form#target)**: Are not supported as they override `<Form>` behavior.
|
||||
- Similarly, `formMethod`, `formEncType`, and `formTarget` can be used to override the `method`, `encType`, and `target` props respectively, and using them will fallback to native browser behavior.
|
||||
- If you need to use these props, use the HTML `<form>` element instead.
|
||||
- **`<input type="file">`**: Using this input type when the `action` is a string will match browser behavior by submitting the filename instead of the file object.
|
||||
|
||||
<AppOnly>
|
||||
|
||||
## Examples
|
||||
|
||||
### Search form that leads to a search result page
|
||||
|
||||
You can create a search form that navigates to a search results page by passing the path as an `action`:
|
||||
|
||||
```tsx filename="/app/page.tsx" switcher
|
||||
import Form from 'next/form'
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<Form action="/search">
|
||||
<input name="query" />
|
||||
<button type="submit">Submit</button>
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="/app/page.js" switcher
|
||||
import Form from 'next/form'
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<Form action="/search">
|
||||
<input name="query" />
|
||||
<button type="submit">Submit</button>
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
When the user updates the query input field and submits the form, the form data will be encoded into the URL as search params, e.g. `/search?query=abc`.
|
||||
|
||||
> **Good to know**: If you pass an empty string `""` to `action`, the form will navigate to the same route with updated search params.
|
||||
|
||||
On the results page, you can access the query using the [`searchParams`](/docs/app/api-reference/file-conventions/page#searchparams-optional) `page.js` prop and use it to fetch data from an external source.
|
||||
|
||||
```tsx filename="/app/search/page.tsx" switcher
|
||||
import { getSearchResults } from '@/lib/search'
|
||||
|
||||
export default async function SearchPage({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: Promise<{ [key: string]: string | string[] | undefined }>
|
||||
}) {
|
||||
const results = await getSearchResults((await searchParams).query)
|
||||
|
||||
return <div>...</div>
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="/app/search/page.js" switcher
|
||||
import { getSearchResults } from '@/lib/search'
|
||||
|
||||
export default async function SearchPage({ searchParams }) {
|
||||
const results = await getSearchResults((await searchParams).query)
|
||||
|
||||
return <div>...</div>
|
||||
}
|
||||
```
|
||||
|
||||
When the `<Form>` becomes visible in the user's viewport, shared UI (such as `layout.js` and `loading.js`) on the `/search` page will be prefetched. On submission, the form will immediately navigate to the new route and show loading UI while the results are being fetched. You can design the fallback UI using [`loading.js`](/docs/app/api-reference/file-conventions/loading):
|
||||
|
||||
```tsx filename="/app/search/loading.tsx" switcher
|
||||
export default function Loading() {
|
||||
return <div>Loading...</div>
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="/app/search/loading.js" switcher
|
||||
export default function Loading() {
|
||||
return <div>Loading...</div>
|
||||
}
|
||||
```
|
||||
|
||||
To cover cases when shared UI hasn't yet loaded, you can show instant feedback to the user using [`useFormStatus`](https://react.dev/reference/react-dom/hooks/useFormStatus).
|
||||
|
||||
First, create a component that displays a loading state when the form is pending:
|
||||
|
||||
```tsx filename="/app/ui/search-button.tsx" switcher
|
||||
'use client'
|
||||
import { useFormStatus } from 'react-dom'
|
||||
|
||||
export default function SearchButton() {
|
||||
const status = useFormStatus()
|
||||
return (
|
||||
<button type="submit">{status.pending ? 'Searching...' : 'Search'}</button>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="/app/ui/search-button.js" switcher
|
||||
'use client'
|
||||
import { useFormStatus } from 'react-dom'
|
||||
|
||||
export default function SearchButton() {
|
||||
const status = useFormStatus()
|
||||
return (
|
||||
<button type="submit">{status.pending ? 'Searching...' : 'Search'}</button>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
Then, update the search form page to use the `SearchButton` component:
|
||||
|
||||
```tsx filename="/app/page.tsx" switcher
|
||||
import Form from 'next/form'
|
||||
import { SearchButton } from '@/ui/search-button'
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<Form action="/search">
|
||||
<input name="query" />
|
||||
<SearchButton />
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="/app/ui/search-button.js" switcher
|
||||
import Form from 'next/form'
|
||||
import { SearchButton } from '@/ui/search-button'
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<Form action="/search">
|
||||
<input name="query" />
|
||||
<SearchButton />
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Mutations with Server Actions
|
||||
|
||||
You can perform mutations by passing a function to the `action` prop.
|
||||
|
||||
```tsx filename="/app/posts/create/page.tsx" switcher
|
||||
import Form from 'next/form'
|
||||
import { createPost } from '@/posts/actions'
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<Form action={createPost}>
|
||||
<input name="title" />
|
||||
{/* ... */}
|
||||
<button type="submit">Create Post</button>
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="/app/posts/create/page.js" switcher
|
||||
import Form from 'next/form'
|
||||
import { createPost } from '@/posts/actions'
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<Form action={createPost}>
|
||||
<input name="title" />
|
||||
{/* ... */}
|
||||
<button type="submit">Create Post</button>
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
After a mutation, it's common to redirect to the new resource. You can use the [`redirect`](/docs/app/guides/redirecting) function from `next/navigation` to navigate to the new post page.
|
||||
|
||||
> **Good to know**: Since the "destination" of the form submission is not known until the action is executed, `<Form>` cannot automatically prefetch shared UI.
|
||||
|
||||
```tsx filename="/app/posts/actions.ts" switcher
|
||||
'use server'
|
||||
import { redirect } from 'next/navigation'
|
||||
|
||||
export async function createPost(formData: FormData) {
|
||||
// Create a new post
|
||||
// ...
|
||||
|
||||
// Redirect to the new post
|
||||
redirect(`/posts/${data.id}`)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="/app/posts/actions.js" switcher
|
||||
'use server'
|
||||
import { redirect } from 'next/navigation'
|
||||
|
||||
export async function createPost(formData) {
|
||||
// Create a new post
|
||||
// ...
|
||||
|
||||
// Redirect to the new post
|
||||
redirect(`/posts/${data.id}`)
|
||||
}
|
||||
```
|
||||
|
||||
Then, in the new page, you can fetch data using the `params` prop:
|
||||
|
||||
```tsx filename="/app/posts/[id]/page.tsx" switcher
|
||||
import { getPost } from '@/posts/data'
|
||||
|
||||
export default async function PostPage({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ id: string }>
|
||||
}) {
|
||||
const { id } = await params
|
||||
const data = await getPost(id)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>{data.title}</h1>
|
||||
{/* ... */}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="/app/posts/[id]/page.js" switcher
|
||||
import { getPost } from '@/posts/data'
|
||||
|
||||
export default async function PostPage({ params }) {
|
||||
const { id } = await params
|
||||
const data = await getPost(id)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>{data.title}</h1>
|
||||
{/* ... */}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
See the [Server Actions](/docs/app/getting-started/mutating-data) docs for more examples.
|
||||
|
||||
</AppOnly>
|
||||
1425
node_modules/next/dist/docs/01-app/03-api-reference/02-components/image.md
generated
vendored
Normal file
1425
node_modules/next/dist/docs/01-app/03-api-reference/02-components/image.md
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
6
node_modules/next/dist/docs/01-app/03-api-reference/02-components/index.md
generated
vendored
Normal file
6
node_modules/next/dist/docs/01-app/03-api-reference/02-components/index.md
generated
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
title: Components
|
||||
description: API Reference for Next.js built-in components.
|
||||
---
|
||||
|
||||
{/* The content of this doc is shared between the app and pages router. You can use the `<PagesOnly>Content</PagesOnly>` component to add content that is specific to the Pages Router. Any shared content should not be wrapped in a component. */}
|
||||
1377
node_modules/next/dist/docs/01-app/03-api-reference/02-components/link.md
generated
vendored
Normal file
1377
node_modules/next/dist/docs/01-app/03-api-reference/02-components/link.md
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
468
node_modules/next/dist/docs/01-app/03-api-reference/02-components/script.md
generated
vendored
Normal file
468
node_modules/next/dist/docs/01-app/03-api-reference/02-components/script.md
generated
vendored
Normal file
@@ -0,0 +1,468 @@
|
||||
---
|
||||
title: Script Component
|
||||
description: Optimize third-party scripts in your Next.js application using the built-in `next/script` Component.
|
||||
---
|
||||
|
||||
{/* The content of this doc is shared between the app and pages router. You can use the `<PagesOnly>Content</PagesOnly>` component to add content that is specific to the Pages Router. Any shared content should not be wrapped in a component. */}
|
||||
|
||||
This API reference will help you understand how to use [props](#props) available for the Script Component. For features and usage, please see the [Optimizing Scripts](/docs/app/guides/scripts) page.
|
||||
|
||||
```tsx filename="app/dashboard/page.tsx" switcher
|
||||
import Script from 'next/script'
|
||||
|
||||
export default function Dashboard() {
|
||||
return (
|
||||
<>
|
||||
<Script src="https://example.com/script.js" />
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/dashboard/page.js" switcher
|
||||
import Script from 'next/script'
|
||||
|
||||
export default function Dashboard() {
|
||||
return (
|
||||
<>
|
||||
<Script src="https://example.com/script.js" />
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
Here's a summary of the props available for the Script Component:
|
||||
|
||||
| Prop | Example | Type | Required |
|
||||
| ----------------------- | --------------------------------- | -------- | ------------------------------------- |
|
||||
| [`src`](#src) | `src="http://example.com/script"` | String | Required unless inline script is used |
|
||||
| [`strategy`](#strategy) | `strategy="lazyOnload"` | String | - |
|
||||
| [`onLoad`](#onload) | `onLoad={onLoadFunc}` | Function | - |
|
||||
| [`onReady`](#onready) | `onReady={onReadyFunc}` | Function | - |
|
||||
| [`onError`](#onerror) | `onError={onErrorFunc}` | Function | - |
|
||||
|
||||
## Required Props
|
||||
|
||||
The `<Script />` component requires the following properties.
|
||||
|
||||
### `src`
|
||||
|
||||
A path string specifying the URL of an external script. This can be either an absolute external URL or an internal path. The `src` property is required unless an inline script is used.
|
||||
|
||||
## Optional Props
|
||||
|
||||
The `<Script />` component accepts a number of additional properties beyond those which are required.
|
||||
|
||||
### `strategy`
|
||||
|
||||
The loading strategy of the script. There are four different strategies that can be used:
|
||||
|
||||
- `beforeInteractive`: Load before any Next.js code and before any page hydration occurs.
|
||||
- `afterInteractive`: (**default**) Load early but after some hydration on the page occurs.
|
||||
- `lazyOnload`: Load during browser idle time.
|
||||
- `worker`: (experimental) Load in a web worker.
|
||||
|
||||
### `beforeInteractive`
|
||||
|
||||
Scripts that load with the `beforeInteractive` strategy are injected into the initial HTML from the server, downloaded before any Next.js module, and executed in the order they are placed.
|
||||
|
||||
Scripts denoted with this strategy are preloaded and fetched before any first-party code, but their execution **does not block page hydration from occurring**.
|
||||
|
||||
<AppOnly>
|
||||
|
||||
`beforeInteractive` scripts must be placed inside the root layout (`app/layout.tsx`) and are designed to load scripts that are needed by the entire site (i.e. the script will load when any page in the application has been loaded server-side).
|
||||
|
||||
</AppOnly>
|
||||
|
||||
<PagesOnly>
|
||||
|
||||
`beforeInteractive` scripts must be placed inside the `Document` Component (`pages/_document.js`) and are designed to load scripts that are needed by the entire site (i.e. the script will load when any page in the application has been loaded server-side).
|
||||
|
||||
</PagesOnly>
|
||||
|
||||
**This strategy should only be used for critical scripts that need to be fetched as soon as possible.**
|
||||
|
||||
<AppOnly>
|
||||
|
||||
```tsx filename="app/layout.tsx" switcher
|
||||
import Script from 'next/script'
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>
|
||||
{children}
|
||||
<Script
|
||||
src="https://example.com/script.js"
|
||||
strategy="beforeInteractive"
|
||||
/>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/layout.js" switcher
|
||||
import Script from 'next/script'
|
||||
|
||||
export default function RootLayout({ children }) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>
|
||||
{children}
|
||||
<Script
|
||||
src="https://example.com/script.js"
|
||||
strategy="beforeInteractive"
|
||||
/>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
</AppOnly>
|
||||
|
||||
<PagesOnly>
|
||||
|
||||
```jsx filename="pages/_document.js"
|
||||
import { Html, Head, Main, NextScript } from 'next/document'
|
||||
import Script from 'next/script'
|
||||
|
||||
export default function Document() {
|
||||
return (
|
||||
<Html>
|
||||
<Head />
|
||||
<body>
|
||||
<Main />
|
||||
<NextScript />
|
||||
<Script
|
||||
src="https://example.com/script.js"
|
||||
strategy="beforeInteractive"
|
||||
/>
|
||||
</body>
|
||||
</Html>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
</PagesOnly>
|
||||
|
||||
> **Good to know**: Scripts with `beforeInteractive` will always be injected inside the `head` of the HTML document regardless of where it's placed in the component.
|
||||
|
||||
Some examples of scripts that should be fetched as soon as possible with `beforeInteractive` include:
|
||||
|
||||
- Bot detectors
|
||||
- Cookie consent managers
|
||||
|
||||
### `afterInteractive`
|
||||
|
||||
Scripts that use the `afterInteractive` strategy are injected into the HTML client-side and will load after some (or all) hydration occurs on the page. **This is the default strategy** of the Script component and should be used for any script that needs to load as soon as possible but not before any first-party Next.js code.
|
||||
|
||||
`afterInteractive` scripts can be placed inside of any page or layout and will only load and execute when that page (or group of pages) is opened in the browser.
|
||||
|
||||
```jsx filename="app/page.js"
|
||||
import Script from 'next/script'
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<>
|
||||
<Script src="https://example.com/script.js" strategy="afterInteractive" />
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
Some examples of scripts that are good candidates for `afterInteractive` include:
|
||||
|
||||
- Tag managers
|
||||
- Analytics
|
||||
|
||||
### `lazyOnload`
|
||||
|
||||
Scripts that use the `lazyOnload` strategy are injected into the HTML client-side during browser idle time and will load after all resources on the page have been fetched. This strategy should be used for any background or low priority scripts that do not need to load early.
|
||||
|
||||
`lazyOnload` scripts can be placed inside of any page or layout and will only load and execute when that page (or group of pages) is opened in the browser.
|
||||
|
||||
```jsx filename="app/page.js"
|
||||
import Script from 'next/script'
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<>
|
||||
<Script src="https://example.com/script.js" strategy="lazyOnload" />
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
Examples of scripts that do not need to load immediately and can be fetched with `lazyOnload` include:
|
||||
|
||||
- Chat support plugins
|
||||
- Social media widgets
|
||||
|
||||
### `worker`
|
||||
|
||||
> **Warning:** The `worker` strategy is not yet stable and does not yet work with the App Router. Use with caution.
|
||||
|
||||
Scripts that use the `worker` strategy are off-loaded to a web worker in order to free up the main thread and ensure that only critical, first-party resources are processed on it. While this strategy can be used for any script, it is an advanced use case that is not guaranteed to support all third-party scripts.
|
||||
|
||||
To use `worker` as a strategy, the `nextScriptWorkers` flag must be enabled in `next.config.js`:
|
||||
|
||||
```js filename="next.config.js"
|
||||
module.exports = {
|
||||
experimental: {
|
||||
nextScriptWorkers: true,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
`worker` scripts can **only currently be used in the `pages/` directory**:
|
||||
|
||||
```tsx filename="pages/home.tsx" switcher
|
||||
import Script from 'next/script'
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<>
|
||||
<Script src="https://example.com/script.js" strategy="worker" />
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="pages/home.js" switcher
|
||||
import Script from 'next/script'
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<>
|
||||
<Script src="https://example.com/script.js" strategy="worker" />
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### `onLoad`
|
||||
|
||||
> **Warning:** `onLoad` does not yet work with Server Components and can only be used in Client Components. Further, `onLoad` can't be used with `beforeInteractive` – consider using `onReady` instead.
|
||||
|
||||
Some third-party scripts require users to run JavaScript code once after the script has finished loading in order to instantiate content or call a function. If you are loading a script with either `afterInteractive` or `lazyOnload` as a loading strategy, you can execute code after it has loaded using the `onLoad` property.
|
||||
|
||||
Here's an example of executing a lodash method only after the library has been loaded.
|
||||
|
||||
```tsx filename="app/page.tsx" switcher
|
||||
'use client'
|
||||
|
||||
import Script from 'next/script'
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<>
|
||||
<Script
|
||||
src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.20/lodash.min.js"
|
||||
onLoad={() => {
|
||||
console.log(_.sample([1, 2, 3, 4]))
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/page.js" switcher
|
||||
'use client'
|
||||
|
||||
import Script from 'next/script'
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<>
|
||||
<Script
|
||||
src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.20/lodash.min.js"
|
||||
onLoad={() => {
|
||||
console.log(_.sample([1, 2, 3, 4]))
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### `onReady`
|
||||
|
||||
> **Warning:** `onReady` does not yet work with Server Components and can only be used in Client Components.
|
||||
|
||||
Some third-party scripts require users to run JavaScript code after the script has finished loading and every time the component is mounted (after a route navigation for example). You can execute code after the script's load event when it first loads and then after every subsequent component re-mount using the `onReady` property.
|
||||
|
||||
Here's an example of how to re-instantiate a Google Maps JS embed every time the component is mounted:
|
||||
|
||||
<AppOnly>
|
||||
|
||||
```tsx filename="app/page.tsx" switcher
|
||||
'use client'
|
||||
|
||||
import { useRef } from 'react'
|
||||
import Script from 'next/script'
|
||||
|
||||
export default function Page() {
|
||||
const mapRef = useRef()
|
||||
|
||||
return (
|
||||
<>
|
||||
<div ref={mapRef}></div>
|
||||
<Script
|
||||
id="google-maps"
|
||||
src="https://maps.googleapis.com/maps/api/js"
|
||||
onReady={() => {
|
||||
new google.maps.Map(mapRef.current, {
|
||||
center: { lat: -34.397, lng: 150.644 },
|
||||
zoom: 8,
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/page.js" switcher
|
||||
'use client'
|
||||
|
||||
import { useRef } from 'react'
|
||||
import Script from 'next/script'
|
||||
|
||||
export default function Page() {
|
||||
const mapRef = useRef()
|
||||
|
||||
return (
|
||||
<>
|
||||
<div ref={mapRef}></div>
|
||||
<Script
|
||||
id="google-maps"
|
||||
src="https://maps.googleapis.com/maps/api/js"
|
||||
onReady={() => {
|
||||
new google.maps.Map(mapRef.current, {
|
||||
center: { lat: -34.397, lng: 150.644 },
|
||||
zoom: 8,
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
</AppOnly>
|
||||
|
||||
<PagesOnly>
|
||||
|
||||
```jsx
|
||||
import { useRef } from 'react'
|
||||
import Script from 'next/script'
|
||||
|
||||
export default function Page() {
|
||||
const mapRef = useRef()
|
||||
|
||||
return (
|
||||
<>
|
||||
<div ref={mapRef}></div>
|
||||
<Script
|
||||
id="google-maps"
|
||||
src="https://maps.googleapis.com/maps/api/js"
|
||||
onReady={() => {
|
||||
new google.maps.Map(mapRef.current, {
|
||||
center: { lat: -34.397, lng: 150.644 },
|
||||
zoom: 8,
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
</PagesOnly>
|
||||
|
||||
### `onError`
|
||||
|
||||
> **Warning:** `onError` does not yet work with Server Components and can only be used in Client Components. `onError` cannot be used with the `beforeInteractive` loading strategy.
|
||||
|
||||
Sometimes it is helpful to catch when a script fails to load. These errors can be handled with the `onError` property:
|
||||
|
||||
<AppOnly>
|
||||
|
||||
```tsx filename="app/page.tsx" switcher
|
||||
'use client'
|
||||
|
||||
import Script from 'next/script'
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<>
|
||||
<Script
|
||||
src="https://example.com/script.js"
|
||||
onError={(e: Error) => {
|
||||
console.error('Script failed to load', e)
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/page.js" switcher
|
||||
'use client'
|
||||
|
||||
import Script from 'next/script'
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<>
|
||||
<Script
|
||||
src="https://example.com/script.js"
|
||||
onError={(e) => {
|
||||
console.error('Script failed to load', e)
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
</AppOnly>
|
||||
|
||||
<PagesOnly>
|
||||
|
||||
```jsx
|
||||
import Script from 'next/script'
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<>
|
||||
<Script
|
||||
src="https://example.com/script.js"
|
||||
onError={(e: Error) => {
|
||||
console.error('Script failed to load', e)
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
</PagesOnly>
|
||||
|
||||
## Version History
|
||||
|
||||
| Version | Changes |
|
||||
| --------- | ------------------------------------------------------------------------- |
|
||||
| `v13.0.0` | `beforeInteractive` and `afterInteractive` is modified to support `app`. |
|
||||
| `v12.2.4` | `onReady` prop added. |
|
||||
| `v12.2.2` | Allow `next/script` with `beforeInteractive` to be placed in `_document`. |
|
||||
| `v11.0.0` | `next/script` introduced. |
|
||||
268
node_modules/next/dist/docs/01-app/03-api-reference/03-file-conventions/01-metadata/app-icons.md
generated
vendored
Normal file
268
node_modules/next/dist/docs/01-app/03-api-reference/03-file-conventions/01-metadata/app-icons.md
generated
vendored
Normal file
@@ -0,0 +1,268 @@
|
||||
---
|
||||
title: favicon, icon, and apple-icon
|
||||
description: API Reference for the Favicon, Icon and Apple Icon file conventions.
|
||||
---
|
||||
|
||||
The `favicon`, `icon`, or `apple-icon` file conventions allow you to set icons for your application.
|
||||
|
||||
They are useful for adding app icons that appear in places like web browser tabs, phone home screens, and search engine results.
|
||||
|
||||
There are two ways to set app icons:
|
||||
|
||||
- [Using image files (.ico, .jpg, .png)](#image-files-ico-jpg-png)
|
||||
- [Using code to generate an icon (.js, .ts, .tsx)](#generate-icons-using-code-js-ts-tsx)
|
||||
|
||||
## Image files (.ico, .jpg, .png)
|
||||
|
||||
Use an image file to set an app icon by placing a `favicon`, `icon`, or `apple-icon` image file within your `/app` directory.
|
||||
The `favicon` image can only be located in the top level of `app/`.
|
||||
|
||||
Next.js will evaluate the file and automatically add the appropriate tags to your app's `<head>` element.
|
||||
|
||||
| File convention | Supported file types | Valid locations |
|
||||
| --------------------------- | --------------------------------------- | --------------- |
|
||||
| [`favicon`](#favicon) | `.ico` | `app/` |
|
||||
| [`icon`](#icon) | `.ico`, `.jpg`, `.jpeg`, `.png`, `.svg` | `app/**/*` |
|
||||
| [`apple-icon`](#apple-icon) | `.jpg`, `.jpeg`, `.png` | `app/**/*` |
|
||||
|
||||
### `favicon`
|
||||
|
||||
Add a `favicon.ico` image file to the root `/app` route segment.
|
||||
|
||||
```html filename="<head> output"
|
||||
<link rel="icon" href="/favicon.ico" sizes="any" />
|
||||
```
|
||||
|
||||
### `icon`
|
||||
|
||||
Add an `icon.(ico|jpg|jpeg|png|svg)` image file.
|
||||
|
||||
```html filename="<head> output"
|
||||
<link
|
||||
rel="icon"
|
||||
href="/icon?<generated>"
|
||||
type="image/<generated>"
|
||||
sizes="<generated>"
|
||||
/>
|
||||
```
|
||||
|
||||
### `apple-icon`
|
||||
|
||||
Add an `apple-icon.(jpg|jpeg|png)` image file.
|
||||
|
||||
```html filename="<head> output"
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
href="/apple-icon?<generated>"
|
||||
type="image/<generated>"
|
||||
sizes="<generated>"
|
||||
/>
|
||||
```
|
||||
|
||||
> **Good to know**:
|
||||
>
|
||||
> - You can set multiple icons by adding a number suffix to the file name. For example, `icon1.png`, `icon2.png`, etc. Numbered files will sort lexically.
|
||||
> - Favicons can only be set in the root `/app` segment. If you need more granularity, you can use [`icon`](#icon).
|
||||
> - The appropriate `<link>` tags and attributes such as `rel`, `href`, `type`, and `sizes` are determined by the icon type and metadata of the evaluated file.
|
||||
> - For example, a 32 by 32px `.png` file will have `type="image/png"` and `sizes="32x32"` attributes.
|
||||
> - `sizes="any"` is added to icons when the extension is `.svg` or the image size of the file is not determined. More details in this [favicon handbook](https://evilmartians.com/chronicles/how-to-favicon-in-2021-six-files-that-fit-most-needs).
|
||||
|
||||
## Generate icons using code (.js, .ts, .tsx)
|
||||
|
||||
In addition to using [literal image files](#image-files-ico-jpg-png), you can programmatically **generate** icons using code.
|
||||
|
||||
Generate an app icon by creating an `icon` or `apple-icon` route that default exports a function.
|
||||
|
||||
| File convention | Supported file types |
|
||||
| --------------- | -------------------- |
|
||||
| `icon` | `.js`, `.ts`, `.tsx` |
|
||||
| `apple-icon` | `.js`, `.ts`, `.tsx` |
|
||||
|
||||
The easiest way to generate an icon is to use the [`ImageResponse`](/docs/app/api-reference/functions/image-response) API from `next/og`.
|
||||
|
||||
```tsx filename="app/icon.tsx" switcher
|
||||
import { ImageResponse } from 'next/og'
|
||||
|
||||
// Image metadata
|
||||
export const size = {
|
||||
width: 32,
|
||||
height: 32,
|
||||
}
|
||||
export const contentType = 'image/png'
|
||||
|
||||
// Image generation
|
||||
export default function Icon() {
|
||||
return new ImageResponse(
|
||||
(
|
||||
// ImageResponse JSX element
|
||||
<div
|
||||
style={{
|
||||
fontSize: 24,
|
||||
background: 'black',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
color: 'white',
|
||||
}}
|
||||
>
|
||||
A
|
||||
</div>
|
||||
),
|
||||
// ImageResponse options
|
||||
{
|
||||
// For convenience, we can re-use the exported icons size metadata
|
||||
// config to also set the ImageResponse's width and height.
|
||||
...size,
|
||||
}
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/icon.js" switcher
|
||||
import { ImageResponse } from 'next/og'
|
||||
|
||||
// Image metadata
|
||||
export const size = {
|
||||
width: 32,
|
||||
height: 32,
|
||||
}
|
||||
export const contentType = 'image/png'
|
||||
|
||||
// Image generation
|
||||
export default function Icon() {
|
||||
return new ImageResponse(
|
||||
(
|
||||
// ImageResponse JSX element
|
||||
<div
|
||||
style={{
|
||||
fontSize: 24,
|
||||
background: 'black',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
color: 'white',
|
||||
}}
|
||||
>
|
||||
A
|
||||
</div>
|
||||
),
|
||||
// ImageResponse options
|
||||
{
|
||||
// For convenience, we can re-use the exported icons size metadata
|
||||
// config to also set the ImageResponse's width and height.
|
||||
...size,
|
||||
}
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```html filename="<head> output"
|
||||
<link rel="icon" href="/icon?<generated>" type="image/png" sizes="32x32" />
|
||||
```
|
||||
|
||||
> **Good to know**:
|
||||
>
|
||||
> - By default, generated icons are [**statically optimized**](/docs/app/glossary#prerendering) (generated at build time and cached) unless they use [Request-time APIs](/docs/app/glossary#request-time-apis) or uncached data.
|
||||
> - You can generate multiple icons in the same file using [`generateImageMetadata`](/docs/app/api-reference/functions/generate-image-metadata).
|
||||
> - You cannot generate a `favicon` icon. Use [`icon`](#icon) or a [favicon.ico](#favicon) file instead.
|
||||
> - App icons are special Route Handlers that are cached by default unless they use a [Request-time API](/docs/app/glossary#request-time-apis) or [dynamic config](/docs/app/guides/caching-without-cache-components#dynamic) option.
|
||||
|
||||
### Props
|
||||
|
||||
The default export function receives the following props:
|
||||
|
||||
#### `params` (optional)
|
||||
|
||||
A promise that resolves to an object containing the [dynamic route parameters](/docs/app/api-reference/file-conventions/dynamic-routes) object from the root segment down to the segment `icon` or `apple-icon` is colocated in.
|
||||
|
||||
> **Good to know**: If you use [`generateImageMetadata`](/docs/app/api-reference/functions/generate-image-metadata), the function will also receive an `id` prop that is a promise resolving to the `id` value from one of the items returned by `generateImageMetadata`.
|
||||
|
||||
```tsx filename="app/shop/[slug]/icon.tsx" switcher
|
||||
export default async function Icon({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ slug: string }>
|
||||
}) {
|
||||
const { slug } = await params
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/shop/[slug]/icon.js" switcher
|
||||
export default async function Icon({ params }) {
|
||||
const { slug } = await params
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
| Route | URL | `params` |
|
||||
| ------------------------------- | ----------- | ---------------------------------- |
|
||||
| `app/shop/icon.js` | `/shop` | `undefined` |
|
||||
| `app/shop/[slug]/icon.js` | `/shop/1` | `Promise<{ slug: '1' }>` |
|
||||
| `app/shop/[tag]/[item]/icon.js` | `/shop/1/2` | `Promise<{ tag: '1', item: '2' }>` |
|
||||
|
||||
### Returns
|
||||
|
||||
The default export function should return a `Blob` | `ArrayBuffer` | `TypedArray` | `DataView` | `ReadableStream` | `Response`.
|
||||
|
||||
> **Good to know**: `ImageResponse` satisfies this return type.
|
||||
|
||||
### Config exports
|
||||
|
||||
You can optionally configure the icon's metadata by exporting `size` and `contentType` variables from the `icon` or `apple-icon` route.
|
||||
|
||||
| Option | Type |
|
||||
| ----------------------------- | --------------------------------------------------------------------------------------------------------------- |
|
||||
| [`size`](#size) | `{ width: number; height: number }` |
|
||||
| [`contentType`](#contenttype) | `string` - [image MIME type](https://developer.mozilla.org/docs/Web/HTTP/Basics_of_HTTP/MIME_types#image_types) |
|
||||
|
||||
#### `size`
|
||||
|
||||
```tsx filename="icon.tsx | apple-icon.tsx" switcher
|
||||
export const size = { width: 32, height: 32 }
|
||||
|
||||
export default function Icon() {}
|
||||
```
|
||||
|
||||
```jsx filename="icon.js | apple-icon.js" switcher
|
||||
export const size = { width: 32, height: 32 }
|
||||
|
||||
export default function Icon() {}
|
||||
```
|
||||
|
||||
```html filename="<head> output"
|
||||
<link rel="icon" sizes="32x32" />
|
||||
```
|
||||
|
||||
#### `contentType`
|
||||
|
||||
```tsx filename="icon.tsx | apple-icon.tsx" switcher
|
||||
export const contentType = 'image/png'
|
||||
|
||||
export default function Icon() {}
|
||||
```
|
||||
|
||||
```jsx filename="icon.js | apple-icon.js" switcher
|
||||
export const contentType = 'image/png'
|
||||
|
||||
export default function Icon() {}
|
||||
```
|
||||
|
||||
```html filename="<head> output"
|
||||
<link rel="icon" type="image/png" />
|
||||
```
|
||||
|
||||
#### Route Segment Config
|
||||
|
||||
`icon` and `apple-icon` are specialized [Route Handlers](/docs/app/api-reference/file-conventions/route) that can use the same [route segment configuration](/docs/app/api-reference/file-conventions/route-segment-config) options as Pages and Layouts.
|
||||
|
||||
## Version History
|
||||
|
||||
| Version | Changes |
|
||||
| --------- | ---------------------------------------------------- |
|
||||
| `v16.0.0` | `params` is now a promise that resolves to an object |
|
||||
| `v13.3.0` | `favicon` `icon` and `apple-icon` introduced |
|
||||
16
node_modules/next/dist/docs/01-app/03-api-reference/03-file-conventions/01-metadata/index.md
generated
vendored
Normal file
16
node_modules/next/dist/docs/01-app/03-api-reference/03-file-conventions/01-metadata/index.md
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
---
|
||||
title: Metadata Files API Reference
|
||||
nav_title: Metadata Files
|
||||
description: API documentation for the metadata file conventions.
|
||||
---
|
||||
|
||||
This section of the docs covers **Metadata file conventions**. File-based metadata can be defined by adding special metadata files to route segments.
|
||||
|
||||
Each file convention can be defined using a static file (e.g. `opengraph-image.jpg`), or a dynamic variant that uses code to generate the file (e.g. `opengraph-image.js`).
|
||||
|
||||
Once a file is defined, Next.js will automatically serve the file (with hashes in production for caching) and update the relevant head elements with the correct metadata, such as the asset's URL, file type, and image size.
|
||||
|
||||
> **Good to know**:
|
||||
>
|
||||
> - Special Route Handlers like [`sitemap.ts`](/docs/app/api-reference/file-conventions/metadata/sitemap), [`opengraph-image.tsx`](/docs/app/api-reference/file-conventions/metadata/opengraph-image), and [`icon.tsx`](/docs/app/api-reference/file-conventions/metadata/app-icons), and other [metadata files](/docs/app/api-reference/file-conventions/metadata) are cached by default.
|
||||
> - If using along with [`proxy.ts`](/docs/app/api-reference/file-conventions/proxy), [configure the matcher](/docs/app/api-reference/file-conventions/proxy#matcher) to exclude the metadata files.
|
||||
72
node_modules/next/dist/docs/01-app/03-api-reference/03-file-conventions/01-metadata/manifest.md
generated
vendored
Normal file
72
node_modules/next/dist/docs/01-app/03-api-reference/03-file-conventions/01-metadata/manifest.md
generated
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
---
|
||||
title: manifest.json
|
||||
description: API Reference for manifest.json file.
|
||||
---
|
||||
|
||||
Add or generate a `manifest.(json|webmanifest)` file that matches the [Web Manifest Specification](https://developer.mozilla.org/docs/Web/Manifest) in the **root** of `app` directory to provide information about your web application for the browser.
|
||||
|
||||
## Static Manifest file
|
||||
|
||||
```json filename="app/manifest.json | app/manifest.webmanifest"
|
||||
{
|
||||
"name": "My Next.js Application",
|
||||
"short_name": "Next.js App",
|
||||
"description": "An application built with Next.js",
|
||||
"start_url": "/"
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
## Generate a Manifest file
|
||||
|
||||
Add a `manifest.js` or `manifest.ts` file that returns a [`Manifest` object](#manifest-object).
|
||||
|
||||
> Good to know: `manifest.js` is a special Route Handlers that is cached by default unless it uses a [Request-time API](/docs/app/glossary#request-time-apis) or [dynamic config](/docs/app/guides/caching-without-cache-components#dynamic) option.
|
||||
|
||||
```ts filename="app/manifest.ts" switcher
|
||||
import type { MetadataRoute } from 'next'
|
||||
|
||||
export default function manifest(): MetadataRoute.Manifest {
|
||||
return {
|
||||
name: 'Next.js App',
|
||||
short_name: 'Next.js App',
|
||||
description: 'Next.js App',
|
||||
start_url: '/',
|
||||
display: 'standalone',
|
||||
background_color: '#fff',
|
||||
theme_color: '#fff',
|
||||
icons: [
|
||||
{
|
||||
src: '/favicon.ico',
|
||||
sizes: 'any',
|
||||
type: 'image/x-icon',
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js filename="app/manifest.js" switcher
|
||||
export default function manifest() {
|
||||
return {
|
||||
name: 'Next.js App',
|
||||
short_name: 'Next.js App',
|
||||
description: 'Next.js App',
|
||||
start_url: '/',
|
||||
display: 'standalone',
|
||||
background_color: '#fff',
|
||||
theme_color: '#fff',
|
||||
icons: [
|
||||
{
|
||||
src: '/favicon.ico',
|
||||
sizes: 'any',
|
||||
type: 'image/x-icon',
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Manifest Object
|
||||
|
||||
The manifest object contains an extensive list of options that may be updated due to new web standards. For information on all the current options, refer to the `MetadataRoute.Manifest` type in your code editor if using [TypeScript](/docs/app/api-reference/config/typescript#ide-plugin) or see the [MDN](https://developer.mozilla.org/docs/Web/Manifest) docs.
|
||||
529
node_modules/next/dist/docs/01-app/03-api-reference/03-file-conventions/01-metadata/opengraph-image.md
generated
vendored
Normal file
529
node_modules/next/dist/docs/01-app/03-api-reference/03-file-conventions/01-metadata/opengraph-image.md
generated
vendored
Normal file
@@ -0,0 +1,529 @@
|
||||
---
|
||||
title: opengraph-image and twitter-image
|
||||
description: API Reference for the Open Graph Image and Twitter Image file conventions.
|
||||
---
|
||||
|
||||
The `opengraph-image` and `twitter-image` file conventions allow you to set Open Graph and Twitter images for a route segment.
|
||||
|
||||
They are useful for setting the images that appear on social networks and messaging apps when a user shares a link to your site.
|
||||
|
||||
There are two ways to set Open Graph and Twitter images:
|
||||
|
||||
- [Using image files (.jpg, .png, .gif)](#image-files-jpg-png-gif)
|
||||
- [Using code to generate images (.js, .ts, .tsx)](#generate-images-using-code-js-ts-tsx)
|
||||
|
||||
## Image files (.jpg, .png, .gif)
|
||||
|
||||
Use an image file to set a route segment's shared image by placing an `opengraph-image` or `twitter-image` image file in the segment.
|
||||
|
||||
Next.js will evaluate the file and automatically add the appropriate tags to your app's `<head>` element.
|
||||
|
||||
| File convention | Supported file types |
|
||||
| ----------------------------------------------- | ------------------------------- |
|
||||
| [`opengraph-image`](#opengraph-image) | `.jpg`, `.jpeg`, `.png`, `.gif` |
|
||||
| [`twitter-image`](#twitter-image) | `.jpg`, `.jpeg`, `.png`, `.gif` |
|
||||
| [`opengraph-image.alt`](#opengraph-imagealttxt) | `.txt` |
|
||||
| [`twitter-image.alt`](#twitter-imagealttxt) | `.txt` |
|
||||
|
||||
> **Good to know**:
|
||||
>
|
||||
> The `twitter-image` file size must not exceed [5MB](https://developer.x.com/en/docs/x-for-websites/cards/overview/summary), and the `opengraph-image` file size must not exceed [8MB](https://developers.facebook.com/docs/sharing/webmasters/images). If the image file size exceeds these limits, the build will fail.
|
||||
|
||||
### `opengraph-image`
|
||||
|
||||
Add an `opengraph-image.(jpg|jpeg|png|gif)` image file to any route segment.
|
||||
|
||||
```html filename="<head> output"
|
||||
<meta property="og:image" content="<generated>" />
|
||||
<meta property="og:image:type" content="<generated>" />
|
||||
<meta property="og:image:width" content="<generated>" />
|
||||
<meta property="og:image:height" content="<generated>" />
|
||||
```
|
||||
|
||||
### `twitter-image`
|
||||
|
||||
Add a `twitter-image.(jpg|jpeg|png|gif)` image file to any route segment.
|
||||
|
||||
```html filename="<head> output"
|
||||
<meta name="twitter:image" content="<generated>" />
|
||||
<meta name="twitter:image:type" content="<generated>" />
|
||||
<meta name="twitter:image:width" content="<generated>" />
|
||||
<meta name="twitter:image:height" content="<generated>" />
|
||||
```
|
||||
|
||||
### `opengraph-image.alt.txt`
|
||||
|
||||
Add an accompanying `opengraph-image.alt.txt` file in the same route segment as the `opengraph-image.(jpg|jpeg|png|gif)` image it's alt text.
|
||||
|
||||
```txt filename="opengraph-image.alt.txt"
|
||||
About Acme
|
||||
```
|
||||
|
||||
```html filename="<head> output"
|
||||
<meta property="og:image:alt" content="About Acme" />
|
||||
```
|
||||
|
||||
### `twitter-image.alt.txt`
|
||||
|
||||
Add an accompanying `twitter-image.alt.txt` file in the same route segment as the `twitter-image.(jpg|jpeg|png|gif)` image it's alt text.
|
||||
|
||||
```txt filename="twitter-image.alt.txt"
|
||||
About Acme
|
||||
```
|
||||
|
||||
```html filename="<head> output"
|
||||
<meta property="twitter:image:alt" content="About Acme" />
|
||||
```
|
||||
|
||||
## Generate images using code (.js, .ts, .tsx)
|
||||
|
||||
In addition to using [literal image files](#image-files-jpg-png-gif), you can programmatically **generate** images using code.
|
||||
|
||||
Generate a route segment's shared image by creating an `opengraph-image` or `twitter-image` route that default exports a function.
|
||||
|
||||
| File convention | Supported file types |
|
||||
| ----------------- | -------------------- |
|
||||
| `opengraph-image` | `.js`, `.ts`, `.tsx` |
|
||||
| `twitter-image` | `.js`, `.ts`, `.tsx` |
|
||||
|
||||
> **Good to know**:
|
||||
>
|
||||
> - By default, generated images are [**statically optimized**](/docs/app/glossary#prerendering) (generated at build time and cached) unless they use [Request-time APIs](/docs/app/glossary#request-time-apis) or uncached data.
|
||||
> - You can generate multiple Images in the same file using [`generateImageMetadata`](/docs/app/api-reference/functions/generate-image-metadata).
|
||||
> - `opengraph-image.js` and `twitter-image.js` are special Route Handlers that is cached by default unless it uses a [Request-time API](/docs/app/glossary#request-time-apis) or [dynamic config](/docs/app/guides/caching-without-cache-components#dynamic) option.
|
||||
|
||||
The easiest way to generate an image is to use the [ImageResponse](/docs/app/api-reference/functions/image-response) API from `next/og`.
|
||||
|
||||
```tsx filename="app/about/opengraph-image.tsx" switcher
|
||||
import { ImageResponse } from 'next/og'
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { join } from 'node:path'
|
||||
|
||||
// Image metadata
|
||||
export const alt = 'About Acme'
|
||||
export const size = {
|
||||
width: 1200,
|
||||
height: 630,
|
||||
}
|
||||
|
||||
export const contentType = 'image/png'
|
||||
|
||||
// Image generation
|
||||
export default async function Image() {
|
||||
// Font loading, process.cwd() is Next.js project directory
|
||||
const interSemiBold = await readFile(
|
||||
join(process.cwd(), 'assets/Inter-SemiBold.ttf')
|
||||
)
|
||||
|
||||
return new ImageResponse(
|
||||
(
|
||||
// ImageResponse JSX element
|
||||
<div
|
||||
style={{
|
||||
fontSize: 128,
|
||||
background: 'white',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
About Acme
|
||||
</div>
|
||||
),
|
||||
// ImageResponse options
|
||||
{
|
||||
// For convenience, we can re-use the exported opengraph-image
|
||||
// size config to also set the ImageResponse's width and height.
|
||||
...size,
|
||||
fonts: [
|
||||
{
|
||||
name: 'Inter',
|
||||
data: interSemiBold,
|
||||
style: 'normal',
|
||||
weight: 400,
|
||||
},
|
||||
],
|
||||
}
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/about/opengraph-image.js" switcher
|
||||
import { ImageResponse } from 'next/og'
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { join } from 'node:path'
|
||||
|
||||
// Image metadata
|
||||
export const alt = 'About Acme'
|
||||
export const size = {
|
||||
width: 1200,
|
||||
height: 630,
|
||||
}
|
||||
|
||||
export const contentType = 'image/png'
|
||||
|
||||
// Image generation
|
||||
export default async function Image() {
|
||||
// Font loading, process.cwd() is Next.js project directory
|
||||
const interSemiBold = await readFile(
|
||||
join(process.cwd(), 'assets/Inter-SemiBold.ttf')
|
||||
)
|
||||
|
||||
return new ImageResponse(
|
||||
(
|
||||
// ImageResponse JSX element
|
||||
<div
|
||||
style={{
|
||||
fontSize: 128,
|
||||
background: 'white',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
About Acme
|
||||
</div>
|
||||
),
|
||||
// ImageResponse options
|
||||
{
|
||||
// For convenience, we can re-use the exported opengraph-image
|
||||
// size config to also set the ImageResponse's width and height.
|
||||
...size,
|
||||
fonts: [
|
||||
{
|
||||
name: 'Inter',
|
||||
data: interSemiBold,
|
||||
style: 'normal',
|
||||
weight: 400,
|
||||
},
|
||||
],
|
||||
}
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```html filename="<head> output"
|
||||
<meta property="og:image" content="<generated>" />
|
||||
<meta property="og:image:alt" content="About Acme" />
|
||||
<meta property="og:image:type" content="image/png" />
|
||||
<meta property="og:image:width" content="1200" />
|
||||
<meta property="og:image:height" content="630" />
|
||||
```
|
||||
|
||||
### Props
|
||||
|
||||
The default export function receives the following props:
|
||||
|
||||
#### `params` (optional)
|
||||
|
||||
A promise that resolves to an object containing the [dynamic route parameters](/docs/app/api-reference/file-conventions/dynamic-routes) object from the root segment down to the segment `opengraph-image` or `twitter-image` is colocated in.
|
||||
|
||||
> **Good to know**: If you use [`generateImageMetadata`](/docs/app/api-reference/functions/generate-image-metadata), the function will also receive an `id` prop that is a promise resolving to the `id` value from one of the items returned by `generateImageMetadata`.
|
||||
|
||||
```tsx filename="app/shop/[slug]/opengraph-image.tsx" switcher
|
||||
export default async function Image({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ slug: string }>
|
||||
}) {
|
||||
const { slug } = await params
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/shop/[slug]/opengraph-image.js" switcher
|
||||
export default async function Image({ params }) {
|
||||
const { slug } = await params
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
| Route | URL | `params` |
|
||||
| ------------------------------------------ | ----------- | ---------------------------------- |
|
||||
| `app/shop/opengraph-image.js` | `/shop` | `undefined` |
|
||||
| `app/shop/[slug]/opengraph-image.js` | `/shop/1` | `Promise<{ slug: '1' }>` |
|
||||
| `app/shop/[tag]/[item]/opengraph-image.js` | `/shop/1/2` | `Promise<{ tag: '1', item: '2' }>` |
|
||||
|
||||
### Returns
|
||||
|
||||
The default export function should return a `Blob` | `ArrayBuffer` | `TypedArray` | `DataView` | `ReadableStream` | `Response`.
|
||||
|
||||
> **Good to know**: `ImageResponse` satisfies this return type.
|
||||
|
||||
### Config exports
|
||||
|
||||
You can optionally configure the image's metadata by exporting `alt`, `size`, and `contentType` variables from `opengraph-image` or `twitter-image` route.
|
||||
|
||||
| Option | Type |
|
||||
| ----------------------------- | --------------------------------------------------------------------------------------------------------------- |
|
||||
| [`alt`](#alt) | `string` |
|
||||
| [`size`](#size) | `{ width: number; height: number }` |
|
||||
| [`contentType`](#contenttype) | `string` - [image MIME type](https://developer.mozilla.org/docs/Web/HTTP/Basics_of_HTTP/MIME_types#image_types) |
|
||||
|
||||
#### `alt`
|
||||
|
||||
```tsx filename="opengraph-image.tsx | twitter-image.tsx" switcher
|
||||
export const alt = 'My images alt text'
|
||||
|
||||
export default function Image() {}
|
||||
```
|
||||
|
||||
```jsx filename="opengraph-image.js | twitter-image.js" switcher
|
||||
export const alt = 'My images alt text'
|
||||
|
||||
export default function Image() {}
|
||||
```
|
||||
|
||||
```html filename="<head> output"
|
||||
<meta property="og:image:alt" content="My images alt text" />
|
||||
```
|
||||
|
||||
#### `size`
|
||||
|
||||
```tsx filename="opengraph-image.tsx | twitter-image.tsx" switcher
|
||||
export const size = { width: 1200, height: 630 }
|
||||
|
||||
export default function Image() {}
|
||||
```
|
||||
|
||||
```jsx filename="opengraph-image.js | twitter-image.js" switcher
|
||||
export const size = { width: 1200, height: 630 }
|
||||
|
||||
export default function Image() {}
|
||||
```
|
||||
|
||||
```html filename="<head> output"
|
||||
<meta property="og:image:width" content="1200" />
|
||||
<meta property="og:image:height" content="630" />
|
||||
```
|
||||
|
||||
#### `contentType`
|
||||
|
||||
```tsx filename="opengraph-image.tsx | twitter-image.tsx" switcher
|
||||
export const contentType = 'image/png'
|
||||
|
||||
export default function Image() {}
|
||||
```
|
||||
|
||||
```jsx filename="opengraph-image.js | twitter-image.js" switcher
|
||||
export const contentType = 'image/png'
|
||||
|
||||
export default function Image() {}
|
||||
```
|
||||
|
||||
```html filename="<head> output"
|
||||
<meta property="og:image:type" content="image/png" />
|
||||
```
|
||||
|
||||
#### Route Segment Config
|
||||
|
||||
`opengraph-image` and `twitter-image` are specialized [Route Handlers](/docs/app/api-reference/file-conventions/route) that can use the same [route segment configuration](/docs/app/api-reference/file-conventions/route-segment-config) options as Pages and Layouts.
|
||||
|
||||
### Examples
|
||||
|
||||
#### Using external data
|
||||
|
||||
This example uses the `params` object and external data to generate the image.
|
||||
|
||||
> **Good to know**:
|
||||
> By default, this generated image will be statically optimized. You can configure the individual `fetch` [`options`](/docs/app/api-reference/functions/fetch) or route segments [options](/docs/app/guides/caching-without-cache-components#route-segment-config-revalidate) to change this behavior.
|
||||
|
||||
```tsx filename="app/posts/[slug]/opengraph-image.tsx" switcher
|
||||
import { ImageResponse } from 'next/og'
|
||||
|
||||
export const alt = 'About Acme'
|
||||
export const size = {
|
||||
width: 1200,
|
||||
height: 630,
|
||||
}
|
||||
export const contentType = 'image/png'
|
||||
|
||||
export default async function Image({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ slug: string }>
|
||||
}) {
|
||||
const { slug } = await params
|
||||
const post = await fetch(`https://.../posts/${slug}`).then((res) =>
|
||||
res.json()
|
||||
)
|
||||
|
||||
return new ImageResponse(
|
||||
(
|
||||
<div
|
||||
style={{
|
||||
fontSize: 48,
|
||||
background: 'white',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
{post.title}
|
||||
</div>
|
||||
),
|
||||
{
|
||||
...size,
|
||||
}
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/posts/[slug]/opengraph-image.js" switcher
|
||||
import { ImageResponse } from 'next/og'
|
||||
|
||||
export const alt = 'About Acme'
|
||||
export const size = {
|
||||
width: 1200,
|
||||
height: 630,
|
||||
}
|
||||
export const contentType = 'image/png'
|
||||
|
||||
export default async function Image({ params }) {
|
||||
const { slug } = await params
|
||||
const post = await fetch(`https://.../posts/${slug}`).then((res) =>
|
||||
res.json()
|
||||
)
|
||||
|
||||
return new ImageResponse(
|
||||
(
|
||||
<div
|
||||
style={{
|
||||
fontSize: 48,
|
||||
background: 'white',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
{post.title}
|
||||
</div>
|
||||
),
|
||||
{
|
||||
...size,
|
||||
}
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
#### Using Node.js runtime with local assets
|
||||
|
||||
These examples use the Node.js runtime to fetch a local image from the file system and pass it to the `<img>` `src` attribute, either as a base64 string or an `ArrayBuffer`. Place the local asset relative to the project root, not the example source file.
|
||||
|
||||
```tsx filename="app/opengraph-image.tsx" switcher
|
||||
import { ImageResponse } from 'next/og'
|
||||
import { join } from 'node:path'
|
||||
import { readFile } from 'node:fs/promises'
|
||||
|
||||
export default async function Image() {
|
||||
const logoData = await readFile(join(process.cwd(), 'logo.png'), 'base64')
|
||||
const logoSrc = `data:image/png;base64,${logoData}`
|
||||
|
||||
return new ImageResponse(
|
||||
(
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<img src={logoSrc} height="100" />
|
||||
</div>
|
||||
)
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/opengraph-image.js" switcher
|
||||
import { ImageResponse } from 'next/og'
|
||||
import { join } from 'node:path'
|
||||
import { readFile } from 'node:fs/promises'
|
||||
|
||||
export default async function Image() {
|
||||
const logoData = await readFile(join(process.cwd(), 'logo.png'), 'base64')
|
||||
const logoSrc = `data:image/png;base64,${logoData}`
|
||||
|
||||
return new ImageResponse(
|
||||
(
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<img src={logoSrc} height="100" />
|
||||
</div>
|
||||
)
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
Passing an `ArrayBuffer` to the `src` attribute of an `<img>` element is not part of the HTML spec. The rendering engine used by `next/og` supports it, but because TypeScript definitions follow the spec, you need a `@ts-expect-error` directive or similar to use this [feature](https://github.com/vercel/satori/issues/606#issuecomment-2144000453).
|
||||
|
||||
```tsx filename="app/opengraph-image.tsx" switcher
|
||||
import { ImageResponse } from 'next/og'
|
||||
import { join } from 'node:path'
|
||||
import { readFile } from 'node:fs/promises'
|
||||
|
||||
export default async function Image() {
|
||||
const logoData = await readFile(join(process.cwd(), 'logo.png'))
|
||||
const logoSrc = Uint8Array.from(logoData).buffer
|
||||
|
||||
return new ImageResponse(
|
||||
(
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
{/* @ts-expect-error Satori accepts ArrayBuffer/typed arrays for <img src> at runtime */}
|
||||
<img src={logoSrc} height="100" />
|
||||
</div>
|
||||
)
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/opengraph-image.js" switcher
|
||||
import { ImageResponse } from 'next/og'
|
||||
import { join } from 'node:path'
|
||||
import { readFile } from 'node:fs/promises'
|
||||
|
||||
export default async function Image() {
|
||||
const logoData = await readFile(join(process.cwd(), 'logo.png'))
|
||||
const logoSrc = Uint8Array.from(logoData).buffer
|
||||
|
||||
return new ImageResponse(
|
||||
(
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<img src={logoSrc} height="100" />
|
||||
</div>
|
||||
)
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Version History
|
||||
|
||||
| Version | Changes |
|
||||
| --------- | ---------------------------------------------------- |
|
||||
| `v16.0.0` | `params` is now a promise that resolves to an object |
|
||||
| `v13.3.0` | `opengraph-image` and `twitter-image` introduced. |
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user