Recipe-app main
This commit is contained in:
Generated
Vendored
+429
@@ -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.
|
||||
Generated
Vendored
+421
@@ -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>
|
||||
Generated
Vendored
+360
@@ -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`.
|
||||
Generated
Vendored
+445
@@ -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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
Generated
Vendored
+593
@@ -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.
|
||||
Generated
Vendored
+724
@@ -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.
|
||||
Generated
Vendored
+594
@@ -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
@@ -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.
|
||||
Generated
Vendored
+194
@@ -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.
|
||||
Generated
Vendored
+438
@@ -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
@@ -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.
|
||||
+192
@@ -0,0 +1,192 @@
|
||||
---
|
||||
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
|
||||
/>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## 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
@@ -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',
|
||||
},
|
||||
],
|
||||
})
|
||||
```
|
||||
Generated
Vendored
+334
@@ -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.
|
||||
Generated
Vendored
+202
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
Reference in New Issue
Block a user