贵得有理由 發表於 2023-8-13 13:06:00

NodeJS系列(9)- Next.js 框架 (二) | 国际化 (i18n)、中间件 (Middleware)

<br>在 “NodeJS系列(8)- Next.js 框架 (一) |&nbsp;安装配置、路由(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>



&nbsp;&nbsp;&nbsp; 操作系统:CentOS 7.9 (x64)<br>&nbsp;&nbsp;&nbsp; NodeJS: 16.20.0<br>&nbsp;&nbsp;&nbsp; NPM: 8.19.4<br>&nbsp;&nbsp;&nbsp; NVM: 0.39.2<br>&nbsp;&nbsp;&nbsp; NextJS: 13.4.12<br><br>
<h2>2. 国际化 (i18n)</h2>
<p>&nbsp;&nbsp;&nbsp; Next.js 自 v10.0.0 起就内置了对国际化(i18n)路由的支持。可以提供区域设置、默认区域设置和域特定区域设置的列表,Next.js 将自动处理路由。<br><br>&nbsp;&nbsp;&nbsp; Next.js 内置的国际化(i18n)路由支持主流的 i18n 库,如 react-intl、react-i18next、lingui、rosetta、next-intl、next-translate、next-multilanguage、typesafe-i18n、tolgee 等。<br><br>&nbsp;&nbsp;&nbsp; 国际化(i18n)部分区域语言码对照表:<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ar 阿拉伯语<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fa 波斯语<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; tr 土耳其语<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; en 英语<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fr 法语<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; de 德语<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ru 俄国语<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; es 西班牙语<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pt 葡萄牙语<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; it 意大利语<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; nl 荷兰语<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; el 希腊语<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; zh-CN 中文简体<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ja 日本语<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ko 韩国语<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; id 印尼语<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ms 马来语<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; th 泰国语<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; vi 越南语<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ...</p>
<h3>&nbsp;&nbsp;&nbsp; 1) 本地化策略</h3>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 有两种区域设置处理策略:子路径路由和域路由。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <strong>(1) 子路径路由</strong><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 就是将区域设置放在 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>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 上述配置,locales 是区域语言列表,en 是默认的区域设置。比如 src/pages/test.js,可以使用以下网址:<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /test <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /zh-CN/test <br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 默认区域设置没有前缀。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<strong> (2) 域路由</strong><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 通过使用域路由,可以配置从不同域提供服务的区域设置,在 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>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 注:子域必须包含在要匹配的域值中,例如 www.example.com 使用域 example.com 。</p>
<h3>&nbsp;&nbsp;&nbsp; 2) React-intl 库</h3>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; React-intl 是雅虎的语言国际化开源项目 FormatJS 的一部分,通过其提供的组件和 API 可以与 React 绑定。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; React-intl 提供了两种使用方法,一种是引用 React 组建,另一种是直接调取 API,官方更加推荐在 React 项目中使用前者, 只有在无法使用 React 组件的地方,才应该调用框架提供的 API。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; React-intl 提供的 React 组件有如下几种:<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;IntlProvider /&gt; 包裹在需要语言国际化的组建的最外层,为包含在其中的所有组建提供包含 id 和字符串的键值对。(如: "homepage.title": "Home Page"; )<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;FormattedDate /&gt; 用于格式化日期,能够将一个时间戳格式化成不同语言中的日期格式。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;FormattedTime&gt; 用于格式化时间,效果与 &lt;FormattedDate /&gt; 相似。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;FormattedRelative /&gt; 通过这个组件可以显示传入组件的某个时间戳和当前时间的关系,比如 "10 minutes ago" 。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;FormattedNumber /&gt; 这个组件最主要的用途是用来给一串数字标逗号,比如 10000 这个数字,在中文的语言环境中应该是1,0000,是每隔 3 位加一个逗号,而在英语的环境中是 10,000,每隔3位加一个逗号。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;FormattedPlural /&gt; 这个组件可用于格式化量词,在中文的语境中,其实不太会用得到。但是在英文的语言环境中,描述一个苹果的时候,量词是 apple,当苹果数量为两个时,就会变成 apples,这个组件的作用就在于此。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 本文在 nextjs-demo 项目基础上,使用 react-intl 库处理多语言和格式化。首先把 React-intl 库安装到 nextjs-demo 项目上,进入 nextjs-demo 目录,运行如下命令:<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $ npm install react-intl<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <strong>(1) 修改配置</strong><br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 使用子路径路由,在 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>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 注:en 表示英语,zh-CHS 表示简体中文。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 创建 src/locales 目录,并在 locales 目录下创建 en.json 和 zh-CN.json 文件。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<strong> (2) 多语言文件</strong><br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 以 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>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 修改 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>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<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 }) =&gt;<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>&lt;IntlProvider locale={locale} messages={messages}&gt;
                        &lt;Layout&gt;
                            &lt;Component {...pageProps} /&gt;
                        &lt;/Layout&gt;
                  &lt;/IntlProvider&gt;
