Next.js客户端组件调用Server Action报错:next/headers仅支持服务端组件
客户端组件调用Server Action时的next/headers导入错误
错误提示:
You're importing a component that needs next/headers. That only works in a Server Component but one of its parents is marked with "use client", so it's a Client Component.
相关代码
actions.ts(Server Action定义)
import { lucia } from "@/auth"; import prisma from "@/lib/prisma"; import { signUpSchema, SignUpValues } from "@/lib/validation"; import { hash } from "@node-rs/argon2"; import { generateIdFromEntropySize } from "lucia"; import { isRedirectError } from "next/dist/client/components/redirect"; import { cookies } from "next/headers"; import { redirect } from "next/navigation"; export async function signUp( credentials: SignUpValues ): Promise<{ error: string }> { try { const { username, email, password } = signUpSchema.parse(credentials); const passwordHash = await hash(password, { // recommended minimum parameters memoryCost: 19456, timeCost: 2, outputLen: 32, parallelism: 1 }); const userId = generateIdFromEntropySize(10); // 16 characters long const existingUsername = await prisma.user.findFirst({ where:{ username:{ equals: username, mode: "insensitive" } } }); if (existingUsername){ return {error: "Username already taken"} } const existingEmail = await prisma.user.findFirst({ where:{ username:{ equals: email, mode: "insensitive" } } }); if (existingEmail){ return {error: "Email already taken"} } await prisma.user.create({ data:{ id: userId, username: username, displayName: username, email: email, passwordHash: passwordHash } }) const session = await lucia.createSession(userId, {}); const sessionCookie = lucia.createSessionCookie(session.id); cookies().set(sessionCookie.name, sessionCookie.value, sessionCookie.attributes); return redirect("/"); } catch (error) { if (isRedirectError(error)) throw error; console.log(error); return{ error: "Something went wrong. Please try again" } } }
SignupForm.tsx(客户端组件)
/* eslint-disable @typescript-eslint/no-unused-vars */ "use client"; // import LoadingButton from "@/components/LoadingButton"; // import { PasswordInput } from "@/components/PasswordInput"; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; import { signUpSchema, SignUpValues } from "@/lib/validation"; import { zodResolver } from "@hookform/resolvers/zod"; import { useState, useTransition } from "react"; import { useForm } from "react-hook-form"; import { signUp } from "./actions"; import { Button } from "@/components/ui/button"; export default function SignUpForm() { const [error, setError] = useState<string>(); const [isPending, startTransition] = useTransition(); const form = useForm<SignUpValues>({ resolver: zodResolver(signUpSchema), defaultValues: { email: "", username: "", password: "", }, }); async function onSubmit(values: SignUpValues) { setError(undefined); startTransition(async () => { const { error } = await signUp(values); if (error) setError(error); }); } return ( <Form {...form}> <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-3"> {error && <p className="text-center text-destructive">{error}</p>} <FormField control={form.control} name="username" render={({ field }) => ( <FormItem> <FormLabel>Username</FormLabel> <FormControl> <Input placeholder="Username" {...field} /> </FormControl> <FormMessage /> </FormItem> )} /> <FormField control={form.control} name="email" render={({ field }) => ( <FormItem> <FormLabel>Email</FormLabel> <FormControl> <Input placeholder="Email" type="email" {...field} /> </FormControl> <FormMessage /> </FormItem> )} /> <FormField control={form.control} name="password" render={({ field }) => ( <FormItem> <FormLabel>Password</FormLabel> <FormControl> <Input placeholder="Password" {...field} /> </FormControl> <FormMessage /> </FormItem> )} /> <Button type="submit" className="w-full"> Create account </Button> </form> </Form> ); }
解决方法
标记Server Action文件:在
actions.ts最顶部添加'use server'指令,明确这是服务端执行的代码,避免客户端误导入服务端专属API:'use server'; // 后续的导入和代码保持不变修正客户端调用逻辑:客户端组件不能直接执行Server Action函数,需通过表单
action属性绑定,结合react-hook-form调整提交逻辑:
修改SignupForm.tsx中的提交处理:// 替换原onSubmit函数 async function handleFormSubmit(formData: FormData) { setError(undefined); const values = Object.fromEntries(formData) as SignUpValues; startTransition(async () => { const result = await signUp(values); if (result.error) setError(result.error); }); } // 同步修改form标签的onSubmit属性 <form onSubmit={form.handleSubmit(handleFormSubmit)} className="space-y-3">修复业务逻辑bug:
signUp函数中检查邮箱重复的条件错误,将where中的username改为email:const existingEmail = await prisma.user.findFirst({ where:{ email:{ equals: email, mode: "insensitive" } } });
内容的提问来源于stack exchange,提问作者darel noutsa
相关产品推荐
相关产品推荐

