Parallel Routes
平行路线
Parallel Routing allows you to simultaneously or conditionally render one or more pages in the same layout. For highly dynamic sections of an app, such as dashboards and feeds on social sites, Parallel Routing can be used to implement complex routing patterns.
并行路由允许您同时或有条件地在同一布局中呈现一个或多个页面。对于应用程序的高度动态部分,例如社交网站上的仪表板和提要,并行路由可用于实现复杂的路由模式。
For example, you can simultaneously render the team and analytics pages.
例如,您可以同时呈现团队和分析页面。
Parallel Routing allows you to define independent error and loading states for each route as they're being streamed in independently.
并行路由允许您为每条路由定义独立的错误和加载状态,因为它们是独立流式传输的。
Parallel Routing also allow you to conditionally render a slot based on certain conditions, such as authentication state. This enables fully separated code on the same URL.
并行路由还允许您根据某些条件(例如身份验证状态)有条件地呈现插槽。这可以在同一 URL 上启用完全分离的代码。
Convention
Parallel routes are created using named slots. Slots are defined with the @folder convention, and are passed to the same-level layout as props.
并行路由是使用命名槽创建的。插槽使用 @folder 约定定义,并传递到与 props 相同级别的布局。
Slots are not route segments and do not affect the URL structure. The file path
/@team/memberswould be accessible at/members.插槽不是路由段,不会影响 URL 结构。文件路径 /@team/members 可以在 /members 访问。
For example, the following file structure defines two explicit slots: @analytics and @team.
例如,以下文件结构定义了两个显式槽:@analytics 和@team。
The folder structure above means that the component in app/layout.js now accepts the @analytics and @team slots props, and can render them in parallel alongside the children prop:
上面的文件夹结构意味着 app/layout.js 中的组件现在接受 @analytics 和 @team slots 属性,并且可以与 children 属性一起并行渲染它们:
app/layout.tsx
export default function Layout(props: { children: React.ReactNode; analytics: React.ReactNode; team: React.ReactNode;}) { return ( <> {props.children} {props.team} {props.analytics} </> );}
Good to know: The
childrenprop is an implicit slot that does not need to be mapped to a folder. This meansapp/page.jsis equivalent toapp/@children/page.js.提示:children 属性是一个隐式插槽,不需要映射到文件夹。这意味着 app/page.js 等同于 app/@children/page.js。
Unmatched Routes
无与伦比的路线
By default, the content rendered within a slot will match the current URL.
默认情况下,插槽中呈现的内容将与当前 URL 匹配。
In the case of an unmatched slot, the content that Next.js renders differs based on the routing technique and folder structure.
在插槽不匹配的情况下,Next.js 呈现的内容因路由技术和文件夹结构而异。
default.js
默认.js
You can define a default.js file to render as a fallback when Next.js cannot recover a slot's active state based on the current URL.
当 Next.js 无法根据当前 URL 恢复插槽的活动状态时,您可以定义一个 default.js 文件作为后备呈现。
Consider the following folder structure. The @team slot has a settings directory, but @analytics does not.
考虑以下文件夹结构。 @team 插槽有一个设置目录,但@analytics 没有。
If you were to navigate from the root / to /settings, the content that gets rendered is different based on the type of navigation and the availability of the default.js file.
如果您要从根 / 导航到 /settings,根据导航类型和 default.js 文件的可用性,呈现的内容会有所不同。
With @analytics/default.js | Without @analytics/default.js | |
|---|---|---|
| Soft Navigation | @team/settings/page.js and @analytics/page.js | @team/settings/page.js and @analytics/page.js |
| Hard Navigation | @team/settings/page.js and @analytics/default.js | 404 |
Soft Navigation
软导航
On a soft navigation - Next.js will render the slot's previously active state, even if it doesn't match the current URL.
在软导航上 - Next.js 将呈现广告位先前的活动状态,即使它与当前 URL 不匹配。
Hard Navigation
硬导航
On a hard navigation - a navigation that requires a full page reload - Next.js will first try to render the unmatched slot's default.js file. If that's not available, a 404 gets rendered.
在硬导航(需要重新加载整页的导航)中,Next.js 将首先尝试呈现不匹配的插槽的 default.js 文件。如果这不可用,则会呈现 404。
The 404 for unmatched routes helps ensure that you don't accidentally render a route that shouldn't be parallel rendered.
不匹配路由的 404 有助于确保您不会意外渲染不应并行渲染的路由。
useSelectedLayoutSegment(s)
在 SelectedLayoutSegment(s)
Both useSelectedLayoutSegment and useSelectedLayoutSegments accept a parallelRoutesKey, which allows you read the active route segment within that slot.
useSelectedLayoutSegment 和 useSelectedLayoutSegments 都接受一个 parallelRoutesKey,它允许您读取该插槽中的活动路由段。
app/layout.tsx
"use client";import { useSelectedLayoutSegment } from "next/navigation"; export default async function Layout(props: { ... authModal: React.ReactNode;}) { const loginSegments = useSelectedLayoutSegment("authModal") ...}
When a user navigates to @authModal/login, or /login in the URL bar, loginSegments will be equal to the string "login".
当用户导航到 @authModal/login 或 URL 栏中的 /login 时,loginSegments 将等于字符串“login”。
Examples
例子
Modals
模态
Parallel Routing can be used to render modals.
并行路由可用于渲染模态。
The @authModal slot renders a<Modal> component that can be shown by navigating to a matching route, for example /login.
@authModal 插槽呈现一个 组件,可以通过导航到匹配的路由(例如 /login)来显示该组件。
app/layout.tsx
export default async function Layout(props: { ... authModal: React.ReactNode;}) { return ( <> ... {props.authModal} </> );}
app/@authModal/login/page.tsx
import { Modal } from 'components/modal'; export default function Login() { return ( <Modal> <h1>Login</h1> ... </Modal> );}
To ensure that the contents of the modal don't get rendered when it's not active, you can create a default.js file that returns null.
为确保模式的内容在未处于活动状态时不会呈现,您可以创建一个返回 null 的 default.js 文件。
app/@authModal/login/default.tsx
export default function Default() { return null;}
Dismissing a modal
关闭模态
If a modal was initiated through client navigation, e.g. by using <Link href="/login">, you can dismiss the modal by calling router.back() or by using a Link component.
如果模式是通过客户端导航启动的,例如通过使用 ,您可以通过调用 router.back() 或使用 Link 组件关闭模态。
app/@authModal/login/page.tsx
'use client';import { useRouter } from 'next/navigation';import { Modal } from 'components/modal'; export default async function Login() { const router = useRouter(); return ( <Modal> <span onClick={() => router.back()}>Close modal</span> <h1>Login</h1> ... </Modal> );}
More information on modals is covered in the Intercepting Routes section.
有关模式的更多信息,请参阅拦截路由部分。
If you want to navigate elsewhere and dismiss a modal, you can also use a catch-all route.
如果你想导航到别处并关闭模态,你也可以使用 catch-all 路由。
app/@authModal/[...catchAll]/page.js
export default function CatchAll() { return null;}
Catch-all routes take presedence over
default.js.Catch-all 路由优先于 default.js。
Conditional Routes
条件路由
Parallel Routes can be used to implement conditional routing. For example, you can render a @dashboard or @login route depending on the authentication state.
Parallel Routes 可用于实现条件路由。例如,您可以根据身份验证状态呈现 @dashboard 或 @login 路由。
app/layout.tsx
import { getUser } from '@/lib/auth'; export default function Layout({ params, dashboard, login }) { const isLoggedIn = getUser(); return isLoggedIn ? dashboard : login;}
Intercepting Routes
拦截路线
Intercepting routes allows you to load a route within the current layout while keeping the context for the current page. This routing paradigm can be useful when you want to "intercept" a certain route to show a different route.
拦截路由允许您在当前布局中加载路由,同时保留当前页面的上下文。当您想“拦截”某条路线以显示不同的路线时,此路由范例会很有用。
For example, when clicking on a photo from within a feed, a modal overlaying the feed should show up with the photo. In this case, Next.js intercepts the /feed route and "masks" this URL to show /photo/123 instead.
例如,当从提要中单击照片时,覆盖提要的模式应该与照片一起显示。在这种情况下,Next.js 拦截 /feed 路由并“屏蔽”此 URL 以显示 /photo/123。
However, when navigating to the photo directly by for example when clicking a shareable URL or by refreshing the page, the entire photo page should render instead of the modal. No route interception should occur.
然而,当直接导航到照片页面时,例如通过单击可共享的URL或刷新页面时,应该渲染整个照片页面而不是模态框。不应进行路由拦截。
Convention
Intercepting routes can be defined with the (..) convention, which is similar to relative path convention ../ but for segments.
拦截路由可以使用 (..) 约定来定义,这类似于相对路径约定 ../ 但用于段。
You can use:
您可以使用:
(.)to match segments on the same level- (.) 匹配同一级别的段
(..)to match segments one level above- (..) 匹配上一级的段
(..)(..)to match segments two levels above- (..)(..) 匹配上两层的段
(...)to match segments from the rootappdirectory- (...) 匹配根应用程序目录中的段
For example, you can intercept the photo segment from within the feed segment by creating a (..)photo directory.
例如,您可以通过创建 (..)photo 目录从提要段中拦截照片段。
Note that the
(..)convention is based on route segments, not the file-system.请注意, (..) 约定基于路由段,而不是文件系统。
Examples
Modals
模态
Intercepting Routes can be used together with Parallel Routes to create modals.
拦截路由可以与并行路由一起使用来创建模态。
Using this pattern to create modals overcomes some common challenges when working with modals, by allowing you to:
使用此模式创建模态可以克服使用模态时的一些常见挑战,它允许您:
- Make the modal content shareable through a URL
- 让模态内容可以通过URL共享
- Preserve context when the page is refreshed, instead of closing the modal
- 在页面刷新时保留上下文,而不是关闭模态
- Close the modal on backwards navigation rather than going to the previous route
- 在向后导航时关闭模态,而不是转到前面的路线
- Reopen the modal on forwards navigation
- 重新打开前向导航的模式
In the above example, the path to the
photosegment can use the(..)matcher since@modalis a slot and not a segment. This means that thephotoroute is only one segment level higher, despite being two file-system levels higher.在上面的示例中,照片片段的路径可以使用 (..) 匹配器,因为 @modal 是一个插槽而不是片段。这意味着照片路由只高了一个段级别,尽管它是两个文件系统级别。
Other examples could include opening a login modal in a top navbar while also having a dedicated /login page, or opening a shopping cart in a side modal.
其他示例可能包括在顶部导航栏中打开登录模式,同时还具有专用的 /login 页面,或在侧面模式中打开购物车。
View an example of modals with Intercepted and Parallel Routes.
查看示例
具有拦截和平行路线的模态。
Route Handlers
路线处理程序
Route Handlers allow you to create custom request handlers for a given route using the Web Request and Response APIs.
路由处理程序允许您使用 Web 请求为给定路由创建自定义请求处理程序
和响应
蜜蜂。
Good to know: Route Handlers are only available inside the
appdirectory. They are the equivalent of API Routes inside thepagesdirectory meaning you do not need to use API Routes and Route Handlers together.提示:路由处理程序仅在 app 目录内可用。它们相当于页面目录中的 API 路由,这意味着您不需要同时使用 API 路由和路由处理程序。
Convention
习俗
Route Handlers are defined in a route.js|ts file inside the app directory:
路由处理程序在 app 目录内的 route.js|ts 文件中定义:
app/api/route.ts
TypeScript
export async function GET(request: Request) {}
Route Handlers can be nested 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.
Route Handlers 可以嵌套在 app 目录中,类似于 page.js 和 layout.js。但是不能有和page.js同级的route.js文件。
Supported HTTP Methods
支持的 HTTP 方法
The following 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.
以下 HTTP 方法
支持:GET、POST、PUT、PATCH、DELETE、HEAD 和 OPTIONS。如果调用了不受支持的方法,Next.js 将返回 405 Method Not Allowed 响应。
Extended NextRequest and NextResponse APIs
扩展的 NextRequest 和 NextResponse API
In addition to supporting native Request and Response. Next.js extends them with NextRequest and NextResponse to provide convenient helpers for advanced use cases.
除了支持原生的Request
和响应
. Next.js 使用 NextRequest 和 NextResponse 扩展它们,为高级用例提供方便的帮助程序。
Behavior
行为
Static Route Handlers
静态路由处理程序
Route Handlers are statically evaluated by default when using the GET method with the Response object.
将 GET 方法与 Response 对象一起使用时,默认情况下会静态评估路由处理程序。
app/items/route.ts
TypeScript
import { NextResponse } from 'next/server'; 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 NextResponse.json({ data });}
TypeScript Warning: Although
Response.json()is valid, native TypeScript types currently shows an error, you can useNextResponse.json()for typed responses instead.TypeScript 警告:尽管 Response.json() 有效,但原生 TypeScript 类型当前显示错误,您可以使用 NextResponse.json() 代替类型化响应。
Dynamic Route Handlers
动态路由处理程序
Route handlers are evaluated dynamically when:
在以下情况下会动态评估路由处理程序:
- Using the
Requestobject with theGETmethod. - 使用带有 GET 方法的 Request 对象。
- Using any of the other HTTP methods.
- 使用任何其他 HTTP 方法。
- Using Dynamic Functions like
cookiesandheaders. - 使用 cookie 和标头等动态功能。
- The Segment Config Options manually specifies dynamic mode.
- Segment Config Options 手动指定动态模式。
For example:
例如:
app/products/api/route.ts
TypeScript
import { NextResponse } from 'next/server'; export async function GET(request: Request) { const { searchParams } = new URL(request.url); const id = searchParams.get('id'); const res = await fetch(`https://data.mongodb-api.com/product/${id}`, { headers: { 'Content-Type': 'application/json', 'API-Key': process.env.DATA_API_KEY, }, }); const product = await res.json(); return NextResponse.json({ product });}
Similarly, the POST method will cause the Route Handler to be evaluated dynamically.
同样,POST 方法将导致路由处理程序被动态评估。
app/items/route.ts
TypeScript
import { NextResponse } from 'next/server'; export async function POST() { const res = await fetch('https://data.mongodb-api.com/...', { method: 'POST', headers: { 'Content-Type': 'application/json', 'API-Key': process.env.DATA_API_KEY, }, body: JSON.stringify({ time: new Date().toISOString() }), }); const data = await res.json(); return NextResponse.json(data);}
Note: Previously, API Routes could have been used for use cases like handling form submissions. Route Handlers are likely not the solution for these uses cases. We will be recommending the use of mutations for this when ready.
注意:以前,API 路由可以用于处理表单提交等用例。路由处理程序可能不是这些用例的解决方案。准备就绪后,我们将建议为此使用突变。
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.jsfile at the same route aspage.js. - route.js 文件不能与 page.js 位于同一路径。
| Page | Route | Result |
|---|---|---|
app/page.js | app/route.js | Conflict |
app/page.js | app/api/route.js | Valid |
app/[user]/page.js | app/api/route.js | Valid |
Each route.js or page.js file takes over all HTTP verbs for that route.
每个 route.js 或 page.js 文件接管该路由的所有 HTTP 动词。
app/page.js
export default function Page() { return <h1>Hello, Next.js!</h1>;} // ❌ Conflict// `app/route.js`export async function POST(request) {}
Examples
例子
The following examples show how to combine Route Handlers with other Next.js APIs and features.
以下示例展示了如何将路由处理程序与其他 Next.js API 和功能相结合。
Revalidating Static Data
重新验证静态数据
You can revalidate static data fetches using the next.revalidate option:
您可以重新验证静态数据
使用 next.revalidate 选项获取:
app/items/route.ts
TypeScript
import { NextResponse } from 'next/server'; export async function GET() { const res = await fetch('https://data.mongodb-api.com/...', { next: { revalidate: 60 }, // Revalidate every 60 seconds }); const data = await res.json(); return NextResponse.json(data);}
Alternatively, you can use the revalidate segment config option:
或者,您可以使用 revalidate segment 配置选项:
export const revalidate = 60;
Dynamic Functions
动态函数
Route Handlers can be used with dynamic functions from Next.js, like cookies and headers.
路由处理程序可以与来自 Next.js 的动态函数一起使用,例如 cookie 和标头。
Cookies
饼干
You can read cookies with cookies from next/headers. This server function can be called directly in a Route Handler, or nested inside of another function.
您可以从 next/headers 读取带有 cookie 的 cookie。这个服务器函数可以直接在路由处理程序中调用,也可以嵌套在另一个函数中。
This cookies instance is read-only. To set cookies, you need to return a new Response using the Set-Cookie header.
此 cookie 实例是只读的。要设置 cookie,您需要使用 Set-Cookie 返回一个新的 Response
标头。
app/api/route.ts
TypeScript
import { cookies } from 'next/headers'; export async function GET(request: Request) { const cookieStore = cookies(); const token = cookieStore.get('token'); return new Response('Hello, Next.js!', { status: 200, headers: { 'Set-Cookie': `token=${token}` }, });}
Alternatively, you can use abstractions on top of the underlying Web APIs to read cookies (NextRequest):
或者,您可以在底层 Web API 之上使用抽象来读取 cookie (NextRequest):
app/api/route.ts
TypeScript
import { type NextRequest } from 'next/server'; export async function GET(request: NextRequest) { const token = request.cookies.get('token');}
Headers
标头
You can read headers with headers from next/headers. This server function can be called directly in a Route Handler, or nested inside of another function.
您可以从 next/headers 中读取带有标题的标题。这个服务器函数可以直接在路由处理程序中调用,也可以嵌套在另一个函数中。
This headers instance is read-only. To set headers, you need to return a new Response with new headers.
此标头实例是只读的。要设置标头,您需要返回带有新标头的新响应。
app/api/route.ts
TypeScript
import { headers } from 'next/headers'; export async function GET(request: Request) { const headersList = headers(); const referer = headersList.get('referer'); return new Response('Hello, Next.js!', { status: 200, headers: { referer: referer }, });}
Alternatively, you can use abstractions on top of the underlying Web APIs to read headers (NextRequest):
或者,您可以在底层 Web API 之上使用抽象来读取标头 (NextRequest):
app/api/route.ts
TypeScript
import { type NextRequest } from 'next/server'; export async function GET(request: NextRequest) { const requestHeaders = new Headers(request.headers);}
Redirects
重定向
app/api/route.ts
TypeScript
import { redirect } from 'next/navigation'; export async function GET(request: Request) { redirect('https://nextjs.org/');}
Dynamic Route Segments
动态路线段
We recommend reading the Defining Routes page before continuing.
我们建议在继续之前阅读定义路由页面。
Route Handlers can use Dynamic Segments to create request handlers from dynamic data.
Route Handlers 可以使用 Dynamic Segments 从动态数据创建请求处理程序。
app/items/[slug]/route.js
export async function GET( request: Request, { params, }: { params: { slug: string }; },) { const slug = params.slug; // 'a', 'b', or 'c'}
| Route | Example URL | params |
|---|---|---|
app/items/[slug]/route.js | /items/a | { slug: 'a' } |
app/items/[slug]/route.js | /items/b | { slug: 'b' } |
app/items/[slug]/route.js | /items/c | { slug: 'c' } |
Streaming
串流
app/api/route.ts
TypeScript
// https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream#convert_async_iterator_to_streamfunction iteratorToStream(iterator: any) { return new ReadableStream({ async pull(controller) { const { value, done } = await iterator.next(); if (done) { controller.close(); } else { controller.enqueue(value); } }, });} function sleep(time: number) { return new Promise((resolve) => { setTimeout(resolve, time); });} const encoder = new TextEncoder(); async function* makeIterator() { yield encoder.encode('<p>One</p>'); await sleep(200); yield encoder.encode('<p>Two</p>'); await sleep(200); yield encoder.encode('<p>Three</p>');} export async function GET() { const iterator = makeIterator(); const stream = iteratorToStream(iterator); return new Response(stream);}
Request Body
请求正文
You can read the Request body using the standard Web API methods:
您可以使用标准 Web API 方法读取请求正文:
app/items/route.ts
TypeScript
import { NextResponse } from 'next/server'; export async function POST(request: Request) { const res = await request.json(); return NextResponse.json({ res });}
CORS
You can set CORS headers on a Response using the standard Web API methods:
您可以使用标准 Web API 方法在响应上设置 CORS 标头:
app/api/route.ts
TypeScript
export async function GET(request: Request) { return new Response('Hello, Next.js!', { status: 200, headers: { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type, Authorization', }, });}
Edge and Node.js Runtimes
Edge 和 Node.js 运行时
Route Handlers have an isomorphic Web API to support both Edge and Node.js runtimes seamlessly, including support for streaming. Since Route Handlers use the same route segment configuration as pages and layouts, they support long-awaited features like general-purpose statically regenerated Route Handlers.
Route Handlers 有一个同构的 Web API 来无缝支持 Edge 和 Node.js 运行时,包括对流的支持。由于路由处理程序使用与页面和布局相同的路由段配置,因此它们支持期待已久的功能,如通用静态重新生成的路由处理程序。
You can use the runtime segment config option to specify the runtime:
您可以使用运行时段配置选项来指定运行时:
export const runtime = 'edge'; // 'nodejs' is the default
Non-UI Responses
非 UI 响应
You can use Route Handlers to return non-UI content. Note that sitemap.xml, robots.txt, favicon.ico, and open graph images all have built-in SEO support.
您可以使用路由处理程序返回非 UI 内容。请注意,sitemap.xml、robots.txt、favicon.ico 和开放图形图像都具有内置的 SEO 支持。
app/rss.xml/route.ts
TypeScript
export async function GET() { return new Response(`<?xml version="1.0" encoding="UTF-8" ?><rss version="2.0"> <channel> <title>Next.js Documentation</title> <link>https://nextjs.org/docs</link> <description>The React Framework for the Web</description></channel> </rss>`);}
Segment Config Options
段配置选项
Route Handlers use the same route segment configuration as pages and layouts.
路由处理程序使用与页面和布局相同的路由段配置。
app/items/route.ts
TypeScript
export const dynamic = 'auto';export const dynamicParams = true;export const revalidate = false;export const fetchCache = 'auto';export const runtime = 'nodejs';export const preferredRegion = 'auto';
See the API reference for more details.
有关详细信息,请参阅 API 参考。
Middleware
中间件
Middleware 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.
中间件允许您在请求完成之前运行代码。然后,根据传入的请求,您可以通过重写、重定向、修改请求或响应标头或直接响应来修改响应。
Middleware runs before cached content and routes are matched. See Matching Paths for more details.
中间件在缓存内容和路由匹配之前运行。有关详细信息,请参阅匹配路径。
Convention
习俗
Use the file middleware.ts (or .js) in the root of your project to define Middleware. For example, at the same level as pages or app, or inside src if applicable.
使用项目根目录中的文件 middleware.ts(或 .js)来定义中间件。例如,与页面或应用处于同一级别,或者在 src 内部(如果适用)。
Example
例子
middleware.ts
import { NextResponse } from 'next/server';import type { NextRequest } from 'next/server'; // This function can be marked `async` if using `await` insideexport function middleware(request: NextRequest) { return NextResponse.redirect(new URL('/home', request.url));} // See "Matching Paths" below to learn moreexport const config = { matcher: '/about/:path*',};
Matching Paths
匹配路径
Middleware will be invoked for every route in your project. The following is the execution order:
headersfromnext.config.js- 来自 next.config.js 的标头
redirectsfromnext.config.js- 从 next.config.js 重定向
- Middleware (
rewrites,redirects, etc.) - 中间件(重写、重定向等)
beforeFiles(rewrites) fromnext.config.js- Filesystem routes (
public/,_next/static/,pages/,app/, etc.) - 文件系统路由(public/、_next/static/、pages/、app/ 等)
afterFiles(rewrites) fromnext.config.js- 来自 next.config.js 的 afterFiles(重写)
- Dynamic Routes (
/blog/[slug]) - 动态路由 (/blog/[slug])
fallback(rewrites) fromnext.config.js
There are two ways to define which paths Middleware will run on:
有两种方法可以定义中间件将在哪些路径上运行:
- Custom matcher config
- 自定义匹配器配置
- Conditional statements
- 条件语句
Matcher
火柴
matcher allows you to filter Middleware to run on specific paths.
匹配器允许您过滤中间件以在特定路径上运行。
middleware.js
export const config = { matcher: '/about/:path*',};
You can match a single path or multiple paths with an array syntax:
您可以使用数组语法匹配单个路径或多个路径:
middleware.js
export const config = { matcher: ['/about/:path*', '/dashboard/:path*'],};
The matcher config allows full regex so matching like negative lookaheads or character matching is supported. An example of a negative lookahead to match all except specific paths can be seen here:
匹配器配置允许完整的正则表达式,因此支持否定前瞻或字符匹配等匹配。可以在此处看到匹配除特定路径之外的所有路径的否定前瞻示例:
middleware.js
export const config = { matcher: [ /* * Match all request paths except for the ones starting with: * - api (API routes) * - _next/static (static files) * - _next/image (image optimization files) * - favicon.ico (favicon file) */ '/((?!api|_next/static|_next/image|favicon.ico).*)', ],};
Note: The
matchervalues need to be constants so they can be statically analyzed at build-time. Dynamic values such as variables will be ignored.注意:匹配器值需要是常量,以便可以在构建时对其进行静态分析。变量等动态值将被忽略。
Configured matchers:
配置的匹配器:
- MUST start with
/ - 必须以 / 开头
- Can include named parameters:
/about/:pathmatches/about/aand/about/bbut not/about/a/c - 可以包含命名参数:/about/:path 匹配 /about/a 和 /about/b 但不匹配 /about/a/c
- Can have modifiers on named parameters (starting with
:):/about/:path*matches/about/a/b/cbecause*is zero or more.?is zero or one and+one or more - 可以在命名参数上使用修饰符(以 : 开头):/about/:path* 匹配 /about/a/b/c 因为 * 为零或更多。 ?是零或一和一或更多
- Can use regular expression enclosed in parenthesis:
/about/(.*)is the same as/about/:path* - 可以使用括号括起来的正则表达式:/about/(.) 等同于/about/:path
Read more details on path-to-regexp documentation.
阅读有关正则表达式路径的更多详细信息
文档。
Note: For backward compatibility, Next.js always considers
/publicas/public/index. Therefore, a matcher of/public/:pathwill match.注意:为了向后兼容,Next.js 始终将 /public 视为 /public/index。因此, /public/:path 的匹配器将匹配。
Conditional Statements
middleware.ts
import { NextResponse } from 'next/server';import type { NextRequest } from 'next/server'; export function middleware(request: NextRequest) { if (request.nextUrl.pathname.startsWith('/about')) { return NextResponse.rewrite(new URL('/about-2', request.url)); } if (request.nextUrl.pathname.startsWith('/dashboard')) { return NextResponse.rewrite(new URL('/dashboard/user', request.url)); }}
NextResponse
下一个响应
The NextResponse API allows you to:
redirectthe incoming request to a different URL- 将传入请求重定向到不同的 URL
rewritethe response by displaying a given URL- 通过显示给定的 URL 重写响应
- Set request headers for API Routes,
getServerSideProps, andrewritedestinations - 为 API 路由、getServerSideProps 和重写目标设置请求标头
- Set response cookies
- Set response headers
- 设置响应头
To produce a response from Middleware, you can:
要从中间件产生响应,您可以:
rewriteto a route (Page or Edge API Route) that produces a response- 重写为产生响应的路由(页面或边缘 API 路由)
- return a
NextResponsedirectly. See Producing a Response
Using Cookies
Cookies are regular headers. On a Request, they are stored in the Cookie header. On a Response they are in the Set-Cookie header. Next.js provides a convenient way to access and manipulate these cookies through the cookies extension on NextRequest and NextResponse.
Cookie 是常规标头。在请求中,它们存储在 Cookie 标头中。在响应中,它们位于 Set-Cookie 标头中。 Next.js 通过 NextRequest 和 NextResponse 上的 cookie 扩展提供了一种访问和操作这些 cookie 的便捷方式。
- For incoming requests,
cookiescomes with the following methods:get,getAll,set, anddeletecookies. You can check for the existence of a cookie withhasor remove all cookies withclear. - 对于传入请求,cookie 带有以下方法:get、getAll、set 和 delete cookie。您可以使用 has 检查 cookie 是否存在或使用 clear 删除所有 cookie。
- For outgoing responses,
cookieshave the following methodsget,getAll,set, anddelete. - 对于传出响应,cookie 具有以下方法 get、getAll、set 和 delete。
middleware.ts
import { NextResponse } from 'next/server';import type { NextRequest } from 'next/server'; export function middleware(request: NextRequest) { // Assume a "Cookie:nextjs=fast" header to be present on the incoming request // Getting cookies from the request using the `RequestCookies` API let cookie = request.cookies.get('nextjs')?.value; console.log(cookie); // => 'fast' const allCookies = request.cookies.getAll(); console.log(allCookies); // => [{ name: 'nextjs', value: 'fast' }] request.cookies.has('nextjs'); // => true request.cookies.delete('nextjs'); request.cookies.has('nextjs'); // => false // Setting cookies on the response using the `ResponseCookies` API const response = NextResponse.next(); response.cookies.set('vercel', 'fast'); response.cookies.set({ name: 'vercel', value: 'fast', path: '/test', }); cookie = response.cookies.get('vercel'); console.log(cookie); // => { name: 'vercel', value: 'fast', Path: '/test' } // The outgoing response will have a `Set-Cookie:vercel=fast;path=/test` header. return response;}
Setting Headers
设置标题
You can set request and response headers using the NextResponse API (setting request headers is available since Next.js v13.0.0).
middleware.ts
TypeScript
import { NextResponse } from 'next/server';import type { NextRequest } from 'next/server'; export function middleware(request: NextRequest) { // Clone the request headers and set a new header `x-hello-from-middleware1` const requestHeaders = new Headers(request.headers); requestHeaders.set('x-hello-from-middleware1', 'hello'); // You can also set request headers in NextResponse.rewrite const response = NextResponse.next({ request: { // New request headers headers: requestHeaders, }, }); // Set a new response header `x-hello-from-middleware2` response.headers.set('x-hello-from-middleware2', 'hello'); return response;}
Note: Avoid setting large headers as it might cause 431 Request Header Fields Too Large error depending on your backend web server configuration.
Producing a Response
You can respond from Middleware directly by returning a Response or NextResponse instance. (This is available since Next.js v13.1.0)
您可以通过返回 Response 或 NextResponse 实例直接从中间件响应。 (自 Next.js v13.1.0 起可用
)
middleware.ts
TypeScript
import { NextRequest, NextResponse } from 'next/server';import { isAuthenticated } from '@lib/auth'; // Limit the middleware to paths starting with `/api/`export const config = { matcher: '/api/:function*',}; export function middleware(request: NextRequest) { // Call our authentication function to check the request if (!isAuthenticated(request)) { // Respond with JSON indicating an error message return new NextResponse( JSON.stringify({ success: false, message: 'authentication failed' }), { status: 401, headers: { 'content-type': 'application/json' } }, ); }}
Advanced Middleware Flags
高级中间件标志
In v13.1 of Next.js two additional flags were introduced for middleware, skipMiddlewareUrlNormalize and skipTrailingSlashRedirect to handle advanced use cases.
skipTrailingSlashRedirect allows disabling Next.js default redirects for adding or removing trailing slashes allowing custom handling inside middleware which can allow maintaining the trailing slash for some paths but not others allowing easier incremental migrations.
skipTrailingSlashRedirect 允许禁用 Next.js 默认重定向以添加或删除尾部斜杠,从而允许在中间件内部进行自定义处理,这可以允许为某些路径保留尾部斜杠,而不是其他路径,从而允许更轻松的增量迁移。
next.config.js
module.exports = { skipTrailingSlashRedirect: true,};
middleware.js
const legacyPrefixes = ['/docs', '/blog']; export default async function middleware(req) { const { pathname } = req.nextUrl; if (legacyPrefixes.some((prefix) => pathname.startsWith(prefix))) { return NextResponse.next(); } // apply trailing slash handling if ( !pathname.endsWith('/') && !pathname.match(/((?!\.well-known(?:\/.*)?)(?:[^/]+\/)*[^/]+\.\w+)/) ) { req.nextUrl.pathname += '/'; return NextResponse.redirect(req.nextUrl); }}
skipMiddlewareUrlNormalize allows disabling the URL normalizing Next.js does to make handling direct visits and client-transitions the same. There are some advanced cases where you need full control using the original URL which this unlocks.
skipMiddlewareUrlNormalize 允许禁用 URL 规范化 Next.js 确实可以处理直接访问和客户端转换。在某些高级情况下,您需要使用此解锁的原始 URL 进行完全控制。
next.config.js
module.exports = { skipMiddlewareUrlNormalize: true,};
middleware.js
export default async function middleware(req) { const { pathname } = req.nextUrl; // GET /_next/data/build-id/hello.json console.log(pathname); // with the flag this now /_next/data/build-id/hello.json // without the flag this would be normalized to /hello}
Internationalization
国际化
Next.js enables you to configure the routing and rendering of content to support multiple languages. Making your site adaptive to different locales includes translated content (localization) and internationalized routes.
Next.js 使您能够配置内容的路由和呈现以支持多种语言。使您的站点适应不同的区域设置包括翻译内容(本地化)和国际化路线。
Terminology
术语
- Locale: An identifier for a set of language and formatting preferences. This usually includes the preferred language of the user and possibly their geographic region.
en-US: English as spoken in the United States- en-US:在美国使用的英语
nl-NL: Dutch as spoken in the Netherlands- nl-NL:荷兰语中的荷兰语
nl: Dutch, no specific region- nl:荷兰语,无特定区域
- 区域设置:一组语言和格式首选项的标识符。这通常包括用户的首选语言以及可能的地理区域。
en-US:在美国使用的英语
nl-NL:荷兰语中的荷兰语
nl:荷兰语,无特定区域
Routing Overview
路由概述
It’s recommended to use the user’s language preferences in the browser to select which locale to use. Changing your preferred language will modify the incoming Accept-Language header to your application.
建议在浏览器中使用用户的语言首选项来选择要使用的语言环境。更改您的首选语言将修改传入的 Accept-Language 标头到您的应用程序。
For example, using the following libraries, you can look at an incoming Request to determine which locale to select, based on the Headers, locales you plan to support, and the default locale.
例如,使用以下库,您可以根据标头、您计划支持的区域设置和默认区域设置,查看传入请求以确定选择哪个区域设置。
middleware.js
import { match } from '@formatjs/intl-localematcher';import Negotiator from 'negotiator'; let headers = { 'Accept-Language': 'en-US,en;q=0.5' };let languages = new Negotiator(headers).languages();let locales = ['en-US', 'nl-NL', 'nl'];let defaultLocale = 'en-US'; match(languages, locales, defaultLocale); // -> 'en-US'
Routing can be internationalized by either the sub-path (/fr/products) or domain (my-site.fr/products). With this information, you can now redirect the user based on the locale inside Middleware.
路由可以通过子路径 (/fr/products) 或域 (my-site.fr/products) 国际化。有了这些信息,您现在可以根据 Middleware 中的语言环境重定向用户。
middleware.js
import { NextResponse } from 'next/server' let locales = ['en-US', 'nl-NL', 'nl'] // Get the preferred locale, similar to above or using a libraryfunction getLocale(request) { ... } export function middleware(request) { // Check if there is any supported locale in the pathname const pathname = request.nextUrl.pathname const pathnameIsMissingLocale = locales.every( (locale) => !pathname.startsWith(`/${locale}/`) && pathname !== `/${locale}` ) // Redirect if there is no locale if (pathnameIsMissingLocale) { const locale = getLocale(request) // e.g. incoming request is /products // The new URL is now /en-US/products return NextResponse.redirect( new URL(`/${locale}/${pathname}`, request.url) ) }} export const config = { matcher: [ // Skip all internal paths (_next) '/((?!_next).*)', // Optional: only run on root (/) URL // '/' ],}
Finally, ensure all special files inside app/ are nested under app/[lang]. This enables the Next.js router to dynamically handle different locales in the route, and forward the lang parameter to every layout and page. For example:
最后,确保 app/ 中的所有特殊文件都嵌套在 app/[lang] 下。这使 Next.js 路由器能够动态处理路由中的不同语言环境,并将 lang 参数转发到每个布局和页面。例如:
app/[lang]/page.js
// You now have access to the current locale// e.g. /en-US/products -> `lang` is "en-US"export default async function Page({ params: { lang } }) { return ...}
The root layout can also be nested in the new folder (e.g. app/[lang]/layout.js).
根布局也可以嵌套在新文件夹中(例如 app/[lang]/layout.js)。
Localization
本土化
Changing displayed content based on the user’s preferred locale, or localization, is not something specific to Next.js. The patterns described below would work the same with any web application.
根据用户的首选语言环境或本地化更改显示的内容并不是 Next.js 特有的。下面描述的模式适用于任何 Web 应用程序。
Let’s assume we want to support both English and Dutch content inside our application. We might maintain two different “dictionaries”, which are objects that give us a mapping from some key to a localized string. For example:
假设我们希望在我们的应用程序中同时支持英语和荷兰语内容。我们可能会维护两个不同的“字典”,它们是为我们提供从某个键到本地化字符串的映射的对象。例如:
dictionaries/en.json
{ "products": { "cart": "Add to Cart" }}
dictionaries/nl.json
{ "products": { "cart": "Toevoegen aan Winkelwagen" }}
We can then create a getDictionary function to load the translations for the requested locale:
然后我们可以创建一个 getDictionary 函数来加载所请求语言环境的翻译:
app/[lang]/dictionaries.js
import 'server-only'; const dictionaries = { en: () => import('./dictionaries/en.json').then((module) => module.default), nl: () => import('./dictionaries/nl.json').then((module) => module.default),}; export const getDictionary = async (locale) => dictionaries[locale]();
Given the currently selected language, we can fetch the dictionary inside of a layout or page.
给定当前选择的语言,我们可以在布局或页面中获取字典。
app/[lang]/page.js
import { getDictionary } from './dictionaries'; export default async function Page({ params: { lang } }) { const dict = await getDictionary(lang); // en return <button>{dict.products.cart}</button>; // Add to Cart}
Because all layouts and pages in the app/ directory default to Server Components, we do not need to worry about the size of the translation files affecting our client-side JavaScript bundle size. This code will only run on the server, and only the resulting HTML will be sent to the browser.
因为app/目录下的所有布局和页面默认都是Server Components ,我们无需担心翻译文件的大小会影响我们客户端 JavaScript 包的大小。此代码只会在服务器上运行,并且只会将生成的 HTML 发送到浏览器。
Static Generation
静态生成
To generate static routes for a given set of locales, we can use generateStaticParams with any page or layout. This can be global, for example, in the root layout:
要为一组给定的语言环境生成静态路由,我们可以对任何页面或布局使用 generateStaticParams。这可以是全局的,例如,在根布局中:
app/[lang]/layout.js
export async function generateStaticParams() { return [{ lang: 'en-US' }, { lang: 'de' }];} export default function Root({ children, params }) { return ( <html lang={params.lang}> <body>{children}</body> </html> );}
Examples
- Minimal i18n routing and translations
- 最少的 i18n 路由和翻译
- next-intl