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

如何在Next.js中创建可共享的Supabase服务端客户端

在Next.js中全局复用Supabase服务端客户端

首先纠正一个误解:createServerClient本身不是单例,每次调用都会生成新的客户端实例。服务端必须为每个请求维护独立的客户端(因为每个请求的用户认证cookie不同),直接全局共享单例会导致请求间的上下文污染。但我们可以通过请求级别的缓存来避免同一请求内重复创建客户端,同时保证请求间的隔离。

实现方案:用AsyncLocalStorage做请求上下文隔离

Node.js的AsyncLocalStorage可以存储请求级别的上下文,确保每个请求有独立的Supabase客户端,同时同一请求内多次调用可复用实例。

1. 创建Supabase客户端工具文件

新建lib/supabase/server.ts,实现请求级别的客户端缓存:

import { createServerClient, type CookieOptions } from '@supabase/ssr'
import { cookies } from 'next/headers'
import { AsyncLocalStorage } from 'async_hooks'
// Pages Router需要额外导入cookie库:import cookie from 'cookie'

// 初始化请求上下文存储
const supabaseContext = new AsyncLocalStorage<ReturnType<typeof createServerClient>>()

// App Router/Next.js Actions 通用方法
export function getSupabaseServerClient() {
  // 检查当前请求上下文是否已有客户端,有则直接返回
  const existingClient = supabaseContext.getStore()
  if (existingClient) {
    return existingClient
  }

  const cookieStore = cookies()
  const newClient = createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.SUPABASE_SERVICE_ROLE_KEY!,
    {
      cookies: {
        get(name: string) {
          return cookieStore.get(name)?.value
        },
        set(name: string, value: string, options: CookieOptions) {
          try {
            cookieStore.set({ name, value, ...options })
          } catch (err) {
            // 服务器组件中无法设置cookie,忽略错误避免崩溃
          }
        },
        remove(name: string, options: CookieOptions) {
          try {
            cookieStore.set({ name, value: '', ...options, maxAge: 0 })
          } catch (err) {
            // 同上处理
          }
        },
      },
    }
  )

  // 将新客户端存入当前请求上下文
  supabaseContext.run(newClient, () => {})
  return newClient
}

// Pages Router API路由适配方法(可选)
export function getSupabaseServerClientForPages(req: NextApiRequest, res: NextApiResponse) {
  const existingClient = supabaseContext.getStore()
  if (existingClient) {
    return existingClient
  }

  const newClient = createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.SUPABASE_SERVICE_ROLE_KEY!,
    {
      cookies: {
        get(name: string) {
          return req.cookies[name]
        },
        set(name: string, value: string, options: CookieOptions) {
          res.setHeader('Set-Cookie', cookie.serialize(name, value, options))
        },
        remove(name: string, options: CookieOptions) {
          res.setHeader('Set-Cookie', cookie.serialize(name, '', { ...options, maxAge: 0 }))
        },
      },
    }
  )

  supabaseContext.run(newClient, () => {})
  return newClient
}

2. 在Next.js Actions中使用

// app/actions.ts
'use server'

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

export async function fetchUser(userId: string) {
  const supabase = getSupabaseServerClient()
  // 同一Action内多次调用会复用同一个客户端实例
  const { data, error } = await supabase.from('users').select('*').eq('id', userId).single()
  
  if (error) throw new Error(error.message)
  return data
}

3. 在Pages Router API路由中使用

// pages/api/users.ts
import type { NextApiRequest, NextApiResponse } from 'next'
import { getSupabaseServerClientForPages } from '@/lib/supabase/server'

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  const supabase = getSupabaseServerClientForPages(req, res)
  const { data, error } = await supabase.from('users').select('*')
  
  if (error) return res.status(500).json({ error: error.message })
  res.status(200).json(data)
}

关键注意事项

  • 请求隔离:AsyncLocalStorage确保每个请求的客户端独立,不会出现不同用户的认证上下文混淆。
  • 权限安全:SUPABASE_SERVICE_ROLE_KEY拥有Supabase的最高权限,务必通过环境变量配置,绝对不能暴露给客户端代码。
  • 开发环境兼容:Next.js热重载不会破坏AsyncLocalStorage的隔离性,每个请求仍会维护独立的客户端实例。
  • 服务器组件限制:在服务器组件中调用set/remove cookie会报错,所以代码中加入了错误捕获,避免应用崩溃。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 12:43:19