Next.js 中的客户端和服务器端重定向
本指南编写于仅
getInitialProps存在该方法时,且仅适用于该方法。它不适用于getServerSideProps,也无法与同时使用的方法配合使用,getServerSideProps因为您不能在同一页面上getInitialProps同时使用这两种方法。getServerSideProps
有时在渲染过程中,您可能需要执行重定向。例如,您可能有一个高阶组件 (HOC),它仅在用户通过身份验证后才渲染该组件,否则重定向到登录页面。Next.js 同时支持客户端渲染和服务端渲染 (SSR),但遗憾的是,这两种情况下的重定向方法截然不同。
客户端
客户端的命令式导航是通过next/router完成的。
import Router from 'next/router'
Router.push('/new/url')
还有一个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
服务器端
路由器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
在 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;
}
我们添加了一些逻辑来对重定向进行条件判断,现在代码变得有点复杂,但这个高阶组件(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');
}
});
}
有关使用 Next.js 在 SSR 中处理身份验证的更多详细信息,请阅读在使用 SSR 时通过 HttpOnly Cookie 在 Next.js 中检测客户端身份验证。
为什么要保留clientCondition和serverCondition分离?它们的运行上下文截然不同:clientCondition在组件渲染期间运行,可以使用钩子;而在serverCondition中运行getInitialProps(),可以访问ctx(从而访问req和res),但不能使用钩子,因为它不是组件渲染的一部分。
你可能想知道为什么我们不直接ctx从返回getInitialProps()。我试过了。这样做行不通,因为req和res是循环结构,无法序列化为 JSON 发送给客户端进行数据填充。参见“getInitialProps”结果中的循环结构。