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

NextJS客户端路由TypeScript私有路由布局不显示问题

NextJS 私有路由HOC导致布局组件无法加载的问题

我在NextJS中尝试不使用服务端渲染实现私有路由组件,用权限校验HOC(withAuth)包裹页面组件后,发现页面的布局组件(通过getLayout设置)无法正常显示,移除HOC包裹后布局恢复正常。


withAuth HOC代码

/* eslint-disable react-hooks/exhaustive-deps */
import { useRouter } from 'next/router';
import React, { useEffect } from 'react';
import { useSelector } from 'react-redux';
import Login from '../pages/login';
import { getAuthState, LoginStatusEnum } from '../store/slices/authSlice.store';

const withAuth = (Component: React.FC) => {
  const AuthenticatedComponent = (props: any) => {
    const router = useRouter();
    const { loggedStatus } = useSelector(getAuthState);

    // TODO token auth needs to be added
    useEffect(() => {
      // If user is not logged in, return login component
      if (loggedStatus !== LoginStatusEnum.LOGGED_IN) {
        router.push('/login');
      }
    }, [loggedStatus]);

    // If user is logged in, return original component
    return <Component {...props} />;
  };

  return AuthenticatedComponent;
};

export default withAuth;

Dashboard页面组件代码

import Head from 'next/head';
import withAuth from '../components/PrivateRoute';
import getLayout, { LayoutOptions } from '../utils/getLayout/getLayout.util';
import { NextPageWithLayout } from './_app';

const Dashboard: NextPageWithLayout = ({ ...props }) => {
  return (
    <>
      <Head>
        <title>Everyone Active - Dashboard</title>
      </Head>
      <div>Welcome to the dashboard</div>
    </>
  );
};

Dashboard.getLayout = getLayout(LayoutOptions.STANDARD);

Dashboard.defaultProps = {};

Dashboard.propTypes = {};

export default withAuth(Dashboard);

getLayout工具函数代码

const getLayout = (name: LayoutOptions) => {
  // eslint-disable-next-line react/display-name
  return (page: ReactElement) => {
    switch (name) {
      case LayoutOptions.STANDARD:
        return <StandardLayout>{page}</StandardLayout>;
      case LayoutOptions.FULL_WIDTH:
        return <FullWidthLayout>{page}</FullWidthLayout>;
      default:
        throw new Error(`Layout "${name}" does not exist.`);
    }
  };
};

export default getLayout;

StandardLayout组件代码

const StandardLayout: React.FC<PropsWithChildren<StandardLayoutProps>> = ({ children, ...props }) => {
  return (
    <div className="flex flex-col h-screen justify-between">
      <div>
        <Header />
        <div className="mt-4 container grid md:grid-cols-12 mx-auto">
          <aside className="col-span-3 order-2 md:order-1">Sidebar</aside>
          <main className="col-span-9 order-1 md:order-2">{children}</main>
        </div>
      </div>
      <Footer />
    </div>
  );
};

StandardLayout.defaultProps = {};

StandardLayout.propTypes = {};

export default StandardLayout;

问题原因

你的withAuth HOC返回的AuthenticatedComponent没有继承原组件Dashboard上的getLayout静态属性。NextJS通过读取页面组件的getLayout方法来应用布局,HOC包裹后导出的是AuthenticatedComponent,它没有这个属性,所以布局无法加载。

修复方法

在HOC中把原组件的getLayout属性复制到返回的AuthenticatedComponent上,同时适配TypeScript类型:

修改后的withAuth代码:

/* eslint-disable react-hooks/exhaustive-deps */
import { useRouter } from 'next/router';
import React, { useEffect } from 'react';
import { useSelector } from 'react-redux';
import { getAuthState, LoginStatusEnum } from '../store/slices/authSlice.store';
import { NextPageWithLayout } from '../pages/_app'; // 导入你的页面类型

const withAuth = <T extends NextPageWithLayout>(Component: T) => {
  const AuthenticatedComponent = (props: any) => {
    const router = useRouter();
    const { loggedStatus } = useSelector(getAuthState);

    useEffect(() => {
      if (loggedStatus !== LoginStatusEnum.LOGGED_IN) {
        router.push('/login');
      }
    }, [loggedStatus]);

    return <Component {...props} />;
  };

  // 复制原组件的getLayout静态属性
  AuthenticatedComponent.getLayout = Component.getLayout;

  return AuthenticatedComponent as T;
};

export default withAuth;

修改后,AuthenticatedComponent继承了原页面组件的getLayout方法,NextJS就能正常加载布局了。另外你之前导入的Login组件未使用,可以考虑删除以减少冗余。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.25 09:06:35