踩坑 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/

相关推荐
大鱼前端2 小时前
2025年,AI时代下的前端职业思考
前端
勉灬之2 小时前
封装上传组件,提供各种校验、显示预览、排序等功能
开发语言·前端·javascript
outstanding木槿4 小时前
react中实现拖拽排序
前端·javascript·react.js
ordinary904 小时前
vue.js scoped样式冲突
前端·vue.js
我要学编程(ಥ_ಥ)5 小时前
速通前端篇——JavaScript
开发语言·前端·javascript
大强的博客6 小时前
《Vue3实战教程》19:Vue3组件 v-model
前端·javascript·vue.js
塔塔开!.6 小时前
element ui 组件 时间选择器出现转换问题的解决办法
前端·javascript·vue.js
胡桃夹夹子7 小时前
前端,npm install安装依赖卡在sill idealTree buildDeps(设置淘宝依赖)
前端·npm·node.js
xing.yu.CTF7 小时前
HTML基础到精通笔记
前端·笔记·html
Amo 67297 小时前
axios 实现进度监控
开发语言·前端·javascript