You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Server Actions调用路由处理器时Middleware无法识别Session的解决方案咨询

问题:Server Actions调用路由处理器时Session为undefined的解决方案

排查发现,使用Server Actions调用路由处理器时,Session会显示为undefined。原因是Server Actions在服务器端运行,不存在浏览器的请求上下文,导致基于Middleware的会话管理(如Cookie或Token)无法生效。请问是否有办法继续使用Server Actions调用路由处理器并通过Middleware进行保护,还是需要完全弃用Server Actions直接调用路由处理器?


middleware.js代码

// middleware.ts
import { NextResponse } from "next/server";
import createMiddleware from "next-intl/middleware";
import { routing } from "./i18n/routing";
import { decrypt, encrypt } from "@/utils/session";

// Next-Intl Middleware
const intlMiddleware = createMiddleware(routing);

// Authentication Middleware
const authMiddleware = async (request) => {
  const path = request.nextUrl.pathname;

  // Skip auth check for non-protected routes
  if (!path.match(/\/(ar|en)\/(quizzes|admin)/)) {
    return NextResponse.next();
  }

  const sessionCookie = request.cookies.get("session")?.value;
  console.log(sessionCookie);
  // If there is no session, redirect to login
  if (!sessionCookie) {
    return NextResponse.redirect(new URL("/ar/login", request.nextUrl));
  }

  try {
    // Decrypt session to check validity
    const session = await decrypt(sessionCookie);
    if (!session?.userId) {
      const response = NextResponse.redirect(
        new URL("/ar/login", request.nextUrl)
      );
      response.cookies.delete("session");
      return response;
    }
    return NextResponse.next();
  } catch (err) {
    const response = NextResponse.redirect(
      new URL("/ar/login", request.nextUrl)
    );
    response.cookies.delete("session");
    return response;
  }
};
// Session Refresh Middleware
const updateSessionMiddleWare = async (request) => {
  const session = request.cookies.get("session")?.value;
  if (!session) return null;

  // Refresh the session expiration
  const parsed = await decrypt(session);

  const token = await encrypt({ userId: parsed.userId });
  const response = NextResponse.next();
  response.cookies.set({
    name: "session",
    value: token,
    httpOnly: true,
    secure: process.env.NODE_ENV === "production", // Ensure cookie is only sent over HTTPS in production
    sameSite: "Lax", // Allow cross-origin requests
    maxAge: 60 * 60, // 1 hour in seconds
    path: "/", // Ensure the cookie is available to all routes
  });
  return response;
};

// Combined Middleware
export default async function middleware(request) {
  const intlResponse = intlMiddleware(request);

  const sessionResponse = await updateSessionMiddleWare(request);

  const authResponse = await authMiddleware(request);
  if (authResponse.status === 307) {
    return authResponse;
  }

  if (sessionResponse) {
    const sessionCookie = sessionResponse.cookies.get("session");
    if (sessionCookie) {
      intlResponse.cookies.set(sessionCookie);
    }
  }

  // Return the final response
  return intlResponse;
}

// Matcher Configuration
export const config = {
  matcher: ["/", "/(ar|en)/:path*"],
};

客户端组件请求代码

import { generateQuiz } from "./action";

const handleGenerate = async () => {
  const data = await generateQuiz(locale, quiz, requestBody);
};

generateQuiz Server Action代码

"use server";
import { fetchData } from "@/utils/fetchData";

// Mark this as a Server Action
export async function generateQuiz(locale, quiz, requestBody) {
  const response = await fetchData(
    `http://localhost:3000/${locale}/quizzes/initiate-quiz/${quiz}/api`,
    "POST",
    requestBody
  );
  return response;
}

fetchData函数代码

export const fetchData = async (url, method, body = null) => {
  try {
    const options = {
      method: method,
      headers: {
        "Content-Type": "application/json",
      },
      credentials: "include", // Ensure the session cookie is sent along with the request
    };

    // Only include the body if the method is not GET or HEAD
    if (method !== "GET" && method !== "HEAD" && body !== null) {
      options.body = JSON.stringify(body);
    }

    const response = await fetch(url, options);

    if (!response.ok) {
      return false;
    }

    return await response.json();
  } catch (error) {
    return false;
  }
};

路由处理器POST请求代码

export async function POST(request, { params }) {
  const session = await verifySession(); 
  console.log(session); // 从Server Action调用时为undefined,其他情况正常识别
  const t = await getTranslations("HomePage");
}

解决方案

不需要弃用Server Actions,以下两种方案均可解决问题:

方案1:抽离业务逻辑,跳过内部HTTP请求

Server Action本身运行在服务器端,无需额外发起HTTP请求调用路由处理器。将路由处理器中的核心业务逻辑(如会话验证、Quiz生成逻辑)抽成独立函数,在Server Action和路由处理器中直接复用即可。

示例代码:

// 抽离的通用业务逻辑函数
export async function handleInitiateQuiz(params, requestBody) {
  const session = await verifySession();
  if (!session) throw new Error("未授权");
  
  // 这里编写生成Quiz的核心逻辑
  const t = await getTranslations("HomePage");
  
  return { /* 返回处理结果 */ };
}

// 路由处理器中调用
export async function POST(request, { params }) {
  const body = await request.json();
  const result = await handleInitiateQuiz(params, body);
  return Response.json(result);
}

// Server Action中调用
"use server";
export async function generateQuiz(locale, quiz, requestBody) {
  return handleInitiateQuiz({ quiz }, requestBody);
}

方案2:在Server Action中手动传递会话Cookie

如果必须保留内部fetch的调用方式,可以从Server Action的请求上下文获取会话Cookie,手动添加到fetch请求头中。

修改Server Action代码:

"use server";
import { cookies } from "next/headers";
import { fetchData } from "@/utils/fetchData";

export async function generateQuiz(locale, quiz, requestBody) {
  // 从Server Action上下文获取session cookie
  const sessionCookie = cookies().get("session")?.value;
  
  const response = await fetchData(
    `http://localhost:3000/${locale}/quizzes/initiate-quiz/${quiz}/api`,
    "POST",
    requestBody,
    sessionCookie // 新增传递session参数
  );
  return response;
}

修改fetchData函数:

export const fetchData = async (url, method, body = null, sessionCookie = null) => {
  try {
    const options = {
      method: method,
      headers: {
        "Content-Type": "application/json",
        // 手动添加Cookie头
        ...(sessionCookie && { Cookie: `session=${sessionCookie}` }),
      },
    };

    if (method !== "GET" && method !== "HEAD" && body !== null) {
      options.body = JSON.stringify(body);
    }

    const response = await fetch(url, options);

    if (!response.ok) {
      return false;
    }

    return await response.json();
  } catch (error) {
    return false;
  }
};

方案说明

  • 方案1更高效,避免了不必要的内部HTTP请求,是优先推荐的做法。
  • 方案2适合无法快速重构业务逻辑的场景,且Middleware依然会对内部请求生效,符合权限保护需求。

内容的提问来源于stack exchange,提问作者aasem shoshari

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 08:04:57