Next.js 14在Layout中设置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
相关产品推荐
相关产品推荐

