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

Next.js Middleware追加user_id到请求体报错排查及替代方案(Supabase)

基于Supabase的Next.js项目:传递user_id到API的问题解决

问题背景

我正在开发一个基于Supabase的Next.js项目,Supabase的users表存储已认证用户,每个用户分配uuid作为user_id。用户填写表单注册为员工时,数据需要存入employees表(该表主键为users表的user_id),但表单提交的API请求体未携带user_id。尝试通过Middleware从用户会话获取user_id并追加到请求体时,出现TypeScript错误:扩展后的对象缺少cookies、geo等NextRequest核心属性。

当前middleware.ts代码

import { NextRequest, NextResponse, Body } from 'next/server';
import { createSupabaseReqResClient } from '@/lib/supabase/server-clients';

export async function middleware(request: NextRequest) {
  let modifiedRequest = request;
  const supabase = createSupabaseReqResClient(request, NextResponse.next());
  const { data: { session } } = await supabase.auth.getSession();
  const user = session?.user;

  if (!user && request.nextUrl.pathname.startsWith('/my-profile')) {
    return NextResponse.redirect(new URL('/', request.url));
  }

  if (request.nextUrl.pathname === '/api/employee') {
    const body = await request.json();
    const user_id = user?.id;
    const modifiedBody = new Body(JSON.stringify({ ...body, id: user_id }));
    modifiedRequest = {
      ...request,
      body: modifiedBody,
    };
  }

  return NextResponse.next(modifiedRequest);
}

export const config = {
  matcher: ['/my-profile','/api/employee'],
};

错误信息

Type '{ body: any; [INTERNALS]: { cookies: RequestCookies;
 geo: { city?: string | undefined;
 country?: string | undefined;
 region?: string | undefined; latitude?: string | undefined;
 longitude?: string | undefined; } | undefined;
 ip?: string | undefined;
 url: string; nextUrl: NextURL; };
 ... 18 more ...;
 text(): Promise<....' is missing the following properties 
from type 'NextRequest': cookies, geo, ip, nextUrl, and 3 more.ts(2740)
let modifiedRequest: NextRequest

解决方案

一、修复Middleware的错误

NextRequest是类实例,无法通过对象展开(...request)完整复制其私有属性和方法,直接修改会导致TypeScript类型不匹配。正确做法是创建新的NextRequest实例,保留原请求的核心属性,再通过NextResponse.rewrite转发修改后的请求。

修正后的middleware.ts代码:

import { NextRequest, NextResponse } from 'next/server';
import { createSupabaseReqResClient } from '@/lib/supabase/server-clients';

export async function middleware(request: NextRequest) {
  const supabase = createSupabaseReqResClient(request, NextResponse.next());
  const { data: { session } } = await supabase.auth.getSession();
  const user = session?.user;

  // 未登录访问/my-profile时重定向到首页
  if (!user && request.nextUrl.pathname.startsWith('/my-profile')) {
    return NextResponse.redirect(new URL('/', request.url));
  }

  // 处理/api/employee请求,仅当用户已登录时修改请求体
  if (request.nextUrl.pathname === '/api/employee' && user) {
    // 读取原请求体
    const body = await request.json();
    // 追加user_id到请求体(注意字段名需与employees表主键一致,这里改为user_id)
    const modifiedBody = JSON.stringify({ ...body, user_id: user.id });
    
    // 构造新的NextRequest实例,保留原请求的核心配置
    const modifiedRequest = new NextRequest(request.url, {
      method: request.method,
      headers: request.headers,
      body: modifiedBody,
      cookies: request.cookies,
    });

    // 转发修改后的请求到原API路由
    return NextResponse.rewrite(request.nextUrl, modifiedRequest);
  }

  return NextResponse.next();
}

export const config = {
  matcher: ['/my-profile','/api/employee'],
};

二、其他传递user_id到API的方案

除了Middleware,还有几种更简洁的实现方式:

1. 客户端直接追加user_id到请求体

在客户端组件中通过Supabase的钩子获取当前用户ID,提交表单时直接加入请求体:

// 客户端表单组件
import { useUser } from '@supabase/auth-helpers-react';

export default function EmployeeForm() {
  const user = useUser();

  const handleSubmit = async (formData: FormData) => {
    if (!user) return; // 未登录时不提交

    const formPayload = Object.fromEntries(formData);
    // 追加user_id到请求数据
    const payload = { ...formPayload, user_id: user.id };

    const res = await fetch('/api/employee', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(payload),
    });

    // 处理响应逻辑
    if (res.ok) {
      alert('注册成功');
    }
  };

  return (
    <form onSubmit={(e) => { e.preventDefault(); handleSubmit(new FormData(e.target as HTMLFormElement)); }}>
      {/* 表单字段 */}
      <button type="submit" disabled={!user}>提交</button>
    </form>
  );
}

优点:实现简单,无需Middleware;缺点:依赖客户端状态,需确保用户已登录。

2. 服务端API路由直接获取user_id

在API路由内部通过Supabase获取当前用户会话,直接使用用户ID插入数据,无需修改请求体:

// app/api/employee/route.ts
import { createSupabaseReqResClient } from '@/lib/supabase/server-clients';
import { NextRequest, NextResponse } from 'next/server';

export async function POST(request: NextRequest) {
  const supabase = createSupabaseReqResClient(request, NextResponse.next());
  const { data: { session } } = await supabase.auth.getSession();
  const user = session?.user;

  if (!user) {
    return NextResponse.json({ error: '未授权访问' }, { status: 401 });
  }

  const body = await request.json();
  // 插入数据时直接使用用户ID作为employees表的主键
  const { error } = await supabase
    .from('employees')
    .insert({ ...body, user_id: user.id });

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

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

优点:安全性高,user_id由服务端获取,客户端无法篡改;缺点:每个需要用户ID的API路由需重复获取会话逻辑(可封装为公用函数)。

3. 通过请求头传递user_id

在Middleware中将user_id加入请求头,API路由从请求头读取:

// middleware.ts中的处理逻辑
if (request.nextUrl.pathname === '/api/employee' && user) {
  const headers = new Headers(request.headers);
  headers.set('X-User-ID', user.id);
  return NextResponse.next({ headers });
}

API路由中读取:

// app/api/employee/route.ts
export async function POST(request: NextRequest) {
  const userId = request.headers.get('X-User-ID');
  const supabase = createSupabaseReqResClient(request, NextResponse.next());
  const { data: { session } } = await supabase.auth.getSession();

  // 验证用户会话,防止请求头被伪造
  if (!userId || session?.user.id !== userId) {
    return NextResponse.json({ error: '未授权访问' }, { status: 401 });
  }

  const body = await request.json();
  const { error } = await supabase.from('employees').insert({ ...body, user_id: userId });

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

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

优点:无需修改请求体,适合固定格式的请求;缺点:需额外验证请求头的合法性,防止伪造。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 06:00:25