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

Next.js 15客户端组件导入next/headers报错,求解决方案

解决Next.js 15 App Router中注册表单的"next/headers"导入错误

问题根源

  1. 客户端组件调用服务器端API:SignupForm作为客户端组件(继承自父页面的'use client'上下文),却导入并调用了依赖next/headers的createSupabaseClient——next/headers仅允许在服务器组件或API路由中使用。
  2. 客户端组件异步化违规:你将SignupForm定义为async函数,而Next.js客户端组件不允许直接使用异步函数声明。
  3. 不必要的客户端密码哈希:在客户端执行bcrypt.hash既不安全,也不符合Supabase的认证流程(Supabase会自动处理密码哈希)。

修复步骤

1. 清理SignupForm组件

移除服务器端Supabase依赖、异步标记和客户端哈希逻辑:

// app/(public)/signup/form.tsx
'use client'; // 显式声明为客户端组件

import styled from '@emotion/styled';
import Grid from '@mui/material/Grid2'
import LifestyleFormButton from '@/components/buttons/LifestyleFormButton';
import { countries, us_states } from '@/data/geography/geographics';
import Link from 'next/link';
import { useForm, SubmitHandler, Controller } from 'react-hook-form';
import axios from 'axios';
import {
  FormControl, Stack, Typography,
  MenuItem, TextField, Select
} from '@mui/material';

// 保留原有样式组件定义
const StyledForm = styled.form`
  border: 1px solid gray;
  width: 50%;
  padding: 50px;
  border-radius: 20px;
`;

const StyledTextField = styled(TextField)`
  .MuiInputBase-root {
    height: 32px;
    border-radius: 12px;
  }
  
  .MuiOutlinedInput-notchedOutline {
    border-radius: 12px;
  }
`;

const StyledSelectInput = styled(Select)`
  border-radius: 12px;
  height: 32px;
`;

type Inputs = {
  first_name: string;
  last_name: string;
  country: string;
  state: string;
  street_address: string;
  city: string;
  zip_code: string;
  phone: string;
  email: string;
  password: string;
}

// 移除async标记,恢复普通客户端组件声明
const SignupForm: React.FC = () => {
  const { control, handleSubmit, reset, watch, formState: { errors } } = useForm<Inputs>();

  const onSubmit: SubmitHandler<Inputs> = async (data) => {
    try {
      // 直接发送原始表单数据,密码哈希交由服务器/Supabase处理
      const response = await axios.post('/api/signup', data);
      const result = response.data;

      if (!result.user) {
        console.error('Error:', result.error);
        return;
      }

      console.log('User signed up successfully:', result.user);
      reset();
    } catch (error) {
      console.error('Unexpected error:', error);
    }
  };

  return (
    <StyledForm onSubmit={handleSubmit(onSubmit)}>
      {/* 保留原有表单UI结构 */}
      <Stack sx={{ gap: '20px' }}>
        <Grid container spacing={2}>
          <Grid size={{ xs: 6 }}>
            <FormControl fullWidth>
              <Controller
                name='first_name'
                control={control}
                defaultValue=''
                render={({ field }) =>
                  <StyledTextField placeholder='first name' {...field} required />
                }
              />
            </FormControl>
          </Grid>
          <Grid size={{ xs: 6 }}>
            <FormControl fullWidth>
              <Controller
                name='last_name'
                control={control}
                defaultValue=''
                render={({ field }) =>
                  <StyledTextField placeholder='last name' {...field} required />
                }
              />
            </FormControl>
          </Grid>
        </Grid>
        {/* 其余表单字段代码保持不变 */}
      </Stack>
    </StyledForm>
  );
};

export default SignupForm;

2. 修正API路由参数(可选但规范)

调整API路由的参数类型,去掉多余的res参数:

// app/api/signup/route.ts
import { createSupabaseClient } from '@/utils/supabase/server';
import { NextRequest, NextResponse } from 'next/server';

// 移除多余的res参数,Next.js API路由无需显式传入
export const POST = async(req: NextRequest) => {
  const supabase = await createSupabaseClient();
  const { email, password, ...profileData } = await req.json();

  try {
    // Supabase的signUp会自动处理密码哈希,无需手动操作
    const { data: { user }, error } = await supabase.auth.signUp({ 
      email, 
      password 
    });

    if (error) return NextResponse.json({ error: error.message }, { status: 400 });    

    const { error: profileError } = await supabase
      .from('user_profiles')
      .insert([{ id: user?.id, ...profileData }]);

    if (profileError) return NextResponse.json({ error: profileError.message }, { status: 400 });
    
    return NextResponse.json({ user }, { status: 200 });
  } catch (error) {
    return NextResponse.json({ error: 'Unexpected error' }, { status: 500 });
  }
};

修复原理

  • 客户端组件不再触碰服务器端专属的next/headers API,彻底规避了跨环境调用错误。
  • 遵循Next.js客户端组件规则,移除异步函数声明,确保组件渲染逻辑符合客户端环境要求。
  • 密码处理完全交给服务器端,既符合Supabase的安全规范,也避免了客户端哈希带来的额外风险。

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

相关产品推荐
方舟 Agent Plan

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

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