为 SSR 创建 Supabase 客户端
Configure your Supabase client to use cookies
学习如何配置你的 Supabase 客户端来使用 cookies。这样你的应用就可以在用户已登录的情况下在服务器上渲染了。
🌐 Learn how to configure your Supabase client to use cookies. Your app can then render on the server with the user already signed in.
使用 Supabase 进行服务器端渲染(SSR)需要基于 cookie 的会话存储。@supabase/ssr 包可以为 JavaScript 和 TypeScript 应用处理这个问题。
🌐 Server-Side Rendering (SSR) with Supabase requires cookie-based session storage. The @supabase/ssr package handles this for JavaScript and TypeScript applications.
使用此指南来:
🌐 Use this guide to:
参考这些参考部分来更好地决定如何验证用户和缓存响应:
🌐 Refer to these reference sections to make better decisions about verifying users and caching responses:
安装 #
🌐 Install
安装 @supabase/supabase-js 和 @supabase/ssr 辅助包:
🌐 Install the @supabase/supabase-js and @supabase/ssr helper packages:
npm install @supabase/supabase-js @supabase/ssr设置环境变量 #
🌐 Set environment variables
在项目根目录下创建一个 .env.local 文件。在文件中,设置项目的 Supabase URL 和 Key:
🌐 Create a .env.local file in the project root directory. In the file, set the project's Supabase URL and Key:
获取 API 详情 #
🌐 Get API details
要与数据库表中的数据进行交互,你可以使用封装了自动生成的数据 API 端点的客户端库,并使用来自项目 Connect 对话框的项目 URL 和密钥进行认证。
🌐 To interact with data in database tables, you use the client libraries that wrap the auto-generated Data API endpoints, authenticating using the Project URL and key from the project Connect dialog.
NEXT_PUBLIC_SUPABASE_URL=supabase_project_urlNEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY=supabase_publishable_key创建一个客户 #
🌐 Create a client
你需要一些设置代码来配置 Supabase 客户端以使用 Cookie。一旦你有了这些工具代码,你就可以使用 createClient 工具函数来获取一个配置好的 Supabase 客户端。
🌐 You need setup code to configure a Supabase client to use cookies. Once you have the utility code, you can use the createClient utility functions to get a properly configured Supabase client.
在浏览器上运行的代码里用浏览器客户端,在服务器上运行的代码里用服务器客户端。
🌐 Use the browser client in code that runs on the browser, and the server client in code that runs on the server.
在你写检查用户身份的代码之前,先看看选择认证方法。
🌐 Before you write code that checks who the user is, see Choosing an auth method.
编写实用函数来创建 Supabase 客户端 #
🌐 Write utility functions to create Supabase clients [#nextjs-utility-functions]
要从 Next.js 应用访问 Supabase,你需要两种类型的 Supabase 客户端:
🌐 To access Supabase from a Next.js app, you need 2 types of Supabase clients:
- 客户端组件 client - 用于从在浏览器中运行的客户端组件访问 Supabase。
- 服务器组件客户端 - 用于从仅在服务器上运行的服务器组件、服务器操作和路由处理程序中访问 Supabase。
由于 Next.js 服务器组件不能写入 cookie,你需要一个 代理 来刷新过期的认证令牌并存储它们。
🌐 Since Next.js Server Components can't write cookies, you need a Proxy to refresh expired Auth tokens and store them.
在 Next.js 15 及更早版本中,proxy.ts 文件从来不会被调用,所以会话永远不会刷新,用户会被登出。Next.js 在 16 版本中重命名了这个文件。在那之前,它是 middleware.ts,函数是 export async function middleware。里面的 Supabase 代码无论哪种方式都是一样的。
🌐 On Next.js 15 and earlier, a proxy.ts file is never called, so sessions never refresh and users get signed out. Next.js renamed this file in version 16. Before that, it's middleware.ts and the function is export async function middleware. The Supabase code inside it is the same either way.
代理负责:
🌐 The Proxy is responsible for:
- 通过调用
supabase.auth.getClaims()刷新认证令牌。 - 将刷新后的 Auth 令牌传递给服务器组件,这样它们就不会自己尝试刷新同一个令牌。这就是保持用户持续登录的方式。这是通过
request.cookies.set实现的。 - 将更新后的认证令牌传递给浏览器,以替换旧的令牌。这是用
response.cookies.set完成的。
在你的项目根目录下创建一个 lib/supabase 文件夹,或者如果你正在使用 ./src 文件夹,就在里面创建,每种客户端类型都放一个文件。然后复制每种客户端类型的 lib 工具函数。
🌐 Create a lib/supabase folder at the root of your project, or inside the ./src folder if you are using one, with a file for each type of client. Then copy the lib utility functions for each client type.
import { createBrowserClient } from '@supabase/ssr'export function createClient() { return createBrowserClient( process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY! )}连接代理 #
🌐 Hook up proxy
这段代码添加了一个 matcher,这样 Proxy 就不会在不访问 Supabase 的路由上运行。
🌐 The code adds a matcher so the Proxy doesn't run on routes that don't access Supabase.
返回 setAll 最后创建的 supabaseResponse 对象。之前的响应不会带上更新的 cookie,所以用户在下一次请求时会被登出。
🌐 Return the supabaseResponse object that setAll last built. An earlier response doesn't carry the refreshed cookies, so the user is signed out on the next request.
当你需要返回不同的响应时,先把 cookies 和缓存头复制过去:
🌐 When you need to return a different response, copy the cookies and the cache headers onto it first:
const myNewResponse = NextResponse.next({ request })myNewResponse.cookies.setAll(supabaseResponse.cookies.getAll())for (const header of ['cache-control', 'expires', 'pragma']) { const value = supabaseResponse.headers.get(header) if (value) myNewResponse.headers.set(header, value)}return myNewResponse任何人都可以伪造会话 cookie,所以如果不验证就信任它,攻击者就能展示其他用户的页面。始终使用 supabase.auth.getClaims() 来保护页面和用户数据。
🌐 Anyone can forge the session cookie, so trusting it without verification lets an attacker render another user's page. Always use supabase.auth.getClaims() to protect pages and user data.
_绝对不要_在服务器代码(例如代理)中信任 supabase.auth.getSession()。它会直接从 cookie 中读取会话,而不会重新验证。
🌐 Never trust supabase.auth.getSession() inside server code such as Proxy. It reads the session out of the cookie without revalidating it.
getClaims() 在每次调用时都会验证令牌的签名。在使用非对称签名密钥的项目中(新项目的默认设置),它会在本地对项目公钥的缓存副本进行验证。在仍然使用对称密钥的项目中,它则会调用认证服务器。不管哪种方式,声明都是来自服务器已验证的令牌,而不是随便看 Cookie 上写了什么。
import { type NextRequest } from 'next/server'import { updateSession } from '@/lib/supabase/proxy'export async function proxy(request: NextRequest) { return await updateSession(request)}export const config = { matcher: [ /* * Match all request paths except for the ones starting with: * - _next/static (static files) * - _next/image (image optimization files) * - favicon.ico (favicon file) * Feel free to modify this pattern to include more paths. */ '/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)', ],}恭喜 #
🌐 Congratulations [#nextjs-congratulations]
总结一下,你已经:
🌐 To recap, you've:
- 从服务器操作中调用了 Supabase。
- 从服务器组件调用了 Supabase。
- 设置一个 Supabase 客户端工具,以便从客户端组件调用 Supabase。如果你需要从客户端组件调用 Supabase,比如设置实时订阅,就可以使用它。
- 设置代理以自动刷新 Supabase Auth 会话。
你现在可以在客户端或服务器端代码中使用任何 Supabase 功能了。
🌐 You can now use any Supabase feature from your client or server code.
选择认证方式 #
🌐 Choosing an auth method
Supabase Auth SDK 包含三种不同的函数,用于验证用户对应用的访问权限:
🌐 The Supabase Auth SDK contains three different functions for authenticating user access to applications:
方法总结 #
🌐 Summary of the methods
- 使用
getClaims来保护页面和用户数据。它会从存储中读取访问令牌并进行验证。本地通过 WebCrypto API 和缓存的 JWKS 端点进行处理,当项目使用非对称签名密钥(新项目的默认设置)时,或者在使用对称密钥时仅通过调用getUser来验证。返回的声明始终来自 JWT 解码,而不是用户查找。当访问令牌快到期时,getClaims会在验证前刷新会话,这就是服务器渲染会话保持活跃的方式。 [getUser](/docs/reference/javascript/auth-getuser)会向项目的 Auth 实例发起网络请求以获取用户记录,这样可以获得用户的最新信息,但需要进行一次网络请求。getSession当你需要原始会话(访问令牌、刷新令牌和过期时间)时使用。例如,将访问令牌转发到另一个服务。会话是直接从本地存储加载的,并不会重新向认证服务器验证,因此当存储与客户端共享(如 cookies、请求头)时,嵌入的用户对象不应单独信任。要验证身份,请使用getClaims验证访问令牌,或调用getUser获取一个新的、服务器确认的用户记录。
总结:使用 getClaims 来验证身份(通常用于保护页面和数据),当你需要从认证服务器获取最新的用户记录时用 getUser,而当你直接需要访问或刷新令牌时用 getSession,但不要依赖它返回的用户对象来做授权决策。
缓存注意事项 #
🌐 Caching considerations
如果你的应用使用 ISR(增量静态再生成)或者部署在 CDN 后面,HTTP 响应的缓存可能会导致用户收到别人的会话。当会话被刷新时,新令牌会通过 Set-Cookie 写入响应。如果该响应被缓存并提供给其他用户,那么这个用户就会以错误的身份登录。
🌐 If your app uses ISR (Incremental Static Regeneration) or is deployed behind a CDN, caching of HTTP responses can cause users to receive another user's session. When a session is refreshed, the new token is written to the response via Set-Cookie. If that response is cached and served to a different user, that user will be signed in as the wrong person.
详细信息和特定框架的示例,请参阅 高级 Auth 服务器端渲染指南。
🌐 See the advanced Auth server-side rendering guide for details and framework-specific examples.
下一步 #
🌐 Next steps