踩坑 nextjs 的 middleware 没有读取到设置后的 cookie

前言

我们网站去年已经做了登录重定向功能,登录重定向就是我的网站在用户登录成功之后会自动跳转到 redirect 的地址,而且其他项目也都有这个功能,但是最近有朋友和我说你们的 网站 登录成功之后并没有跳转,如下图;记得之前也有人也和我说过这个问题,但是当时没有我这边并没有重现出来,后来就没管了,但是这次又有人提出了这个问题,所以这不得不看下到底是怎么回事了,好在两天时间没白花,最后终于在 StackOverflow 上找到了解决方法,链接地址。如果你对我是如何在 nextjs 中实现的登录认证感兴趣,可以翻到文章后面,文末有给大家分享。

问题描述

在朋友提出问题之后,我进行了多次测试,总结起来就是 nextjs 的 middleware 没有读取到设置后的cookie,需要重新加载页面才能读取到。但是这个问题只在线上有这个问题,本地是没有这个问题的。那一旦在 middleware 中没有拿到 cookie,那用户登录成功之后如果重定向的是一个 private 页面,那就重定向不过去。

这里可能有人会问在中间件里面没有拿到 cookie 为什么会影响你的重定向呢?

问得好,我来解释下:首先,在用户登录成功之后,我会让浏览器跳转到 redirect 页面,代码如下:

tsx 复制代码
const handleSubmit = async (event: SyntheticEvent) => {
  event.preventDefault();

  try {
    // 用户登录请求
    const response: any = await login({
      username,
      password,
      captcha,
    });
    // 获取用户信息
    const user = await currentUser();
    if (user) {
      setUser(user);
      toast.success('登录成功!');
      // 跳转到 redirect 页面
      router.replace(redirect);
    }
  } catch (error) {
    toast.error(`登录失败:${error}`);
  }
};

然后,在 nextjs 中我是用 middleware 去拦截 private 页面的,具体代码如下:

ts 复制代码
const signInPage = '/login';

export function middleware(req: NextRequest) {
  const { pathname, search, origin, basePath } = req.nextUrl;
  const cookieKey: any = 'accessToken';

  if (req.cookies.has(cookieKey)) {
    return NextResponse.next();
  }

  const signInUrl = new URL(
    `${basePath}${signInPage}?redirect=${pathname}`,
    origin
  );

  return NextResponse.redirect(signInUrl);
}

export const config = {
  matcher: [
    '/dashboard/:path*',
  ],
};

上面的 dashboard 页面就是 private 的,其他页面的是 public 的。 这里 middleware 的作用就是:如果访问的是一个 private 页面,没有获取到 cookie 就会重定向到登录页面,因此,middleware 中能否拿到 cookie 决定用户在登录之后能否重定向到 private 页面。

这下明白了吧,但是问题就出现在这里了,在本地当用户登录成功之后重定向到 private 页面是没有问题的,但是如果部署到线上就不行了,需要用户在登录成功之后刷新一下页面,这样就能重定向过去了,是不是很离谱,真的是离大谱啊。好了,问题就是这么一个问题,那如何解决呢?请看下面:

解决方法

解决方法比较简单,我们在 StackOverflow 上找到这位老哥 @Sanmeet,看下他的回答,他说:"I understand that you're encountering an issue where your Next.js middleware isn't being triggered consistently in production mode, unlike in development mode. This is likely due to Next.js caching the responses to prevent unnecessary re-executions and improve performance.

To ensure your middleware triggers every time on production, you need to set the x-middleware-cache header to no-cache in the redirect response:"。翻译过来就是:"我知道您遇到了一个问题,即与开发模式不同,Next.js 中间件在生产模式下无法持续触发。这可能是由于 Next.js 缓存了响应,以防止不必要的重新执行并提高性能。

为确保您的中间件在生产模式下每次都能触发,您需要将重定向响应中的 x-middleware-cache 标头设置为 no-cache:" 为确保您的中间件在生产模式下每次都能触发,您需要将重定向响应中的 x-middleware-cache 标头设置为 no-cache。那我们就按照他的方法去修改,修改完之后部署到线上居然好了,我只能说这位老哥是真牛皮🌹,下面是修改之后的代码。

ts 复制代码
export function middleware(req: NextRequest) {  
    const { pathname, search, origin, basePath } = req.nextUrl;  
    const cookieKey: any = 'accessToken';  

    if (req.cookies.has(cookieKey)) {  
        return NextResponse.next();  
    }  

    const signInUrl = new URL(  
    `${basePath}${signInPage}?redirect=${pathname}`,  
    origin  
    );  

    const redirectResponse = NextResponse.redirect(signInUrl);  
    redirectResponse.headers.set('x-middleware-cache', 'no-cache'); // ! FIX: Disable caching  
    return redirectResponse;  
}

