Next.js:如何在实现私有路由时防止未授权路由/页面的闪烁重定向?

5

本质上,我为我的Next.js应用程序中的两个页面(即 profile 和 dashboard )创建了一个高阶组件,以防止未经授权的用户访问它们。

例如: pages / profile.js

<code>import withAuth from "../components/AuthCheck/index";

function Profile() {
  return (
    <>
      <h1>Profile</h1>
    </>
  )
}


export default withAuth(Profile);
</code>

我的认证组件/HOC:

import { useRouter } from 'next/router'
import { useUser } from '../../lib/hooks'
import { PageLoader } from '../Loader/index'

const withAuth = Component => {
  const Auth = (props) => {
    const { isError } = useUser(); //My hook which is calling /api/user see if there is a user
    const router = useRouter()


    if (isError === 'Unauthorized') {
      if (typeof window !== 'undefined' && router.pathname === '/profile' || router.pathname === 'dashboard') router.push('/login')

      return <PageLoader />
    }
    return (
      <Component {...props} />
    );

  };

  if (Component.getInitialProps) {
    Auth.getInitialProps = Component.getInitialProps;
  }

  return Auth;
};

export default withAuth;

现在的情况是,如果您在浏览器URL栏中输入/profile/dashboard,在重定向之前会先看到页面闪烁一下。
你知道为什么会发生这种情况吗?

5
由于重定向发生在客户端,因此在它发生之前你会短暂地看到服务器生成的页面。唯一防止这种情况的方法是在服务器端进行重定向。 - juliomalves
@juliomalves 很有趣。谢谢。那么你会把那个逻辑放在哪里呢? - Antonio Pavicevac-Ortiz
2个回答

4

对于需要进行身份验证的页面,建议考虑使用 getServerSideProps。该方法将在每次请求时在服务器端运行。

或者,根据您的项目设置(特别是如果您可以在_app.tsx_中访问身份验证状态),在您的_app中呈现身份验证组件可能更有意义。具体来说,您可以在需要进行身份验证的页面上添加一个protected: true属性(使用静态属性)。然后在app中,您可以检查特定页面是否具有protected===true,如果用户未经身份验证,则重定向到身份验证组件,例如:

            {pageProps.protected && !user ? (
                <LoginComponent />
            ) : (
              <Component {...pageProps} />
            )}

谢谢Adrian,你的帖子帮助解决了问题! - Antonio Pavicevac-Ortiz

4

根据Juliomalves和Adrian提到的内容,我重新阅读了Next.js文档,这样做总是很好的。

话虽如此,我尝试了Adian发布的内容。

_app.js文件中,我执行了以下操作:

import dynamic from "next/dynamic";
import { useRouter } from 'next/router'

import { useEffect } from 'react';

import { PageLoader } from '../components/Loader/index'

import { useUser } from '../lib/hooks'

import Login from '../pages/login'

const Layout = dynamic(() => import('../components/Layout'));

function MyApp({ Component, pageProps }) {
  const { user, isLoading } = useUser();
  const router = useRouter();

  useEffect(() => {
    router.replace(router.pathname, undefined, { shallow: true })
  }, [user])

  function AuthLogin() {
    useEffect(() => {
      router.replace('/login', undefined, { shallow: true })
    }, [])
    return <Login />
  }

  return (
          <Layout>
            {isLoading ? <PageLoader /> :
              pageProps.auth && !user ? (
                <AuthLogin />
              ) : (
                <Component {...pageProps} />
              )
            }
          </Layout>

  );
}

export default MyApp

isLoading是来自SWR hook useUser()的一个 prop,在第一个条件三元表达式中,如果为true,则显示<Loader/>;如果为false,则进入下一个条件三元表达式;

如果auth!user都为true,则渲染AuthLogin组件!

这就是我的做法。我进入了我想要私有化的页面,并使用异步函数getStaticProps创建了auth prop,并将其设置为true

/pages/dashboard.js 或者任何你想要私有化的页面;

export default function Dashboard() {
  return (
    <>
      <h1>Dashboard</h1>
    </>
  )
}

export async function getStaticProps() {
  return {
    props: {
      auth: true
    },
  }
}

在`_app.js`中,当页面被渲染时,根据文档所述,`getStaticProps`将会预渲染此页面并在构建时间使用返回的props。因此,当在`_app`中到达`pageProps.auth && !user`时,就是`auth`的来源。
最后两件事情:
你需要在`MyApp`组件中使用带有钩子中的`user` prop的`useEffect`函数。因为这将保持重定向之间的URL同步/正确。
在`/pages/_app`中,添加`MyApp`。
 useEffect(() => {
    router.replace(router.pathname, undefined, { shallow: true })
  }, [user]);

AuthLogin 组件中添加:
 useEffect(() => {
      router.replace('/login', undefined, { shallow: true })
    }, []);

这样做可以确保组件渲染时URL是正确的。

如果你的页面经常更改,我相信你需要研究一下getServerSideProps ,但对于静态页面,这解决了我的用例!

感谢Juliomalves和Adrian!


小问题,我会重构并将您的AuthLogin函数定义从MyApp定义中提取出来。在另一个组件内定义组件绝不是一个好主意。 - mccambridge

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接