Example
例子
middleware.ts
python
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:
headers
fromnext.config.js
- 来自 next.config.js 的标头
redirects
fromnext.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
arduino
export const config = { matcher: '/about/:path*',};
You can match a single path or multiple paths with an array syntax:
您可以使用数组语法匹配单个路径或多个路径:
middleware.js
arduino
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
arduino
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 matcher values 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/:path
matches/about/a
and/about/b
but 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/c
because*
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
/public
as/public/index
. Therefore, a matcher of/public/:path
will 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:
redirect
the incoming request to a different URL- 将传入请求重定向到不同的 URL
rewrite
the response by displaying a given URL- 通过显示给定的 URL 重写响应
- Set request headers for API Routes,
getServerSideProps
, andrewrite
destinations - 为 API 路由、getServerSideProps 和重写目标设置请求标头
- Set response cookies
- Set response headers
- 设置响应头
To produce a response from Middleware, you can:
要从中间件产生响应,您可以:
rewrite
to a route (Page or Edge API Route) that produces a response- 重写为产生响应的路由(页面或边缘 API 路由)
- return a
NextResponse
directly. 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,
cookies
comes with the following methods:get
,getAll
,set
, anddelete
cookies. You can check for the existence of a cookie withhas
or remove all cookies withclear
. - 对于传入请求,cookie 带有以下方法:get、getAll、set 和 delete cookie。您可以使用 has 检查 cookie 是否存在或使用 clear 删除所有 cookie。
- For outgoing responses,
cookies
have the following methodsget
,getAll
,set
, anddelete
. - 对于传出响应,cookie 具有以下方法 get、getAll、set 和 delete。
middleware.ts
sql
复制代码
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
python
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
sql
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
ini
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
ini
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
javascript
复制代码
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
sql
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
vbnet
// 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.j
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