Next 实现登录认证

下面分享下如何在 next 中实现登录认证,整体思路如下:

由于这里我的后端服务是一个 Java 服务,所以可以在用户发送登录请求时通过 HttpServletResponse 设置 cookie,代码如下:

java 复制代码
private void writeAccessToken(String accessToken){
    Cookie cookie = new Cookie(CommonConstant.ACCESS_TOKEN, accessToken);
    cookie.setDomain(cookieDomain);
    cookie.setPath("/");
    cookie.setMaxAge(Math.toIntExact(properties.getToken().getAccessTokenExpiresIn()));
    response.addCookie(cookie);
}

里面的 response 即 HttpServletResponse,这样我们会在请求的响应头上看到 Set-Cookie 字段,如下图。

客户端浏览器看到 Set-Cookie 之后会将我们的 accessToken 记录到浏览器中

当然,除了通过上面这种设置 cookie 的方式,你也可以通过 js-cookie 去设置 cookie。

首先,安装 js-cookie

shell 复制代码
npm install js-cookie
//如果是 ts 项目,还需要安装 @types/js-cookie
npm install @types/js-cookie

然后,像下面这样设置 accessToken 即可

ts 复制代码
export function setAccessToken(accessToken: string) {  
    Cookies.set('accessToken', accessToken, {  
        domain: domain,  
        expires: 15,  
    });  
}

这样就是通过你的某个 api 接口拿到 cookie,然后通过 js-cookie 去设置 cookie。这两种方法都可以,需要你自己选择一个合适的。

第二步:使用 middleware 拦截 private 页面

这个就仿照我上面的那样写就 ok,或者加入你自己的业务逻辑。

第三步:登录页面获取 redirect 地址并在登录成功之后跳转

这里你可以写一个方法获取,例如下面这样

ts 复制代码
import { usePathname, useSearchParams } from 'next/navigation';  
  
const REDIRECT_KEY = 'redirect';

const useLoginRedirect = () => {  
    const searchParams = useSearchParams();  
    const redirect = searchParams.get(REDIRECT_KEY);  
    const pathname = usePathname();  
    console.log('pathname', pathname);  

    if (!redirect) {  
        return '/';  
    }  
    if (redirect === pathname) {  
        return '/';  
    }  
    return redirect;  
};

第四步:实现退出登录

这个就非常简单了。

如果你是通过 HttpServletResponse 去设置 cookie 的,那退出登录就这样写

java 复制代码
public void logout() {
    Cookie cookie = new Cookie(CommonConstant.ACCESS_TOKEN, "");
    cookie.setPath("/");
    cookie.setMaxAge(0); // 将 Max-Age 设置为 0,告诉浏览器删除该 cookie
    cookie.setDomain(cookieDomain);
    response.addCookie(cookie);
}

如果你是通过 js-cookie 设置的,那退出登录就这样写

javascript 复制代码
import Cookies from 'js-cookie';

export function removeAccessToken() {  
    Cookies.remove('accessToken');  
}

最后

听说你想看看源码,安排,上链接:

github.com/lijunping36...

感谢老铁的三连🤞

欢迎访问我的官网并进行验证:www.openbytecode.com/

相关推荐
正小安30 分钟前
如何在微信小程序中实现分包加载和预下载
前端·微信小程序·小程序
_.Switch2 小时前
Python Web 应用中的 API 网关集成与优化
开发语言·前端·后端·python·架构·log4j
一路向前的月光2 小时前
Vue2中的监听和计算属性的区别
前端·javascript·vue.js
长路 ㅤ   2 小时前
vite学习教程06、vite.config.js配置
前端·vite配置·端口设置·本地开发
长路 ㅤ   2 小时前
vue-live2d看板娘集成方案设计使用教程
前端·javascript·vue.js·live2d
Fan_web2 小时前
jQuery——事件委托
开发语言·前端·javascript·css·jquery
安冬的码畜日常2 小时前
【CSS in Depth 2 精译_044】第七章 响应式设计概述
前端·css·css3·html5·响应式设计·响应式
莹雨潇潇3 小时前
Docker 快速入门(Ubuntu版)
java·前端·docker·容器
Jiaberrr3 小时前
Element UI教程:如何将Radio单选框的圆框改为方框
前端·javascript·vue.js·ui·elementui
Tiffany_Ho4 小时前
【TypeScript】知识点梳理(三)
前端·typescript