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

Next.js 13 + Supabase:如何正确创建登录用户上下文?

Next.js 13 + Supabase 用户上下文/提供者实现方案

针对你遇到的水合错误、状态设置问题,下面是经过验证的实现方案,完全适配Next.js 13 App Router + Supabase的架构:

1. 创建用户上下文客户端组件

首先在app/providers目录下创建UserContext.tsx,这是核心的状态管理组件,负责监听Supabase认证状态、获取完整用户信息:

'use client';

import { createContext, useContext, useEffect, useState } from 'react';
import { createClientComponentClient } from '@supabase/auth-helpers-nextjs';
import type { User } from '@supabase/supabase-js';

// 定义上下文类型,包含用户数据和加载状态
type UserContextType = {
  user: User | null;
  loading: boolean;
};

const UserContext = createContext<UserContextType | undefined>(undefined);

export function UserProvider({ children }: { children: React.ReactNode }) {
  const [user, setUser] = useState<User | null>(null);
  const [loading, setLoading] = useState(true);
  const supabase = createClientComponentClient();

  useEffect(() => {
    // 根据用户ID从数据库获取完整用户对象
    const fetchUserProfile = async (userId: string) => {
      const { data, error } = await supabase
        .from('users')
        .select('*')
        .eq('id', userId)
        .single();

      if (!error) setUser(data);
      setLoading(false);
    };

    // 处理认证状态变化
    const handleAuthStateChange = async (_event: string, session: any) => {
      setLoading(true);
      if (session?.user) {
        await fetchUserProfile(session.user.id);
      } else {
        setUser(null);
        setLoading(false);
      }
    };

    // 订阅Supabase认证状态变更
    const { data: listener } = supabase.auth.onAuthStateChange(handleAuthStateChange);

    // 初始化时检查当前会话
    const initAuth = async () => {
      const { data: { session } } = await supabase.auth.getSession();
      if (session?.user) {
        await fetchUserProfile(session.user.id);
      } else {
        setLoading(false);
      }
    };

    initAuth();

    // 组件卸载时取消订阅
    return () => listener?.unsubscribe();
  }, []);

  return (
    <UserContext.Provider value={{ user, loading }}>
      {children}
    </UserContext.Provider>
  );
}

// 自定义Hook,方便组件调用上下文
export const useUser = () => {
  const context = useContext(UserContext);
  if (context === undefined) {
    throw new Error('useUser必须在UserProvider内部使用');
  }
  return context;
};

2. 在根布局中注入Provider

根布局(app/layout.tsx)作为服务器组件,直接导入并包裹UserProvider,让整个应用都能访问用户上下文:

import { UserProvider } from './providers/UserContext';

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="zh-CN">
      <body>
        <UserProvider>{children}</UserProvider>
      </body>
    </html>
  );
}

3. 核心问题解决要点

  • 避免水合错误:所有Supabase操作、状态更新都放在useEffect中执行,确保仅在客户端环境运行,避免服务器渲染与客户端DOM不匹配。
  • 处理加载状态:保留loading状态,在用户数据未获取完成前渲染占位内容(如加载动画),防止页面闪烁或水合失败。
  • 状态一致性:在认证状态变更时(包括登出),及时同步更新user状态为null,避免残留旧数据。

4. 在业务组件中使用用户上下文

在任意客户端组件中,通过useUser Hook获取用户数据:

'use client';

import { useUser } from '../providers/UserContext';

export default function ProfilePage() {
  const { user, loading } = useUser();

  if (loading) return <div>加载中...</div>;
  if (!user) return <div>请先登录</div>;

  return (
    <div>
      <h1>欢迎回来,{user.nickname}</h1>
      <p>邮箱:{user.email}</p>
    </div>
  );
}

常见问题排查

  • 状态设置错误:检查handleAuthStateChange中是否正确处理了session为空的情况,确保登出时user被重置为null;同时在fetchUserProfile中处理数据库查询错误,避免未捕获的Promise导致状态异常。
  • 水合延迟:禁止在组件初始化阶段同步设置用户状态,必须通过useEffect异步执行数据获取逻辑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 15:50:26