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

Next.js客户端组件中使用Server Actions的问题求助

问题解决方案

核心问题分析

你的冲突本质是客户端组件环境和**Server Action依赖的Server API(如cookies())**的矛盾:

  • 加'use client'时,Server Action里的cookies()(来自next/headers)只能在Server环境运行,因此报错;
  • 移除'use client'时,react-hook-form依赖客户端上下文(createContext),只能在客户端组件运行,因此报错;
  • 之前担心抽离Server Action违反Hook规则是误解——Hook规则仅约束组件内的Hook调用,Server Action是普通异步函数,抽离完全合规。

具体解决步骤

1. 把Server Action单独抽离到独立文件

创建app/actions/register.ts文件,专门存放注册的Server逻辑:

'use server';

import { z } from 'zod';
import prisma from '@/lib/prisma'; // 替换成你的Prisma实例路径
import { lucia } from '@/lib/auth'; // 替换成你的Lucia实例路径
import { redirect } from 'next/navigation';
import { cookies } from 'next/headers';

// 复用你的Zod校验规则
const registerFormSchema = z.object({
  name: z.string().min(2),
  email: z.string().email(),
  password: z.string().min(6),
});

export async function register(values: z.infer<typeof registerFormSchema>) {
  const result = registerFormSchema.safeParse(values);
  if (!result.success) {
    return { error: result.error.message };
  }

  // 修复异步问题:必须await用户创建,避免提前跳转
  const user = await prisma.user.create({
    data: {
      name: values.name,
      email: values.email,
      password: values.password, // 记得后续添加密码哈希逻辑
    },
  });

  // 同步创建Session和Cookie
  const session = await lucia.createSession(user.id, {});
  const sessionCookie = lucia.createSessionCookie(session.id);
  cookies().set(sessionCookie.name, sessionCookie.value, sessionCookie.attributes);

  redirect("/dashboard");
}

2. 修改客户端表单组件

保留'use client',导入抽离的Server Action,正常使用react-hook-form和shadcn/ui组件:

'use client';

import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form';
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from '@/components/ui/card';
import { Input } from '@/components/ui/input';
import { Button } from '@/components/ui/button';
// 导入抽离的Server Action
import { register } from '@/app/actions/register';

const registerFormSchema = z.object({
  name: z.string().min(2),
  email: z.string().email(),
  password: z.string().min(6),
});

export default function Register() {
  const form = useForm<z.infer<typeof registerFormSchema>>({
    resolver: zodResolver(registerFormSchema),
    defaultValues: {
      name: '',
      email: '',
      password: '',
    },
  });

  return (
    <Form {...form}>
      {/* 用form.handleSubmit包裹Server Action,确保表单值正确传递 */}
      <form onSubmit={form.handleSubmit(register)} className="space-y-4">
        <CardHeader>
          <CardTitle>Register</CardTitle>
        </CardHeader>
        <CardContent className="space-y-4">
          <FormField
            control={form.control}
            name="name"
            render={({ field }) => (
              <FormItem className="formItem">
                <FormLabel>Name</FormLabel>
                <FormControl>
                  <Input type="text" {...field} />
                </FormControl>
                <FormMessage />
              </FormItem>
            )}
          />
          {/* 邮箱、密码字段类似,补充完整即可 */}
          <FormField
            control={form.control}
            name="email"
            render={({ field }) => (
              <FormItem className="formItem">
                <FormLabel>Email</FormLabel>
                <FormControl>
                  <Input type="email" {...field} />
                </FormControl>
                <FormMessage />
              </FormItem>
            )}
          />
          <FormField
            control={form.control}
            name="password"
            render={({ field }) => (
              <FormItem className="formItem">
                <FormLabel>Password</FormLabel>
                <FormControl>
                  <Input type="password" {...field} />
                </FormControl>
                <FormMessage />
              </FormItem>
            )}
          />
        </CardContent>
        <CardFooter className="flex justify-between">
          <Button type="submit">Register</Button>
        </CardFooter>
      </form>
    </Form>
  );
}

关键优化点说明

  • 分离Server和客户端逻辑:Server Action单独在Server环境运行,可正常使用cookies()、Prisma等Server API;客户端组件保留'use client',满足react-hook-form的上下文需求。
  • 修复异步漏洞:原代码中user.then()是异步执行,会导致未创建Session就跳转,改为await确保所有操作完成后再重定向。
  • 合规Hook使用:抽离Server Action不违反Hook规则,因为Hook规则仅约束组件内的Hook调用,Server Action是独立的业务函数。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 22:53:16