<span style="color: rgba(0, 0, 0, 1)">                )
            } </span></pre>
</div>
<p><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <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) =&gt;<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>&lt;&gt;

                        &lt;header&gt;
                            &lt;div className={styles.languages}&gt;<span style="color: rgba(0, 0, 0, 1)">
                              {[...locales].sort().map((locale) </span>=&gt;<span style="color: rgba(0, 0, 0, 1)"> (
                                    </span>&lt;Link key={locale} href="/test" locale={locale}&gt;<span style="color: rgba(0, 0, 0, 1)">
                                        {locale </span>+ ' | '<span style="color: rgba(0, 0, 0, 1)">}
                                    </span>&lt;/Link&gt;
<span style="color: rgba(0, 0, 0, 1)">                              ))}
                            </span>&lt;/div&gt;
                        &lt;/header&gt;

                        &lt;main className={styles.main}&gt;

                            &lt;h3&gt;Title: {title}&lt;/h3&gt;

                            &lt;p className={styles.description}&gt;<span style="color: rgba(0, 0, 0, 1)">
                              Description: {description}
                            </span>&lt;/p&gt;
                        
                        &lt;/main&gt;
                  &lt;/&gt;
<span style="color: rgba(0, 0, 0, 1)">                )
            }</span></pre>
</div>
<p><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 运行 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>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 鼠标点击 "zh-CN" 链接,跳转到 http://localhost:3000/zh-CN/test,显示中文内容。</p>
<p>&nbsp;</p>
<h2>3. 中间件 (Middleware)</h2>
<p>&nbsp;&nbsp;&nbsp; 中间件 (Middleware) 就是在请求完成之前运行的代码,中间件在缓存内容和路由匹配之前运行。中间件根据传入的请求,可以通过重写、重定向、修改请求或响应标头或直接响应来修改响应。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;<br>&nbsp;&nbsp;&nbsp; 在 src 目录下(即和 pages 或 app 目录同级)添加 middleware.js (或 middleware.ts) 文件,该文件被命名为中间件。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;<br>&nbsp;&nbsp;&nbsp; Next.js 项目中的每条路由的运行都会调用到中间件。以下是执行顺序:<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (1) next.config.js 中的标头<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (2) 从 next.config.js 重定向<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (3) 中间件(重写、重定向等)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (4) beforeFiles(重写)来自 next.config.js<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (5) 文件系统路由(public/、_next/static/、pages/、app/ 等)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (6) afterFiles(重写)来自 next.config.js<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (7) 动态路由(/bog/)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (8) 从 next.config.js 回退(重写) &nbsp;<br><br>&nbsp;&nbsp;&nbsp; 有两种方法可以定义中间件将匹配哪些路径:自定义匹配器、条件语句</p>
<h3>&nbsp;&nbsp;&nbsp; 1) 自定义匹配器</h3>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 示例,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) =&gt; {
                </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>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 注:把访问 /about/* 的请求重定向到 /test <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 可以使用数组语法匹配单个路径或多个路径:<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; export const config = {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; matcher: ['/about/:path*', '/dashboard/:path*'],<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 匹配器配置允许使用完整的 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>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 注:matcher 值必须是常量,这样才能在构建时对其进行静态分析,将忽略变量等动态值。匹配器的规则:<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (1) 以开头 /<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (2) 可以包括命名参数:/about/:path 匹配 /about/a 和 /about/b,但不匹配 /about/a/c<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (3) 命名参数上可以有修饰符(以 :开头): /about/:path* 与 /about/a/b/c 匹配,因为 * 为零或更多,?为零或 1个或多个<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (4) 可以使用括号中的正则表达式:/about/(.*)与 /about/:path* 相同</p>
<h3>&nbsp;&nbsp;&nbsp; 2) 条件语句</h3>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 示例,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) =&gt; {
                </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>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 使用条件语句时,无需配置匹配器。以上自定义匹配器和条件语句,都使用 NextResponse 的 API 返回响应。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NextResponse 的 API 能完成如下工作:<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (1) 将传入请求重定向到其他 URL<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (2) 通过显示给定的 URL 重写响应<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (3) 设置 API 路由、getServerSideProps 和重写目标的请求头<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (4) 设置响应 cookie<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (5) 设置响应标头</p>
<h3>&nbsp;&nbsp;&nbsp; 3) 使用 Cookie</h3>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Cookie 是常规标头。在请求时,它们存储在 Cookie 标头中。在响应中,它们位于 Set Cookie 标头中。Next.js 提供了一种方便的方式,可以通过 NextRequest 和 NextResponse 上的 cookie 扩展来访问和操作这些 cookie。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 对于传入请求,cookie 具有以下方法:get、getAll、set 和 delete cookie。您可以使用 has 检查是否存在 cookie,也可以使用 clear 删除所有 cookie。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 对于传出响应,cookie 具有以下方法 get、getAll、set 和 delete。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 示例,代码如下:</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) =&gt; {

                </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)"> =&gt; { 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)"> =&gt; [{ 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)"> =&gt; 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)"> =&gt; 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)"> =&gt; { 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>&nbsp;&nbsp;&nbsp; 4) 设置 Headers </h3>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 可以使用 NextResponse API 设置请求和响应头(从 Next.js v13.0.0 开始提供设置请求头)。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 示例,代码如下:</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) =&gt; {
                </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>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 注:避免设置大 Headers,因为根据后端 Web 服务器配置,这可能会导致 431 请求标题字段过大错误。</p>
<h3><br>&nbsp;&nbsp;&nbsp; 5) 产生响应</h3>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Next.js 从 v13.1.0 开始,可以通过返回 Response 或 NextResponse 实例直接从中间件进行响应。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 示例,代码如下:</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) =&gt; {
                </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>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 注:访问 http://localhost:3000/api/*,中间件拦截请求,返回 401</p>
<h3><br>&nbsp;&nbsp;&nbsp; 6) 高级中间件标志</h3>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 在 Next.js 的 v13.1 中,为中间件引入了两个额外的标志 skipMiddlewareUrlNormalize 和 skipTrailingLashRedirect,以处理高级用例。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; skipTrailingSlashRedirect 允许禁用 Next.js 默认重定向以添加或删除尾部斜杠,从而允许在中间件内部进行自定义处理,这可以允许为某些路径维护尾部斜杠,但不允许为其他路径维护尾部斜线,从而允许更容易的增量迁移。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 在 next.config.js 里的配置如下:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; module.exports = {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; skipTrailingSlashRedirect: true,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 示例,代码如下:</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) =&gt; {
                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) =&gt;<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('/') &amp;&amp;
                  !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>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; skipMiddlewareUrlNormalize 允许禁用规范 Next.js 的 URL,以使处理直接访问和客户端转换相同。在一些高级情况下,您需要使用解锁的原始 URL 进行完全控制。<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 在 next.config.js 里的配置如下:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; module.exports = {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; skipMiddlewareUrlNormalize: true,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 示例,代码如下:</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) =&gt; {
                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>&nbsp;</p><br><br>
来源:https://www.cnblogs.com/tkuang/p/17626435.html
頁: [1]
查看完整版本: NodeJS系列(9)- Next.js 框架 (二) | 国际化 (i18n)、中间件 (Middleware)