Next.js使用Auth组件包裹页面后无法服务端渲染如何解决
故障根因
- 你当前用的是Next.js Pages Router 架构,该架构下不支持直接将普通组件定义为async异步组件,服务端渲染阶段不会等待Auth组件内的异步鉴权请求执行完成,就会提前返回未渲染完整的HTML结构,最终导致页面源码根节点为空,和CSR渲染效果一致。
- 你写在Auth组件内的异步鉴权逻辑会被推迟到客户端hydrate阶段才执行,完全不符合SSR的执行时机要求。
修复方案
方案1:鉴权逻辑迁移到getServerSideProps(推荐,符合SSR规范)
所有需要鉴权的页面,统一在getServerSideProps生命周期中完成鉴权,不需要额外封装Auth组件,从根源上保证服务端渲染前就完成鉴权校验、拿到完整的页面数据,生成完整HTML结构返回给客户端。
通用鉴权逻辑封装
可以把鉴权逻辑封装为高阶函数,避免每个页面重复写鉴权代码:
// 封装鉴权高阶函数,可复用给所有需要鉴权的页面 const withAuth = (getServerSidePropsFunc) => async (ctx) => { // 服务端直接执行鉴权请求,注意要把用户cookie带到接口请求里,避免拿不到登录态 const auth = await POST('http://localhost:7000/services/api/auth', { headers: { cookie: ctx.req.headers.cookie || '' } }); // 未登录直接重定向到登录页 if (!auth) { return { redirect: { destination: '/login', permanent: false, } } } // 鉴权通过后执行原页面的getServerSideProps业务逻辑 return getServerSidePropsFunc(ctx); }
页面代码调整
// 页面的getServerSideProps直接用withAuth包裹即可 export const getServerSideProps = withAuth(async ctx =>{ const { id } = ctx.query; try { const postById = await GET(`http://localhost:7000/services/api/post/${id}`); return { props: { post: postById }, } } catch (err) { console.log(err) return { notFound: true } } }) const DetailPost = (props) => { return ( // 原有页面逻辑不变 ) } // 页面的auth标记可以删掉了 DetailPost.auth = false export default DetailPost
_app.js代码调整
直接去掉鉴权相关的判断逻辑即可:
function MyApp({ Component, pageProps }) { return <Component {...pageProps} /> } export default MyApp
这个方案可以100%保证SSR返回完整的页面HTML结构,完全满足SEO要求。
方案2:保留Auth组件封装(仅适合无SEO需求的内部后台页面)
如果不需要SEO,只想保留组件级鉴权的写法,可以把Auth组件改为同步组件,将鉴权逻辑移到客户端useEffect中执行,同时给SSR阶段加兜底渲染结构:
// 修改后的Auth组件代码 import { useEffect, useState } from 'react'; export const Auth = (props) => { const { children } = props; const [authState, setAuthState] = useState({ loading: true, hasAuth: false }); useEffect(() => { const checkAuth = async () => { const authRes = await POST('http://localhost:7000/services/api/auth'); setAuthState({ loading: false, hasAuth: !!authRes }) } checkAuth(); }, []) if (authState.loading) { return <div>Loading...</div> } if (!authState.hasAuth) { return <div>无访问权限,请先登录</div> } return children }
注意这个方案下SSR阶段返回的HTML只会包含Loading兜底结构,不会返回实际页面内容,相当于降级为客户端渲染,不适合需要SEO的公开页面使用。
注意事项
- 服务端发起接口请求时不要用相对路径,必须写完整的域名地址,建议用环境变量区分开发/生产环境的接口域名,避免部署后接口请求异常。
- 服务端鉴权时需要把当前请求的cookie透传给鉴权接口,否则会拿不到用户的登录态,导致鉴权失败。
内容的提问来源于stack exchange,提问作者Anna
相关产品推荐
相关产品推荐

