NodeJS系列(9)- Next.js 框架 (二) | 国际化 (i18n)、中间件 (Middleware)
<br>在 “NodeJS系列(8)- Next.js 框架 (一) | 安装配置、路由(Routing)、页面布局(Layout)” 里,我们简单介绍了 Next.js 的安装配置,创建了 nextjs-demo 项目,讲解和演示了 Next.js 项目的运行、路由(Routing)、页面布局(Layout)等内容。<br><br>本文继续在 nextjs-demo 项目(Pages Router)基础上,讲解和演示国际化 (i18n)、中间件 (Middleware) 等内容。<br><br>NextJS: https://nextjs.org/<br>NextJS GitHub: https://github.com/vercel/next.js<br><br><h2>1. 系统环境</h2>
操作系统:CentOS 7.9 (x64)<br> NodeJS: 16.20.0<br> NPM: 8.19.4<br> NVM: 0.39.2<br> NextJS: 13.4.12<br><br>
<h2>2. 国际化 (i18n)</h2>
<p> Next.js 自 v10.0.0 起就内置了对国际化(i18n)路由的支持。可以提供区域设置、默认区域设置和域特定区域设置的列表,Next.js 将自动处理路由。<br><br> Next.js 内置的国际化(i18n)路由支持主流的 i18n 库,如 react-intl、react-i18next、lingui、rosetta、next-intl、next-translate、next-multilanguage、typesafe-i18n、tolgee 等。<br><br> 国际化(i18n)部分区域语言码对照表:<br><br> ar 阿拉伯语<br> fa 波斯语<br> tr 土耳其语<br> en 英语<br> fr 法语<br> de 德语<br> ru 俄国语<br> es 西班牙语<br> pt 葡萄牙语<br> it 意大利语<br> nl 荷兰语<br> el 希腊语<br> zh-CN 中文简体<br> ja 日本语<br> ko 韩国语<br> id 印尼语<br> ms 马来语<br> th 泰国语<br> vi 越南语<br> ...</p>
<h3> 1) 本地化策略</h3>
<p> 有两种区域设置处理策略:子路径路由和域路由。<br><br> <strong>(1) 子路径路由</strong><br> <br> 就是将区域设置放在 url 路径中,在 next.config.js 里的配置如下:</p>
<div class="cnblogs_code">
<pre> module.exports =<span style="color: rgba(0, 0, 0, 1)"> {
i18n: {
locales: [</span>'en', 'zh-CN'<span style="color: rgba(0, 0, 0, 1)">],
defaultLocale: </span>'en'<span style="color: rgba(0, 0, 0, 1)">,
},
}</span></pre>
</div>
<p><br> 上述配置,locales 是区域语言列表,en 是默认的区域设置。比如 src/pages/test.js,可以使用以下网址:<br><br> /test <br> /zh-CN/test <br><br> 默认区域设置没有前缀。<br><br> <strong> (2) 域路由</strong><br> <br> 通过使用域路由,可以配置从不同域提供服务的区域设置,在 next.config.js 里的配置如下:</p>
<div class="cnblogs_code">
<pre> module.exports =<span style="color: rgba(0, 0, 0, 1)"> {
i18n: {
locales: [</span>'en', 'zh-CN'<span style="color: rgba(0, 0, 0, 1)">],
defaultLocale: </span>'en'<span style="color: rgba(0, 0, 0, 1)">,
},
domains: [
{
domain: </span>'example.com'<span style="color: rgba(0, 0, 0, 1)">,
defaultLocale: </span>'en'<span style="color: rgba(0, 0, 0, 1)">,
},
{
domain: </span>'example.cn'<span style="color: rgba(0, 0, 0, 1)">,
defaultLocale: </span>'zh-CN'<span style="color: rgba(0, 0, 0, 1)">,
},
],
}</span></pre>
</div>
<p> 注:子域必须包含在要匹配的域值中,例如 www.example.com 使用域 example.com 。</p>
<h3> 2) React-intl 库</h3>
<p> React-intl 是雅虎的语言国际化开源项目 FormatJS 的一部分,通过其提供的组件和 API 可以与 React 绑定。<br><br> React-intl 提供了两种使用方法,一种是引用 React 组建,另一种是直接调取 API,官方更加推荐在 React 项目中使用前者, 只有在无法使用 React 组件的地方,才应该调用框架提供的 API。<br><br> React-intl 提供的 React 组件有如下几种:<br><br> <IntlProvider /> 包裹在需要语言国际化的组建的最外层,为包含在其中的所有组建提供包含 id 和字符串的键值对。(如: "homepage.title": "Home Page"; )<br> <FormattedDate /> 用于格式化日期,能够将一个时间戳格式化成不同语言中的日期格式。<br> <FormattedTime> 用于格式化时间,效果与 <FormattedDate /> 相似。<br> <FormattedRelative /> 通过这个组件可以显示传入组件的某个时间戳和当前时间的关系,比如 "10 minutes ago" 。<br> <FormattedNumber /> 这个组件最主要的用途是用来给一串数字标逗号,比如 10000 这个数字,在中文的语言环境中应该是1,0000,是每隔 3 位加一个逗号,而在英语的环境中是 10,000,每隔3位加一个逗号。<br> <FormattedPlural /> 这个组件可用于格式化量词,在中文的语境中,其实不太会用得到。但是在英文的语言环境中,描述一个苹果的时候,量词是 apple,当苹果数量为两个时,就会变成 apples,这个组件的作用就在于此。<br><br> 本文在 nextjs-demo 项目基础上,使用 react-intl 库处理多语言和格式化。首先把 React-intl 库安装到 nextjs-demo 项目上,进入 nextjs-demo 目录,运行如下命令:<br><br> $ npm install react-intl<br><br> <strong>(1) 修改配置</strong><br><br> 使用子路径路由,在 next.config.js 文件里,配置如下:</p>
<div class="cnblogs_code">
<pre> module.exports =<span style="color: rgba(0, 0, 0, 1)"> {
i18n: {
locales: [</span>'en', 'zh-CN'<span style="color: rgba(0, 0, 0, 1)">],
defaultLocale: </span>'en'<span style="color: rgba(0, 0, 0, 1)">,
},
}</span></pre>
</div>
<p> 注:en 表示英语,zh-CHS 表示简体中文。<br><br> 创建 src/locales 目录,并在 locales 目录下创建 en.json 和 zh-CN.json 文件。<br><br> <strong> (2) 多语言文件</strong><br><br> 以 nextjs-demo 项目的 test 页面为例,修改 en.json 文件,内容如下:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)"> {
</span>"page.test.title": "Test Page"<span style="color: rgba(0, 0, 0, 1)">,
</span>"page.test.description": "React intl locales"<span style="color: rgba(0, 0, 0, 1)">
}</span></pre>
</div>
<p><br> 修改 zh-CN.json 文件,内容如下:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)"> {
</span>"page.test.title": "测试页"<span style="color: rgba(0, 0, 0, 1)">,
</span>"page.test.description": "React intl 多语言组件"<span style="color: rgba(0, 0, 0, 1)">
}</span></pre>
</div>
<p><br> <strong> (3) 修改 src/pages/_app.js 文件</strong> </p>
<div class="cnblogs_code">
<pre> import { useRouter } from "next/router"<span style="color: rgba(0, 0, 0, 1)">;
import { IntlProvider } from </span>"react-intl"<span style="color: rgba(0, 0, 0, 1)">;
import </span>'antd/dist/antd.min.css'<span style="color: rgba(0, 0, 0, 1)">;
import Layout from </span>'../components/layout'<span style="color: rgba(0, 0, 0, 1)">
import en from </span>"../locales/en.json"<span style="color: rgba(0, 0, 0, 1)">;
import zhCN from </span>"../locales/zh-CN.json"<span style="color: rgba(0, 0, 0, 1)">;
const messages </span>=<span style="color: rgba(0, 0, 0, 1)"> {
</span>"en"<span style="color: rgba(0, 0, 0, 1)">: en,
</span>"zh-CN"<span style="color: rgba(0, 0, 0, 1)">: zhCN,
}
export </span><span style="color: rgba(0, 0, 255, 1)">default</span> ({ Component, pageProps }) =><span style="color: rgba(0, 0, 0, 1)"> {
const { locale } </span>=<span style="color: rgba(0, 0, 0, 1)"> useRouter();
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> (
</span><IntlProvider locale={locale} messages={messages}>
<Layout>
<Component {...pageProps} />
</Layout>
</IntlProvider>
<span style="color: rgba(0, 0, 0, 1)"> )
} </span></pre>
</div>
<p><br> <strong>(4) 修改 src/pages/test.js 文件</strong> </p>
<div class="cnblogs_code">
<pre> import { FormattedMessage, useIntl } from "react-intl"<span style="color: rgba(0, 0, 0, 1)">;
import { useRouter } from </span>'next/router'<span style="color: rgba(0, 0, 0, 1)">;
import Link from </span>"next/link"<span style="color: rgba(0, 0, 0, 1)">;
import styles from </span>'@/styles/Home.module.css'<span style="color: rgba(0, 0, 0, 1)">
export </span><span style="color: rgba(0, 0, 255, 1)">default</span> (props) =><span style="color: rgba(0, 0, 0, 1)"> {
const { locales } </span>=<span style="color: rgba(0, 0, 0, 1)"> useRouter();
const intl </span>=<span style="color: rgba(0, 0, 0, 1)"> useIntl();
const title </span>= intl.formatMessage({ id: "page.test.title"<span style="color: rgba(0, 0, 0, 1)"> });
const description </span>= intl.formatMessage({ id: "page.test.description"<span style="color: rgba(0, 0, 0, 1)"> });
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> (
</span><>
<header>
<div className={styles.languages}><span style="color: rgba(0, 0, 0, 1)">
{[...locales].sort().map((locale) </span>=><span style="color: rgba(0, 0, 0, 1)"> (
</span><Link key={locale} href="/test" locale={locale}><span style="color: rgba(0, 0, 0, 1)">
{locale </span>+ ' | '<span style="color: rgba(0, 0, 0, 1)">}
</span></Link>
<span style="color: rgba(0, 0, 0, 1)"> ))}
</span></div>
</header>
<main className={styles.main}>
<h3>Title: {title}</h3>
<p className={styles.description}><span style="color: rgba(0, 0, 0, 1)">
Description: {description}
</span></p>
</main>
</>
<span style="color: rgba(0, 0, 0, 1)"> )
}</span></pre>
</div>
<p><br> 运行 nextjs-demo 项目,使用浏览器访问 http://localhost:3000/test,显示内容如下:</p>
<div class="cnblogs_code">
<pre> HomeLogin # 菜单
en | zh-CN |
Title: Test Page
Description: React intl locales
Footer</pre>
</div>
<p><br> 鼠标点击 "zh-CN" 链接,跳转到 http://localhost:3000/zh-CN/test,显示中文内容。</p>
<p> </p>
<h2>3. 中间件 (Middleware)</h2>
<p> 中间件 (Middleware) 就是在请求完成之前运行的代码,中间件在缓存内容和路由匹配之前运行。中间件根据传入的请求,可以通过重写、重定向、修改请求或响应标头或直接响应来修改响应。<br> <br> 在 src 目录下(即和 pages 或 app 目录同级)添加 middleware.js (或 middleware.ts) 文件,该文件被命名为中间件。<br> <br> Next.js 项目中的每条路由的运行都会调用到中间件。以下是执行顺序:<br><br> (1) next.config.js 中的标头<br> (2) 从 next.config.js 重定向<br> (3) 中间件(重写、重定向等)<br> (4) beforeFiles(重写)来自 next.config.js<br> (5) 文件系统路由(public/、_next/static/、pages/、app/ 等)<br> (6) afterFiles(重写)来自 next.config.js<br> (7) 动态路由(/bog/)<br> (8) 从 next.config.js 回退(重写) <br><br> 有两种方法可以定义中间件将匹配哪些路径:自定义匹配器、条件语句</p>
<h3> 1) 自定义匹配器</h3>
<p> 示例,src/middleware.js 代码如下:</p>
<div class="cnblogs_code">
<pre> import { NextResponse } from 'next/server'
<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> This function can be marked `async` if using `await` inside</span>
export const <span style="color: rgba(0, 0, 0, 1)">middleware = (request) => {
</span><span style="color: rgba(0, 0, 255, 1)">return</span> NextResponse.redirect(<span style="color: rgba(0, 0, 255, 1)">new</span> URL('/test'<span style="color: rgba(0, 0, 0, 1)">, request.url))
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> See "Matching Paths" below to learn more</span>
export const config =<span style="color: rgba(0, 0, 0, 1)"> {
matcher: </span>'/about/:path*'<span style="color: rgba(0, 0, 0, 1)">,
}</span></pre>
</div>
<p> 注:把访问 /about/* 的请求重定向到 /test <br> <br> 可以使用数组语法匹配单个路径或多个路径:<br><br> export const config = {<br> matcher: ['/about/:path*', '/dashboard/:path*'],<br> }<br><br> 匹配器配置允许使用完整的 regex,因此支持像定向排除 (negative look ahead) 或字符匹配这样的匹配。以下是一个定向排除的例子,以匹配除特定路径之外的所有路径:</p>
<div class="cnblogs_code">
<pre> export const config =<span style="color: rgba(0, 0, 0, 1)"> {
matcher: [
</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">
* 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)
</span><span style="color: rgba(0, 128, 0, 1)">*/</span>
'/((?!api|_next/static|_next/image|favicon.ico).*)'<span style="color: rgba(0, 0, 0, 1)">,
],
}</span></pre>
</div>
<p> 注:matcher 值必须是常量,这样才能在构建时对其进行静态分析,将忽略变量等动态值。匹配器的规则:<br><br> (1) 以开头 /<br> (2) 可以包括命名参数:/about/:path 匹配 /about/a 和 /about/b,但不匹配 /about/a/c<br> (3) 命名参数上可以有修饰符(以 :开头): /about/:path* 与 /about/a/b/c 匹配,因为 * 为零或更多,?为零或 1个或多个<br> (4) 可以使用括号中的正则表达式:/about/(.*)与 /about/:path* 相同</p>
<h3> 2) 条件语句</h3>
<p> 示例,src/middleware.js 代码如下:</p>
<div class="cnblogs_code">
<pre> import { NextResponse } from 'next/server'<span style="color: rgba(0, 0, 0, 1)">
export </span>const <span style="color: rgba(0, 0, 0, 1)">middleware = (request) => {
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (request.nextUrl.pathname.startsWith('/about'<span style="color: rgba(0, 0, 0, 1)">)) {
</span><span style="color: rgba(0, 0, 255, 1)">return</span> NextResponse.rewrite(<span style="color: rgba(0, 0, 255, 1)">new</span> URL('/about-2'<span style="color: rgba(0, 0, 0, 1)">, request.url))
}
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (request.nextUrl.pathname.startsWith('/dashboard'<span style="color: rgba(0, 0, 0, 1)">)) {
</span><span style="color: rgba(0, 0, 255, 1)">return</span> NextResponse.rewrite(<span style="color: rgba(0, 0, 255, 1)">new</span> URL('/dashboard/user'<span style="color: rgba(0, 0, 0, 1)">, request.url))
}
}</span></pre>
</div>
<p> </p>
<p> 使用条件语句时,无需配置匹配器。以上自定义匹配器和条件语句,都使用 NextResponse 的 API 返回响应。<br> <br> NextResponse 的 API 能完成如下工作:<br><br> (1) 将传入请求重定向到其他 URL<br> (2) 通过显示给定的 URL 重写响应<br> (3) 设置 API 路由、getServerSideProps 和重写目标的请求头<br> (4) 设置响应 cookie<br> (5) 设置响应标头</p>
<h3> 3) 使用 Cookie</h3>
<p> Cookie 是常规标头。在请求时,它们存储在 Cookie 标头中。在响应中,它们位于 Set Cookie 标头中。Next.js 提供了一种方便的方式,可以通过 NextRequest 和 NextResponse 上的 cookie 扩展来访问和操作这些 cookie。<br><br> 对于传入请求,cookie 具有以下方法:get、getAll、set 和 delete cookie。您可以使用 has 检查是否存在 cookie,也可以使用 clear 删除所有 cookie。<br><br> 对于传出响应,cookie 具有以下方法 get、getAll、set 和 delete。<br><br> 示例,代码如下:</p>
<div class="cnblogs_code">
<pre> import { NextResponse } from 'next/server'<span style="color: rgba(0, 0, 0, 1)">
export </span>const <span style="color: rgba(0, 0, 0, 1)">middleware = (request) => {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Assume a "Cookie:nextjs=fast" header to be present on the incoming request</span>
<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Getting cookies from the request using the `RequestCookies` API</span>
let cookie = request.cookies.get('nextjs'<span style="color: rgba(0, 0, 0, 1)">)
console.log(cookie) </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> => { name: 'nextjs', value: 'fast', Path: '/' }</span>
const allCookies =<span style="color: rgba(0, 0, 0, 1)"> request.cookies.getAll()
console.log(allCookies) </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> => [{ name: 'nextjs', value: 'fast' }]</span>
<span style="color: rgba(0, 0, 0, 1)">
request.cookies.has(</span>'nextjs') <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> => true</span>
request.cookies.<span style="color: rgba(0, 0, 255, 1)">delete</span>('nextjs'<span style="color: rgba(0, 0, 0, 1)">)
request.cookies.has(</span>'nextjs') <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> => false</span>
<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Setting cookies on the response using the `ResponseCookies` API</span>
const response =<span style="color: rgba(0, 0, 0, 1)"> NextResponse.next()
response.cookies.set(</span>'vercel', 'fast'<span style="color: rgba(0, 0, 0, 1)">)
response.cookies.set({
name: </span>'vercel'<span style="color: rgba(0, 0, 0, 1)">,
value: </span>'fast'<span style="color: rgba(0, 0, 0, 1)">,
path: </span>'/'<span style="color: rgba(0, 0, 0, 1)">,
})
cookie </span>= response.cookies.get('vercel'<span style="color: rgba(0, 0, 0, 1)">)
console.log(cookie) </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> => { name: 'vercel', value: 'fast', Path: '/' }</span>
<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> The outgoing response will have a `Set-Cookie:vercel=fast;path=/test` header.</span>
<span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> response
}</span></pre>
</div>
<h3><br> 4) 设置 Headers </h3>
<p> 可以使用 NextResponse API 设置请求和响应头(从 Next.js v13.0.0 开始提供设置请求头)。<br><br> 示例,代码如下:</p>
<div class="cnblogs_code">
<pre> import { NextResponse } from 'next/server'<span style="color: rgba(0, 0, 0, 1)">
export </span>const <span style="color: rgba(0, 0, 0, 1)">middleware = (request) => {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Clone the request headers and set a new header `x-hello-from-middleware1`</span>
const requestHeaders = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Headers(request.headers)
requestHeaders.set(</span>'x-hello-from-middleware1', 'hello'<span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> You can also set request headers in NextResponse.rewrite</span>
const response =<span style="color: rgba(0, 0, 0, 1)"> NextResponse.next({
request: {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> New request headers</span>
<span style="color: rgba(0, 0, 0, 1)"> headers: requestHeaders,
},
})
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Set a new response header `x-hello-from-middleware2`</span>
response.headers.set('x-hello-from-middleware2', 'hello'<span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> response
}</span></pre>
</div>
<p> 注:避免设置大 Headers,因为根据后端 Web 服务器配置,这可能会导致 431 请求标题字段过大错误。</p>
<h3><br> 5) 产生响应</h3>
<p> Next.js 从 v13.1.0 开始,可以通过返回 Response 或 NextResponse 实例直接从中间件进行响应。<br><br> 示例,代码如下:</p>
<div class="cnblogs_code">
<pre> import { NextResponse } from 'next/server'
<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Limit the middleware to paths starting with `/api/`</span>
export const config =<span style="color: rgba(0, 0, 0, 1)"> {
matcher: </span>'/api/:function*'<span style="color: rgba(0, 0, 0, 1)">,
}
export const</span><span style="color: rgba(0, 0, 0, 1)"> middleware = (request) => {
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (<span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">) {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Respond with JSON indicating an error message</span>
<span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> NextResponse(
JSON.stringify({ success: </span><span style="color: rgba(0, 0, 255, 1)">false</span>, message: 'authentication failed'<span style="color: rgba(0, 0, 0, 1)"> }),
{ status: </span>401, headers: { 'content-type': 'application/json'<span style="color: rgba(0, 0, 0, 1)"> } }
)
}
} </span></pre>
</div>
<p> 注:访问 http://localhost:3000/api/*,中间件拦截请求,返回 401</p>
<h3><br> 6) 高级中间件标志</h3>
<p> 在 Next.js 的 v13.1 中,为中间件引入了两个额外的标志 skipMiddlewareUrlNormalize 和 skipTrailingLashRedirect,以处理高级用例。<br><br> skipTrailingSlashRedirect 允许禁用 Next.js 默认重定向以添加或删除尾部斜杠,从而允许在中间件内部进行自定义处理,这可以允许为某些路径维护尾部斜杠,但不允许为其他路径维护尾部斜线,从而允许更容易的增量迁移。<br><br> 在 next.config.js 里的配置如下:<br> <br> module.exports = {<br> skipTrailingSlashRedirect: true,<br> }<br><br> 示例,代码如下:</p>
<div class="cnblogs_code">
<pre> const legacyPrefixes = ['/docs', '/blog'<span style="color: rgba(0, 0, 0, 1)">]
export </span><span style="color: rgba(0, 0, 255, 1)">default</span> <span style="color: rgba(0, 0, 0, 1)">middleware = async (req) => {
const { pathname } </span>=<span style="color: rgba(0, 0, 0, 1)"> req.nextUrl
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (legacyPrefixes.some((prefix) =><span style="color: rgba(0, 0, 0, 1)"> pathname.startsWith(prefix))) {
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> NextResponse.next()
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> apply trailing slash handling</span>
<span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (
</span>!pathname.endsWith('/') &&
!pathname.match(/((?!\.well-known(?:\/.*)?)(?:[^/]+\/)*[^/]+\.\w+)/)
<span style="color: rgba(0, 0, 0, 1)"> ) {
req.nextUrl.pathname </span>+= '/'
<span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> NextResponse.redirect(req.nextUrl)
}
}</span></pre>
</div>
<p><br> skipMiddlewareUrlNormalize 允许禁用规范 Next.js 的 URL,以使处理直接访问和客户端转换相同。在一些高级情况下,您需要使用解锁的原始 URL 进行完全控制。<br><br> 在 next.config.js 里的配置如下:<br> <br> module.exports = {<br> skipMiddlewareUrlNormalize: true,<br> }<br><br> 示例,代码如下:</p>
<div class="cnblogs_code">
<pre> export <span style="color: rgba(0, 0, 255, 1)">default</span> <span style="color: rgba(0, 0, 0, 1)">middleware = async (req) => {
const { pathname } </span>=<span style="color: rgba(0, 0, 0, 1)"> req.nextUrl
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> GET /_next/data/build-id/hello.json</span>
<span style="color: rgba(0, 0, 0, 1)">
console.log(pathname)
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> with the flag this now /_next/data/build-id/hello.json</span>
<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> without the flag this would be normalized to /hello</span>
}</pre>
</div>
<p> </p><br><br>
来源:https://www.cnblogs.com/tkuang/p/17626435.html
頁:
[1]