Next.js中返回页面导致服务端组件多次渲染的问题排查
问题:Next.js服务端组件在router.back后多次渲染并报错
我用Next.js和Supabase开发应用,部分页面结构如下:
const SomePage = async () => { const user = await supabase.auth.getUser() return ( <Suspense fallback={<Loading />}> <SomeClientSidePage user={user} /> </Suspense> ); }
在SomeClientSidePage组件内,某些操作会触发路由跳转到其他页面。初始加载页面时一切正常,但执行router.back操作后,服务端组件会出现多次渲染的情况。
我在SomePage中添加日志语句测试:
const SomePage = async () => { const user = await supabase.auth.getUser() console.log('here') return ( <Suspense fallback={<Loading />}> <SomeClientSidePage user={user} /> </Suspense> ); }
发现'here'被打印了20多次(初始加载时仅打印1次),当打印次数超过35-40次的阈值时,会抛出以下错误:
Uncaught Error: async/await is not yet supported in Client Components, only Server Components. This error is often caused by accidentally adding `'use client'` to a module that was originally written for the server.
移除const user = await supabase.auth.getUser()及async声明后,问题完全消失。想请教该问题的原因及解决方法?
原因分析
- 服务端组件重复渲染触发框架误判:你的
SomePage是服务端组件(无'use client'指令),但router.back触发页面回退时,Next.js的缓存机制出现异常,导致服务端组件被反复执行。当执行次数超过框架内部阈值时,渲染逻辑出错,误将服务端组件判定为客户端组件,进而抛出async/await不支持的错误——因为客户端组件不允许直接使用async/await语法。 - Supabase异步调用的副作用:
supabase.auth.getUser()是服务端异步请求,每次组件渲染都会执行一次。重复渲染会引发大量重复请求,进一步加剧渲染循环,最终触发框架的错误保护机制。
解决方法
方法1:缓存服务端用户请求结果
使用Next.js的unstable_cache缓存supabase.auth.getUser()的结果,避免每次渲染重复调用:
import { unstable_cache } from 'next/cache' const getCachedUser = unstable_cache( async () => { const { data: { user } } = await supabase.auth.getUser() return user }, ['user-data'], // 唯一缓存键 { revalidate: 60 } // 每60秒重新验证缓存 ) const SomePage = async () => { const user = await getCachedUser() console.log('here') return ( <Suspense fallback={<Loading />}> <SomeClientSidePage user={user} /> </Suspense> ); }
方法2:将用户逻辑迁移到客户端组件
如果不需要在服务端获取用户信息,直接在带'use client'的SomeClientSidePage中获取用户:
// SomeClientSidePage.jsx 'use client' import { useEffect, useState } from 'react' import { supabase } from '@/lib/supabase' export default function SomeClientSidePage() { const [user, setUser] = useState(null) useEffect(() => { const fetchUser = async () => { const { data: { user } } = await supabase.auth.getUser() setUser(user) } fetchUser() }, []) // 包含router.back的业务逻辑 return <div>{user?.email}</div> }
此时服务端页面可简化为:
const SomePage = () => { return ( <Suspense fallback={<Loading />}> <SomeClientSidePage /> </Suspense> ); }
方法3:使用Supabase Auth助手(推荐)
Next.js与Supabase集成时,官方推荐使用Auth助手获取用户,它会自动处理缓存,避免重复请求:
import { createServerComponentClient } from '@supabase/auth-helpers-nextjs' import { cookies } from 'next/headers' const SomePage = async () => { const supabase = createServerComponentClient({ cookies }) const { data: { user } } = await supabase.auth.getUser() return ( <Suspense fallback={<Loading />}> <SomeClientSidePage user={user} /> </Suspense> ); }
内容的提问来源于stack exchange,提问作者1_want_to_be_good_dev
相关产品推荐
相关产品推荐

