首页 > 其他分享 >【NextJS】中间件实战介绍

【NextJS】中间件实战介绍

时间:2024-08-24 22:28:01浏览次数:11  
标签:实战 请求 request 中间件 js NextJS next Next

原创 洞窝技术

使用 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);

    // 没有有国家代码,默认sg
    if (!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官网

标签:实战,请求,request,中间件,js,NextJS,next,Next
From: https://www.cnblogs.com/o-O-oO/p/18378368

相关文章

  • 【Spring Boot进阶】掌握Spring Boot框架核心注解:从入门到精通(实战指南)
    文章目录SpringBoot注解大全:深入理解与实践引言第一部分:基础知识1.SpringBoot和Java注解简介2.SpringBoot项目搭建第二部分:核心注解详解3.@SpringBootApplication4.@Component,@Service,@Repository,@Controller,@RestController5.@Autowired6.@Bean7......
  • 【Spring进阶】掌握Spring MVC框架核心注解:从基础到实战应用(实战指南)
    文章目录SpringMVC常用注解详解及实践引言第一部分:SpringMVC基础回顾第1章:SpringMVC概述第2章:MVC模式与SpringMVC第3章:快速上手SpringMVC第二部分:核心控制器注解第4章:@Controller第5章:@RestController第6章:@RequestMapping第三部分:请求处理注解第7章:@RequestParam......
  • RabbitMQ 从原理到实战—golang版本
    1.MQ1.1概念MQ(MessageQueue,消息队列)是一种用于在分布式系统中实现消息传递和异步通信的技术。它充当了发送方和接收方之间的中间人,用于在应用程序或服务之间传递消息。MQ允许系统中的不同组件彼此独立运行,而无需直接通信或相互依赖,从而提高系统的可扩展性、可靠性和灵......
  • 豆瓣评分9.0!Python3网络爬虫开发实战,堪称教学典范!
    今天我们所处的时代是信息化时代,是数据驱动的人工智能时代。在人工智能、物联网时代,万物互联和物理世界的全面数字化使得人工智能可以基于这些数据产生优质的决策,从而对人类的生产生活产生巨大价值。在这个以数据驱动为特征的时代,数据是最基础的。数据既可以通过研发产品获得,......
  • Playbook剧本案例实战
    script模块script模块⽤于在远程机器上执⾏本地脚本。#在master上准备⼀个脚本[root@m0~]#vimtest.shmkdir/tmp/threetouch/tmp/three/testecho'iamecho,isusedwrite'>/tmp/three/test[root@m0~]#sourcetest.sh#在group02的远程机器⾥都执⾏master上的......
  • 前端宝典九:React Native从入门到精通实战
    本文主要介绍ReactNative新旧框架对比React与ReactNative区别ReactNative性能优化其中第3点ReactNative性能优化的拆包分包,是项目实战中使用过的,在这里整理分享,如果没有用过的小伙伴会觉得晦涩难懂,建议按照在实际项目中需要去实践,纸上得来终觉浅,绝知此事要躬行。一、......
  • 重生之我要当前端大王--node篇--02express路由,中间件
    重生之我要当前端大王–node篇第一篇章后端服务篇–nodeJS启动!02express路由,中间件前言阅读本章可学习到将接口抽离到独立模块,减少耦合,以及中间件的使用一、路由是什么,有什么用?路由是Express应用中用于处理客户端请求的规则和处理程序。每个路由可以定义一个特定......
  • 基于华为昇腾910B和LLaMA Factory多卡微调的实战教程
      大家好,我是herosunly。985院校硕士毕业,现担任算法研究员一职,热衷于大模型算法的研究与应用。曾担任百度千帆大模型比赛、BPAA算法大赛评委,编写微软OpenAI考试认证指导手册。曾获得阿里云天池比赛第一名,CCF比赛第二名,科大讯飞比赛第三名。授权多项发明专利。对机器学习和......
  • Flutter实战篇
    第四章Flutter实战4.1FluterAPP代码结构''lib”Dart代码目录“ios”、“android”是两个平台相关代码、配置目录pubspec.yaml是依赖的组件库配置如:environment:sdk:">=2.12.0<3.0.0"dependencies:flutter:sdk:flutter#Thefollowinga......
  • Flask开发实战-初识flask
    Flask开发实战-初识FlaskFlask是一个轻量级的可定制框架,使用Python语言编写,较其他同类型框架更为灵活、轻便、安全且容易上手。它可以很好地结合MVC模式进行开发,开发人员分工合作,小型团队在短时间内就可以完成功能丰富的中小型网站或Web服务的实现。另外,Flask还有很强的定制性,用......