开发Next.js 的过程中遇到的一些问题

47 阅读3分钟
  1. 使用server action 来处理form表单的文件上传的时候,出现文件名乱码的情况,无论怎么修都修不好,后面换成了API route的方式

原因:Server Actions 对 multipart/form-data 里的文件名编码解析错误,官方至今没有完全修复

  1. 我当时,使用了Prima 的最新版本V7.x,但Next使用的是V14,比较低的版本,结果一直没有连上。后面知道Prisma与Next版本、Node版本有对应关系,改了版本,就好了。

老项目(13/14) :锁 Prisma 5–6。稳定主力(15) :Prisma 6.x 最稳,新项目(16) :直接上 Prisma 7.x + Node 22。

  1. Next.js 深度解析:解决 "next/headers 只能在 Server Components 中使用" 的最佳实践

问题背景

最近在开发 Next.js 项目时,遇到了一个令人困惑的错误:

Ecmascript file had an error
You're importing a component that needs "next/headers". That only works in a Server Component which is not supported in the pages/ directory.

这个错误出现在我使用 Supabase SSR 客户端时,具体代码如下:

// lib/supabase/server.ts
import { createServerClient } from "@supabase/ssr"
import { cookies } from "next/headers"  // 👈 这里出错了

export async function createClient() {
  const cookieStore = await cookies()
  // ...
}

错误原因分析

错误的核心在于 next/headers 模块只能在Server Components 中使用。但从调用栈来看,我的 server.ts 文件被多个地方引用:

  • Server Components(服务器组件)
  • Client Components(客户端组件)
  • Client Component SSR(客户端组件服务端渲染)

这意味着我试图在一个共享文件中使用只能在服务器端工作的 API,这显然是行不通的。

解决方案

经过多种尝试后,我采用了条件渲染的方案,根据运行环境返回不同的客户端:

import { createServerClient } from "@supabase/ssr"
import { createBrowserClient } from "@supabase/ssr"

export async function createClient() {
  // Server Component: create client with cookie support
  if (typeof window === 'undefined') {
    const cookieStore = await import("next/headers").then(cookies => cookies.cookies())

    return createServerClient(
      process.env.NEXT_PUBLIC_SUPABASE_URL!,
      process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
      {
        cookies: {
          getAll() {
            return cookieStore.getAll()
          },
          setAll(cookiesToSet) {
            try {
              cookiesToSet.forEach(({ name, value, options }) => cookieStore.set(name, value, options))
            } catch {
              // The "setAll" method was called from a Server Component.
            }
          },
        },
      }
    )
  }

  // Client Component: create client without cookie support
  return createBrowserClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
  )
}

export function createServerSideClient() {
  // Only for server components (no cookie handling needed for server-only operations)
  return createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookies: {
        getAll() {
          return []
        },
        setAll() {
          // No-op for server-only operations
        },
      },
    }
  )
}

解决方案的优点

  1. 保持向后兼容:现有代码基本不需要修改

  2. 符合 Next.js 最佳实践

    • 服务端组件:使用 next/headers 处理 cookies
    • 客户端组件:使用 createBrowserClient 不依赖 cookies
  3. 清晰的职责分离

    • createClient():通用客户端,自动适应环境
    • createServerSideClient():专门用于服务器端操作

使用示例

在页面组件中使用:

// app/posts/[id]/page.tsx
import { createClient } from "@/lib/supabase/server"

export default async function PostPage({ params }) {
  const supabase = await createClient()
  // ...
}

在服务端操作中使用:

// app/actions/profile-actions.ts
'use server'

import { createServerSideClient } from '@/lib/supabase/server'

export async function updateProfile(userId, updates) {
  const supabase = createServerSideClient()
  // ...
}

这个问题的解决让我对 Next.js 的混合渲染模式有了更深的理解:

  1. Server Components 和 Client Components 有明确的边界
  2. 共享代码需要考虑运行环境的差异
  3. 条件渲染是解决这类问题的有效手段

Next.js unstable_cache 函数中,不能使用cookie

Route /[locale]/dashboard used cookies() inside a function cached with unstable_cache(). Accessing Dynamic data sources inside a cache scope is not supported. If you need this data inside a cached function use cookies() outside of the cached function and pass the required dynamic data in as an argument.

  • bug 本质

动态数据(cookies/headers)不能进缓存函数。 unstable_cache 是用来缓存异步函数执行的结果的。被缓存的函数必须是 “纯静态” 的,不能直接读动态数据源,cookies()headers()searchParams,因为这些是每个请求 / 用户都不同的,一旦缓存就会串用户、出安全问题Next.js。