This guide was written back when only
getInitialProps
existed and only applies to that method. This does not apply togetServerSideProps
, and it doesn't work withgetServerSideProps
because you can't use bothgetInitialProps
andgetServerSideProps
on the same page.
Sometimes when rendering, you might want to perform a redirect. For example, you might have an HOC that only renders the component when the user is authenticated and otherwise redirects to the login page. Next.js supports both client-side and server-side rendering (SSR) and unfortunately the method for redirecting is very different in both contexts.
Client-Side
Client-side, imperative navigation is done via next/router.
import Router from 'next/router'
Router.push('/new/url')
There's also a useRouter()
hook that can be used in components.
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
Server-Side
Router uses window.history
underneath which means you can't change URLs on the server. Instead, we must get access to the response object and respond with an HTTP redirect status code.
The response object is available via the context object that get's passed to 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
Do Both in an HOC
That's messy logic for a page component, and if we plan on doing redirects in more than one place then it'd be best to abstract that into an HOC component.
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;
}
We added some logic to add a condition on the redirect, and now it's getting a little ugly, but that HOC lets us make other conditional redirecting HOCs that are much more simple. Let's say we want to create a withAuth()
HOC that redirects the user to the login page if they aren't already logged in.
// 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');
}
});
}
For more details on handling authentication in SSR with Next.js, read Detecting Authentication Client-Side in Next.js with an HttpOnly Cookie When Using SSR.
Why do we keep clientCondition
and serverCondition
separate? They are run in very different contexts: clientCondition
is run during the component rendering and can use hooks while serverCondition
is run in getInitialProps()
, has access to ctx
(and thereby req
and res
), and can't use hooks because it's not part of the component render.
You might wonder why we don't just return ctx
from getInitialProps()
. I tried it. It doesn't work because req
and res
are circular structures and can't be serialized into JSON to send down to the client for hydrating. See Circular structure in "getInitialProps" result.
Top comments (7)
It seems this doesn't work now that
getServerSideProps
exists - if your page has SSP, Next will complain that you can't usegetServerSideProps
andgetInitialProps
.You are correct in that you can't use both. This guide was back when only
getInitialProps
existed. I'll add a note to the top about that.I did figure it out though, thanks to the article I got the general idea of how it should work so thanks!
Did you do it per page? Or did you use SSP on an HOC?
Hey Justin - I implemented the server-side version of your code. and it just returns me the json as opposed to redirecting me. Any thoughts on why?
Any chance you're calling
res.send
rather thanres.end
?Thanks, the SS redirect helped me.