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

登录10分钟后supabase.auth.onAuthStateChange()自发触发原因

问题分析与解决

问题背景

使用Next.js搭配Supabase数据库,通过Supabase魔法链接实现身份认证,在_app.tsx中编写了以下认证监听逻辑:

useEffect(() => {
        const {data: authListener} = supabase.auth.onAuthStateChange((event, session) => {
            handleAuthChange(event, session)
            if (event === 'SIGNED_IN') {
                setIsAuthenticated(true)
                console.log('SIGNED_IN')
                console.log(`router.asPath: ${router.asPath}`)
                if(router.asPath.includes('/signin')) {
                    console.log('redirect from signin')
                    router.push('/')
                }
            }
            if (event === 'SIGNED_OUT') {
                setIsAuthenticated(false)
                console.log('SIGNED_OUT')
                router.push('/signin')
            }
        })
        checkUser()
        return () => {
            authListener.unsubscribe()
        }
    }, [])

登录约10分钟后,SIGNED_IN事件会自动触发,导致页面被重定向到首页,此时router.asPath显示为带魔法链接token参数的/signin路径。

原因拆解

  1. Supabase会话自动刷新机制:Supabase的access token默认有效期为1小时,客户端SDK会在token过期前主动发起刷新请求。刷新成功时,无论用户是否已经处于登录状态,都会触发SIGNED_IN事件,用于更新本地会话信息——这就是你看到的“自发触发登录事件”的核心原因。
  2. 路由状态残留:从日志的router.asPath可以看出,当前路由仍保留着/signin路径和魔法链接的hash参数。这是因为登录跳转后,Next.js的客户端路由状态没有正确同步,或者URL中的hash参数未被清理,导致你的代码误判用户仍停留在登录页,进而执行了重定向逻辑。

解决方案

1. 优化SIGNED_IN事件的判断逻辑

不要仅通过router.asPath判断是否需要重定向,结合会话是否为新创建、当前页面的实际路径(不带hash)来做判断:

// 在_app.tsx组件中新增状态保存会话ID
const [prevSessionId, setPrevSessionId] = useState<string | null>(null);

useEffect(() => {
    const {data: authListener} = supabase.auth.onAuthStateChange((event, session) => {
        handleAuthChange(event, session);
        if (event === 'SIGNED_IN') {
            setIsAuthenticated(true);
            // 仅当会话是全新创建,且当前确实在登录页时才执行重定向
            const isNewSession = session?.id !== prevSessionId;
            const isOnSigninPage = router.pathname === '/signin';
            
            if (isNewSession && isOnSigninPage) {
                router.push('/');
            }
            // 更新会话ID记录
            setPrevSessionId(session?.id || null);
        }
        if (event === 'SIGNED_OUT') {
            setIsAuthenticated(false);
            router.push('/signin');
        }
    });
    // 初始化时获取当前会话ID
    checkUser().then(currentSession => setPrevSessionId(currentSession?.id || null));
    return () => authListener.unsubscribe();
}, [router.pathname, handleAuthChange]); // 补充完整依赖,避免闭包问题

2. 清理登录页的hash参数

在/signin页面的组件中添加逻辑,登录成功后自动清理URL中的token参数,避免路由状态残留:

// 在signin页面组件内
useEffect(() => {
    // 检测URL中是否包含魔法链接的token参数
    if (window.location.hash.includes('access_token')) {
        // 保留路径,移除hash参数
        window.history.replaceState({}, document.title, window.location.pathname);
    }
}, []);

3. 修正useEffect依赖

原代码的useEffect依赖为空数组,可能导致闭包问题(比如引用旧的router或handleAuthChange)。将依赖补充完整,确保逻辑使用的是最新的状态和方法。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 08:15:43