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

Next.js 14在Layout中设置Cookie报错,如何实现数据缓存?

问题:在Next.js Layout中创建Cookie失败,提示仅可在Server Action或路由处理器中修改Cookie

我尝试通过Cookie获取并缓存数据,但创建Cookie时出现错误,不确定是否允许在Layout文件中直接获取并存储数据,寻求帮助理解并解决问题。

错误信息:创建Cookie 'studentData' 失败,错误提示:仅可在Server Action或路由处理器中修改Cookie(原英文:Cookies can only be modified in a Server Action or Route Handler)


student/Layout.tsx

import type { Metadata } from "next";
import "../globals.css";
import Sidebar from "@/app/components/templates/sidebar";
import {
  Home,
  Calendar,
  BookOpenText,
  LibraryBig,
  ClipboardList,
  Waypoints,
} from "lucide-react";

import { Montserrat } from "next/font/google";
import { fetchServerSession } from "../backend/lib/hooks/getServerSession";
import { fetchStudentData } from "../backend/lib/onLoad/student/fetchStudentData";
import fetchEvents from "../backend/lib/onLoad/all/fetchEvents";
import { createCookie } from "../backend/lib/cookies/create";
import { redirect } from "next/navigation";

async function fetchAndCacheData() {
  "use server";
  const session = await fetchServerSession();

  if (!session?.user) {
    redirect("/");
  }

  const schoolId = session?.user?.school_id;
  const student_id = session?.user?.id;

  // Use Promise.all to fetch both promises concurrently
  const [studentData, events] = await Promise.all([
    fetchStudentData(schoolId?.toString() ?? "", student_id?.toString() ?? ""),
    fetchEvents(schoolId?.toString() ?? ""),
  ]);

  await Promise.all([
    createCookie("studentData", studentData),
    createCookie("events", events),
  ]);
}

await fetchAndCacheData();

const montserrat = Montserrat({ subsets: ["latin"] });

export const metadata: Metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

export default async function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  const navigationItems = [
    {
      name: "Dashboard",
      href: "/student/dashboard",
      icon: <Home />,
      current: true,
    },

    {
      name: "Calendar",
      href: "/student/calendar",
      icon: <Calendar />,
      current: false,
    },
    {
      name: "Classes",
      href: `/student/classes`,
      icon: <BookOpenText />,
      current: false,
    },
    {
      name: "Study Material",
      href: "/student/studyMaterial",
      icon: <LibraryBig />,
      current: false,
    },
    {
      name: "Tests & Assingments",
      href: "/student/tests+assignments",
      icon: <ClipboardList />,
      current: false,
    },
    {
      name: "Learning Paths",
      href: "/student/learningPaths",
      icon: <Waypoints />,
      current: true,
    },
  ];

  return (
    <html lang="en h-full bg-gray-100">
      <body className={`${montserrat.className} antialiased bg-lightBg`}>
        <Sidebar navigation={navigationItems} />
        <main className="py-10 lg:pl-72">
          <div className="px-4 sm:px-6 lg:px-8">{children}</div>
        </main>
      </body>
    </html>
  );
}

lib/cookies/create.ts

"use server";
import { cookies } from "next/headers";
import { Encrypt } from "../encryption/encrypt";

export async function createCookie(name: string, data: any) {
  try {
    // Encrypt data
    const encryptedData = await Encrypt(data);

    // Set session cookie
    cookies().set({
      name: name,
      value: encryptedData,
      httpOnly: true,
      sameSite: "strict",
      secure: process.env.NODE_ENV === "production",
      path: "/",
    });

    return true;
  } catch (err) {
    console.error(`Error creating cookie: ${name}`, err);
    return false;
  }
}

解决方案

问题根源

你直接在Layout组件的顶层调用了fetchAndCacheData(),虽然这个函数标记了"use server",但Next.js不允许在组件渲染流程的顶层直接执行修改Cookie的操作——Cookie属于HTTP响应头的一部分,只有在Route Handler或者用户交互触发的Server Action中才能修改响应头,组件渲染阶段无法操作响应头。

修正方案1:改用Route Handler处理数据获取与Cookie设置

创建一个专门的路由处理器,把数据获取和Cookie设置逻辑移到这里,再在Layout中调用这个API:

步骤1:创建Route Handler

新建app/api/cache-student-data/route.ts:

import { NextResponse } from 'next/server';
import { fetchServerSession } from '@/backend/lib/hooks/getServerSession';
import { fetchStudentData } from '@/backend/lib/onLoad/student/fetchStudentData';
import fetchEvents from '@/backend/lib/onLoad/all/fetchEvents';
import { createCookie } from '@/backend/lib/cookies/create';

export async function GET(request: Request) {
  const session = await fetchServerSession();
  if (!session?.user) {
    return NextResponse.redirect(new URL('/', request.url));
  }

  const schoolId = session.user.school_id?.toString() ?? '';
  const studentId = session.user.id?.toString() ?? '';

  const [studentData, events] = await Promise.all([
    fetchStudentData(schoolId, studentId),
    fetchEvents(schoolId),
  ]);

  await Promise.all([
    createCookie('studentData', studentData),
    createCookie('events', events),
  ]);

  return NextResponse.json({ success: true });
}

步骤2:修改Layout.tsx调用API

移除原来的await fetchAndCacheData();,替换为调用新创建的API:

// 替换原有的await fetchAndCacheData();
async function initializeData() {
  // 使用no-store避免缓存,确保每次请求都执行
  await fetch('/api/cache-student-data', { cache: 'no-store' });
}

await initializeData();

修正方案2:通过用户交互触发Server Action

如果不需要在Layout加载时就设置Cookie,可以把fetchAndCacheData改为由用户交互触发(比如页面加载后的客户端操作,或者按钮点击):

// 在Layout或子组件中使用
async function handleInitializeData() {
  await fetchAndCacheData();
}

// 客户端组件中可以用useEffect触发
useEffect(() => {
  handleInitializeData();
}, []);

注意事项

  • 服务器组件渲染阶段属于静态生成或流式渲染流程,无法修改HTTP响应头,因此不能直接在组件顶层执行修改Cookie的操作。
  • Route Handler是处理Cookie修改的安全场景,因为它直接控制HTTP请求和响应。
  • 如果只是为了缓存数据供组件使用,也可以考虑Next.js的内置cache机制或React.cache,避免用Cookie存储大量数据(Cookie有大小限制)。

内容的提问来源于stack exchange,提问作者Guilherme Abreu

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 04:43:09