原创 洞窝技术
使用 Next.js 中间件实现高性能个性化
在当今的数字时代,用户期望获得量身定制的在线体验。个性化已经从一个奢侈品变成了必需品,尤其是对于希望在竞争激烈的市场中脱颖而出的企业来说。然而,实现高性能的个性化往往是一个挑战,需要在用户体验和系统性能之间取得平衡。
这就是 Next.js 中间件发挥作用的地方。作为 React 框架中的一个强大功能,Next.js 的中间件为开发者提供了一种优雅而高效的方式来实现个性化,同时保持应用程序的高性能。
本文将探讨如何利用 Next.js 中间件来创建动态、响应迅速且个性化的 web 应用。我们将深入研究中间件的工作原理,讨论其在实现个性化中的优势,并提供实际的实现步骤和优化技巧。无论你是经验丰富的 Next.js 开发者,还是刚刚开始探索这个强大框架的新手,本文都将为你提供有价值的见解,帮助你提升用户体验并保持网站的高性能。
什么是Next.js中间件
先来看看官网的解释:中间件允许你在请求完成之前运行代码。然后,根据传入的请求,你可以通过重写、重定向、修改请求或响应标头或直接响应来修改响应。
Next.js 中间件是在 Next.js 12版本中引入的概念,并在后续版本中得到增强。它允许开发者在请求被 Next.js 路由处理之前拦截这些请求,并执行自定义的服务器端和/或客户端代码。
这些中间件可以运行在两个不同的阶段:
Server-side: 当请求初始到达服务器时;Client-side: 在客户端导航到不同页面时(例如,当使用 next/link 或 next/router)。
中间件的作用
Next.js 中间件的作用非常多样化,根据用户的喜好定制内容可以极大地改善用户在您网站上的体验,最终可以提高您的转化率。通过解析 HTTP 请求标头,您可以向用户提供不同版本的网站。例如:
国际化: 基于请求头或 URL 参数调整内容以适应不同的区域和语言。重写和重定向: 基于请求动态重写 URL 路径或进行重定向。A/B 测试: 根据某些逻辑展示不同版本的页面。地理位置:根据用户的区域或地理位置(即由 IP 地址确定),您可以翻译文本、显示本地相关内容以及显示以当地货币定制的价格。用户信息:一旦用户登录,您就可以设置包含有关用户信息的 cookie,例如他们的性别或他们在您的网站上购物的频率。技术栈:从浏览器的用户代理,您可以向iPhone或Android用户呈现不同的内容,例如下载链接。回访用户:通过在首次访问时设置 cookie,后续访问可以向回访用户显示特殊内容,例如诱人的促销信息。身份验证和授权:检查用户的身份验证状态和权限。如果用户没有适当的权限,可以在中间件中阻止请求继续进行,并返回适当的错误响应。
当然您还可以发掘他更多的用法。
如何使用中间件
在根目录下创建 middleware.js或middleware.ts。(与 pages 或 app 处于同一级别)这个文件是特殊的,Next.js 会自动将其识别为中间件。
1. 请求到达服务器
当一个请求到达 Next.js 服务器时,服务器会首先检查是否有任何中间件需要执行。
2. 中间件执行
中间件是一个函数,它接受 request(请求对象)和 response(响应对象)作为参数,并且可以对它们进行操作。中间件函数的签名通常是这样的:
export default function middleware(request, response) {// 你的中间件逻辑
}
3. 中间件的配置
中间件可以通过 next.config.js 文件进行配置,指定在哪些路径下生效。例如:
// next.config.js
module.exports = {async middleware(request, response) {// 在这里导入并使用你的中间件const { middleware } = require('./middleware');return middleware(request);},async rewrites() {return [{source: '/:path*',destination: '/:path*',},]},
}
在这个配置中,中间件将应用于所有路径(/:path*),你可以根据需要调整路径匹配模式来限制中间件的应用范围。
例如,如果你只想让中间件在 /protected 路径下生效,可以这样配置:
// next.config.js
module.exports = {async rewrites() {return [{source: '/protected/:path*',destination: '/protected/:path*',},];},async middleware(request, response) {// 在这里导入并使用你的中间件const { middleware } = require('./middleware');return middleware(request);},
};
4. 请求处理和响应修改
在中间件中,你可以对请求进行各种处理,例如:身份验证,重定向等等
例如,一个简单的重定向中间件可能是这样的:
export default function middleware(request, response) {if (!request.cookies.token) {// 如果用户没有 token,则重定向到登录页面return response.redirect('/login');}// 继续处理请求
}
5. 继续处理或结束请求
中间件可以决定是否继续处理请求或直接结束请求。
如果中间件调用了 next() 函数,表示继续传递给下一个中间件或最终的页面处理函数。
如果中间件直接返回了响应,则请求处理到此结束,不再继续传递。
6. 异步中间件
中间件可以是异步函数,允许你进行异步操作,例如数据库查询、外部 API 调用等:
export default async function middleware(request, respons) {const user = await fetchUserFromDatabase(request.cookies.token);if (!user) {return respons.redirect('/login');}// 继续处理请求
}
7. 错误处理
中间件中出现的错误可以通过捕获异常来处理,确保不会因为错误导致整个请求崩溃:
export default async function middleware(request, respons) {try {const user = await fetchUserFromDatabase(request.cookies.token);if (!user) {return respons.redirect('/login');}// 继续处理请求} catch (error) {console.error('Middleware error:', error);return respons.status(500).send('Internal Server Error');}
}
通过中间件,Next.js 提供了一种灵活且强大的方式来处理请求,使你可以在请求到达页面或 API 路由之前进行各种操作。
import { NextResponse } from 'next/server';export function middleware(request) {// 例如,检查用户是否已认证const isAuthenticated = checkUserAuthentication(request);if (!isAuthenticated) {// 如果用户未认证,重定向到登录页面return NextResponse.redirect('/login');}// 如果用户已认证,继续处理请求return NextResponse.next();
}function checkUserAuthentication(request) {// 自定义的身份验证逻辑// 例如,检查请求头部中的认证信息const authHeader = request.headers.get('Authorization');return authHeader === 'Bearer valid-token';
}
在这个示例中,中间件检查用户是否已认证,如果未认证,则重定向到登录页面。如果已认证,则继续处理请求。
使用中间件可以让您在 Next.js 应用中更灵活地处理请求和响应,从而实现更复杂和定制化的功能。
中间件使我们能够完全在Edge Runtime(边缘运行时)实现个性化。中间件根据 HTTP 请求标头中的数据,将不同的用户重定向到应用程序的不同版本,例如我们根据客户端的当前IP将网站重定向到不同国家的网站。上图显示了 Next.js 中间件如何工作。
在 Next.js 上实现个性化
下面结合我们的真实业务场景,来看看如何根据客户端的当前的IP地址将网站重定向到不同国家的网站。
我们的网站根据国家不同配置了不一样的营销活动和商品,我们要求通过url做区分,例如新加坡 www.xxx.com/sg, 泰国 www.xxx.com/th, 看到这里,使用过nextjs的小伙伴应该已经有一个疑问了,在地址里写/sg,next会去pages里找名为sg的文件,如果不作任何处理,那你直接访问就会报找不到对应的路由。
想要解决有两种方案,1.修改你的pages目录结构,在pages目录下创建一个[country]文件夹,这样你的目录就会变成pages/[country]/xxx; 这种方案虽然能实现,但是不是有点麻烦。
接下来我们看利用nextjs的中间件怎么解决,上代码
// middleware.js
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { countryCodes } from '@/utils/country'; // countryCodes['sg', 'my',...] export function middleware(request: NextRequest) {try {const { pathname } = request.nextUrl;const pathParts = pathname.split('/').filter(Boolean);// 没有有国家代码,默认sgif (!countryCodes.includes(pathParts[0])) {const defaultCountry = 'sg';return NextResponse.redirect(new URL(`/${defaultCountry}${pathname}`, request.url));}// 重写 URL,移除国家代码const newPathname = '/' + pathParts.slice(1).join('/');return NextResponse.rewrite(new URL(newPathname, request.url));} catch (error) {console.error('Error in middleware:', error);return NextResponse.error();}
}// Configuration to specify the paths the middleware should match
export const config = {matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)',],
};
上面这段代码会检查路径的第一个部分是否是一个有效的国家代码。如果不是,则默认使用 'sg'(新加坡)作为国家代码,并重定向到新的 URL。如果路径中包含有效的国家代码,则将其移除,并重写请求的 URL。
运行一下会发现,已经实现了我们想要的效果,已经不会报路由错误了,每个页面访问地址前都增加了国家代码,但是新的问题又来了,你会发现页面是空白的,打开控制台你会发现你的静态资源和接口请求都自动拼接了国家代码,这显然是不对的,下面让我们改动一下,加入下面的代码
// 不处理 Next.js 内部请求和静态文件if (pathname.startsWith('/_next') || pathname.startsWith('/static') || pathname.includes('/dowo-app-application/') ||pathname.includes('/dowo-ops-web-application/') ||pathname.includes('/comment/') ||pathname.includes('.')) {return NextResponse.next()}
这样再看,页面就正常了,以为到这儿就完事了吗?小伙伴突然过来和我说,服务端接口请求都拿不到参数了,还得在改进一下
const { pathname, searchParams } = request.nextUrl;//将searchParams拼在地址后面
到这里功能实现完成了,点一点都正常了。结果产品突然说我们想要根据当前的访问用户的ip自动去跳转对应的国家,还得再修改一下,整体思路就是,先判断有没有国家代码,如果没有就获取当前的用户ip,通过ip去获取所在国家的代码,获取到之后判断是否是在我们所支持的范围内,如果不支持还按新加坡处理,下面来看看完整的代码吧
// middleware.js
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
import { countryCodes } from '@/utils/country'export async function middleware(request: NextRequest) {const { pathname, searchParams } = request.nextUrl;// 不处理 Next.js 内部请求和静态文件if (pathname.startsWith('/_next') || pathname.startsWith('/static') || pathname.includes('/easyhome-app-application/') ||pathname.includes('/easyhome-ops-web-application/') ||pathname.includes('/api_config/') ||pathname.includes('/comment/') ||pathname.includes('.')) {return NextResponse.next()}const pathParts = pathname.split('/').filter(Boolean)if (!countryCodes.includes(pathParts[0])) { // 国家代码不存在let defaultCountry = 'sg';// 获取 IP 地址try {const ip = (request.ip || request.headers.get('x-real-ip') || request.headers.get('x-forwarded-for') || '').split(',')[0].trim();const response = await fetch(`${request.nextUrl.origin}/api/ipLookup?ip=${ip}`);if (!response.ok) {return NextResponse.redirect(new URL(`/${defaultCountry}${pathname}?${searchParams.toString()}`, request.url))}const ipInfo = await response.json();if (ipInfo && ipInfo.country) {const code = ipInfo.country.iso_code.toLowerCase();if (countryCodes.includes(code)) {defaultCountry = code;}}return NextResponse.redirect(new URL(`/${defaultCountry}${pathname}?${searchParams.toString()}`, request.url))} catch (error) {console.error('Error fetching IP info:', error);return NextResponse.redirect(new URL(`/${defaultCountry}${pathname}?${searchParams.toString()}`, request.url)) }}// 重写 URL,移除国家代码const newPathname = '/' + pathParts.slice(1).join('/') + '?' + searchParams.toString();return NextResponse.rewrite(new URL(newPathname, request.url))
}export const config = {matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)",],
};
到这里,我们的功能就全部完成了,可以通过用户的ip地址自动跳转对应的国家。
总结
借助 Next.js 中间件,我们如今能够以一种对开发者来说相对简单、对用户来说高性能的方式实现个性化功能。Next.js 的中间件为我们提供了一个强大的扩展点,能够在请求处理和页面渲染之间进行干预。它的应用不仅限于身份验证和重定向,还可以处理任何需要在请求生命周期特定时刻介入的逻辑。
Next.js 团队通过引入中间件这一概念,进一步增强了框架的灵活性,使开发者能够以最少的配置实现复杂的请求处理策略。随着 Next.js 的不断演进,我们可以期待中间件变得更加强大,为开发者提供更多的可能性。
参考:next官网