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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 03:47:24