发布于 2026-01-06 1 阅读
0

Next.js 中的客户端和服务器端重定向

Next.js 中的客户端和服务器端重定向

本指南编写于仅getInitialProps存在该方法时,且仅适用于该方法。它不适用于getServerSideProps,也无法与同时使用的方法配合使用,getServerSideProps因为您不能在同一页面上getInitialProps同时使用这两种方法。getServerSideProps

有时在渲染过程中,您可能需要执行重定向。例如,您可能有一个高阶组件 (HOC),它仅在用户通过身份验证后才渲染该组件,否则重定向到登录页面。Next.js 同时支持客户端渲染和服务端渲染 (SSR),但遗憾的是,这两种情况下的重定向方法截然不同。

客户端

客户端的命令式导航是通过next/router完成的。

import Router from 'next/router'

Router.push('/new/url')
Enter fullscreen mode Exit fullscreen mode

还有一个useRouter()可以在组件中使用的钩子。

import { useRouter } from 'next/router'

function RedirectPage() {
   const router = useRouter()
   // Make sure we're in the browser
   if (typeof window !== 'undefined') {
     router.push('/new/url')
   }
}

export default RedirectPage
Enter fullscreen mode Exit fullscreen mode

服务器端

路由器window.history底层使用了某种机制,这意味着您无法在服务器上更改 URL。相反,我们必须访问响应对象并使用 HTTP 重定向状态码进行响应。

响应对象可通过传递给 get 的上下文对象getInitialProps()获得。

import { useRouter } from 'next/router'

function RedirectPage({ ctx }) {
  const router = useRouter()
  // Make sure we're in the browser
  if (typeof window !== 'undefined') {
    router.push('/new/url');
    return; 
  }
}

RedirectPage.getInitialProps = ctx => {
  // We check for ctx.res to make sure we're on the server.
  if (ctx.res) {
    ctx.res.writeHead(302, { Location: '/new/url' });
    ctx.res.end();
  }
  return { };
}

export default RedirectPage
Enter fullscreen mode Exit fullscreen mode

在 HOC 中同时做到这两点

对于页面组件来说,这样的逻辑太混乱了,如果我们计划在多个地方进行重定向,那么最好将其抽象到一个高阶组件中。

import { useRouter } from 'next/router';

function isBrowser() {
  return typeof window !== 'undefined';
}

/**
 * Support conditional redirecting, both server-side and client-side.
 *
 * Client-side, we can use next/router. But that doesn't exist on the server.
 * So on the server we must do an HTTP redirect. This component handles
 * the logic to detect whether on the server and client and redirect
 * appropriately.
 *
 * @param WrappedComponent The component that this functionality
 * will be added to.
 * @param clientCondition A function that returns a boolean representing
 * whether to perform the redirect. It will always be called, even on
 * the server. This is necessary so that it can have hooks in it (since
 * can't be inside conditionals and must always be called).
 * @param serverCondition A function that returns a boolean representing
 * whether to perform the redirect. It is only called on the server. It
 * accepts a Next page context as a parameter so that the request can
 * be examined and the response can be changed.
 * @param location The location to redirect to.
 */
export default function withConditionalRedirect({
  WrappedComponent,
  clientCondition,
  serverCondition,
  location
}) {
  const WithConditionalRedirectWrapper = props => {
    const router = useRouter();
    const redirectCondition = clientCondition();
    if (isBrowser() && redirectCondition) {
      router.push(location);
      return <></>;
    }
    return <WrappedComponent {...props} />;
  };

  WithConditionalRedirectWrapper.getInitialProps = async (ctx) => {
    if (!isBrowser() && ctx.res) {
      if (serverCondition(ctx)) {
        ctx.res.writeHead(302, { Location: location });
        ctx.res.end();
      }
    }

    const componentProps =
      WrappedComponent.getInitialProps &&
      (await WrappedComponent.getInitialProps(ctx));

    return { ...componentProps };
  };

  return WithConditionalRedirectWrapper;
}
Enter fullscreen mode Exit fullscreen mode

我们添加了一些逻辑来对重定向进行条件判断,现在代码变得有点复杂,但这个高阶组件(HOC)让我们能够创建其他更简单的条件重定向高阶组件。假设我们想要创建一个withAuth()高阶组件,当用户尚未登录时,将其重定向到登录页面。

// This is a hook that returns a simple boolean: true if the user is
// signed in, false otherwise.
import { useIsAuthenticated } from 'src/providers/Auth';
import withConditionalRedirect from '../withConditionalRedirect';

/**
 * Require the user to be authenticated in order to render the component.
 * If the user isn't authenticated, forward to the signin page.
 */
export default function withAuth(WrappedComponent) {
  return withConditionalRedirect({
    WrappedComponent,
    location: '/signin',
    clientCondition: function withAuthClientCondition() {
      return !useIsAuthenticated();
    },
    serverCondition: function withAuthServerCondition(ctx) {
      // This isn't a good way to check for cookie values.
      // See the blog post linked below for something better.
      // We kept it simple here.
      return !ctx.req.headers.cookie.includes('session');
    }
  });
}
Enter fullscreen mode Exit fullscreen mode

有关使用 Next.js 在 SSR 中处理身份验证的更多详细信息,请阅读在使用 SSR 时通过 HttpOnly Cookie 在 Next.js 中检测客户端身份验证

为什么要保留clientConditionserverCondition分离?它们的运行上下文截然不同:clientCondition在组件渲染期间运行,可以使用钩子;而在serverCondition中运行getInitialProps(),可以访问ctx(从而访问reqres),但不能使用钩子,因为它不是组件渲染的一部分。

你可能想知道为什么我们不直接ctx从返回getInitialProps()。我试过了。这样做行不通,因为reqres是循环结构,无法序列化为 JSON 发送给客户端进行数据填充。参见“getInitialProps”结果中的循环结构

文章来源:https://dev.to/justincy/client-side-and-server-side-redirection-in-next-js-3ile