Next.js App目录下SSR页面使用HOC鉴权报错的解决方法
问题修复:Next.js App Router中SSR页面与客户端HOC的兼容问题
问题场景
在Next.js App目录下使用带'use client'的HOC处理SSR页面认证跳转时,出现报错:将SSR页面作为props传给客户端渲染组件,而客户端组件仅支持将服务端组件作为children传入。
现有代码
客户端HOC withAuth.tsx
'use client' import { useRouter } from "next/navigation"; import { useEffect } from "react"; const withAuth = (WrappedComponent: any) => { return (props: any) => { const router = useRouter(); useEffect(() => { const isLoggedIn = true; if (!isLoggedIn) { router.push("/en/auth/login"); // Redirect the user to the login page } }, []); return <WrappedComponent {...props} />; }; }; export default withAuth;
SSR页面 Dashboard/page.tsx
import EmployerLayout from '@/shared/layouts/employer-layout/employer-layout'; import {Locale} from 'i18n-config'; import {getDictionary} from 'git-dictionary'; import withAuth from '@/shared/providers/with-auth'; async function Dashboard({ params: {lang}, }: { params: {lang: Locale}; }) { const dictionary = await getDictionary(lang); return ( <EmployerLayout dictionary={dictionary}> <p>This text is rendered on the server: {dictionary.jobbit}</p> </EmployerLayout> ); } export default withAuth(Dashboard)
问题原因
Next.js App Router的组件交互规则:客户端组件(标记'use client')只能将服务端组件作为children传入,不能作为组件类型的props接收并渲染。原HOC的写法是把服务端组件Dashboard作为参数传入,然后在客户端组件中直接渲染<WrappedComponent {...props} />,本质是将服务端组件作为props传递,违反了这个规则。
解决方案
方案1:将HOC改为客户端Wrapper组件(推荐)
把原来的HOC改成一个客户端组件,让服务端组件作为它的children传入,既保留认证逻辑,又符合Next.js的组件规则。
改造后的客户端认证组件 AuthWrapper.tsx
'use client' import { useRouter } from "next/navigation"; import { useEffect, ReactNode } from "react"; type AuthWrapperProps = { children: ReactNode; }; export default function AuthWrapper({ children }: AuthWrapperProps) { const router = useRouter(); useEffect(() => { const isLoggedIn = true; // 替换为真实登录态判断逻辑 if (!isLoggedIn) { router.push("/en/auth/login"); } }, []); return <>{children}</>; }
修改SSR页面的使用方式
import EmployerLayout from '@/shared/layouts/employer-layout/employer-layout'; import {Locale} from 'i18n-config'; import {getDictionary} from 'git-dictionary'; import AuthWrapper from '@/shared/providers/auth-wrapper'; async function Dashboard({ params: {lang}, }: { params: {lang: Locale}; }) { const dictionary = await getDictionary(lang); return ( <AuthWrapper> <EmployerLayout dictionary={dictionary}> <p>This text is rendered on the server: {dictionary.jobbit}</p> </EmployerLayout> </AuthWrapper> ); } export default Dashboard;
方案2:改用服务端中间件做认证跳转(更安全)
如果认证逻辑可在服务端完成(比如通过Cookie、请求头判断登录态),推荐使用Next.js Middleware做跳转,避免客户端跳转延迟,安全性更高。
创建 middleware.ts 文件(项目根目录或app目录下)
import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server'; export function middleware(request: NextRequest) { // 替换为服务端登录态判断逻辑,比如读取Cookie const isLoggedIn = request.cookies.get('auth_token') ? true : false; // 定义需要认证的路由 const protectedRoutes = ['/en/dashboard', '/fr/dashboard']; // 根据多语言路由调整 const isProtectedRoute = protectedRoutes.some(route => request.nextUrl.pathname.startsWith(route)); if (isProtectedRoute && !isLoggedIn) { // 重定向到登录页,保留原路由作为跳转回来的地址 const loginUrl = new URL('/en/auth/login', request.nextUrl.origin); loginUrl.searchParams.set('redirect', request.nextUrl.pathname); return NextResponse.redirect(loginUrl); } return NextResponse.next(); } // 指定需要匹配的路由规则 export const config = { matcher: ['/en/dashboard/:path*', '/fr/dashboard/:path*'], // 匹配dashboard下所有子路由 };
这种方式下,页面无需引入客户端认证组件,完全在服务端完成权限校验和跳转,更适配SSR场景。
方案3:保留HOC但调整写法(兼容参考,不推荐)
若一定要保留HOC形式,可将服务端组件的渲染逻辑放到客户端组件的children中,但写法较繁琐,不如Wrapper组件直观:
'use client' import { useRouter } from "next/navigation"; import { useEffect, ReactNode } from "react"; const withAuth = (getServerComponent: () => Promise<ReactNode>) => { return () => { const router = useRouter(); useEffect(() => { const isLoggedIn = true; if (!isLoggedIn) { router.push("/en/auth/login"); } }, []); return <>{getServerComponent()}</>; }; }; export default withAuth;
页面使用时:
// 其他导入不变 import withAuth from '@/shared/providers/with-auth'; async function Dashboard({ params: { lang } }) { // 原逻辑不变 } export default withAuth(() => Dashboard({ params: { lang } }));
内容的提问来源于stack exchange,提问作者khaled jarrad
相关产品推荐
相关产品推荐

