Practical Guide to Redirects in NextJS
Jasser Mark Arioste
Hello, hustlers! In this article, you're going to learn best practices when using redirects in NextJS.
I also created a GitHub repo to provide better context to the code snippets in this article. You can easily check it out by running this command:
npx create-next-app -e https://github.com/jmarioste/nextjs-redirects-guide nextjs-redirects
1
What are redirects? #
Redirects allow you to redirect an incoming request path to a different destination path.
Where to use redirects in NextJS #
There are five (5) places where you can use redirects in NextJS. These five places are getStaticProps, getServerSideProps, middleware, API routes, and next.config.js. Depending on the size of your app, the optimal place to implement a redirect might be different.
For a small website, where there are few links, it's fine to place your redirects inside next.config.js
. Learn more about redirects from the docs.
The redirects are usually managed on the cms for a large eCommerce app or news site. It's better to catch all the redirects inside middleware.ts
. We'll explore how to implement this later. To make URLs shorter, eCommerce websites add a 301 redirect.
Redirecting inside getServerSideProps
#
Unlike next.config.js
, we can fine-tune the redirect logic inside getServerSideProps
.
User flow Redirects
Sometimes we want to redirect the user based on his/her access at a given time. Let's call this type a user-flow redirect. Redirects like these should be on getServerSideProps
.
For example in Github when a user enters sudo
mode and there's an extra verification needed to input an OTP code. One example is when you change app permissions for your GitHub account, it redirects you to this page:
After entering the authentication code, you will be redirected to the actual page.
Below is an example to redirect to the login page if the user is not logged in:
import React from 'react' import { GetServerSideProps } from 'next'; import { getSession } from "next-auth/react" const RestrictedPage = () => { return ( <div> This page is restricted and requires login. </div> ); }; export default RestrictedPage; export const getServerSideProps: GetServerSideProps = async ( ctx ) => { const redirectURL = encodeURIComponent(ctx.req.url!); const session = await getSession({ req: ctx.req }) if (!session) { return { redirect: { destination: `/login?redirectUrl=${redirectURL}`, permanent: false } } } return { props: { }, }; };
1234567891011121314151617181920212223242526272829
Updated Path or Slug Redirects
Sometimes, we want to redirect a request because the slug is updated or shortened. We can also handle this type of redirect inside getServerSideProps.
Suppose you have a route `/posts/a-very-long-url-that-is-bad-for-seo`
it should redirect to `/posts/hello-world`
, below is an example of how you might implement this in getServerSideProps
.
// posts/[slug].tsx ... export const getServerSideProps: GetServerSideProps = async ( ctx ) => { const slug = ctx.params?.slug as string; const path = ctx.req.url as string; //check redirect const redirect = getRedirectByPath(path) if (redirect) { return { redirect: { destination: redirect.destination, permanent: redirect.statusCode === 301, statusCode: redirect.statusCode //explicitly set the statuscode } } } const post = getPostBySlug(slug) ?? null; if (!post) { return { notFound: true, }; } return { props: { post, }, }; };
123456789101112131415161718192021222324252627282930313233
Explanation:
Line 6-8: First we get the URL path, then check if there's a redirect for the path using getRedirectByPath
. getRedirectByPath
is a function that checks our cms if there's a redirect mapped to a route. Note that in our GitHub repo, we only use data/redirects.json
to manage the redirects. Below is the function signature:
// data/RedirectItem.ts export interface RedirectItem { statusCode: 301 | 302 source: string; destination: string; } // data/getRedirectByPath.ts export default function getRedirectByPath(sourcePath: string): RedirectItem
12345678910
Lines #10-18, If we find a RedirectItem
, we then return a redirect result using the RedirectItem
.
Note that doing this is fine if you have a small number of routes. Once your app gets bigger, It's better to handle them inside middleware.ts
. Let me tell you why.
Suppose you have the following redirects manage inside your CMS:
# | Source | Destination | Status Code |
---|---|---|---|
1 | /posts/this-is-a-very-long-url | /posts/hello-world | 301 |
2 | /tutorials/this-tutortial-has-a-very-long-slug | /tutorials/tutorial-with-short-slug | 301 |
The source has two different base routes: posts
and tutorials
. You'll have to implement redirects for both routes inside getServerSideProps
. Imagine if you have 10 more base routes than this and you have to check 10 more redirects. I would say it's pretty tiring.
Redirecting inside getStaticProps
#
GetStaticProps
is a little bit different than getServerSideProps
. We cannot access the req
object since the pages are generated at build time. However, placing a redirect inside getStaticProps
is pretty similar to getServerSideProps
Suppose you have a route `/tutorials/[slug].tsx`
that is using ISR:
// tutorials/[slug].tsx ... export const getStaticPaths: GetStaticPaths = async () => { return { paths: [], fallback: "blocking" } } export const getStaticProps: GetStaticProps = async (ctx) => { const slug = ctx.params?.slug as string; const tutorial = getTutorialBySlug(slug) ?? null; const path = `/tutorials/${slug}`; //notice that we cannot use req.url here const redirect = getRedirectByPath(path) if (redirect) { return { redirect: { destination: redirect.destination, permanent: redirect.statusCode === 301, statusCode: redirect.statusCode } } } if (!tutorial) { return { notFound: true, }; } return { props: { tutorial: tutorial, }, revalidate: 300 }; };
123456789101112131415161718192021222324252627282930313233343536373839
Explanation:
Lines #2-8, #32: We use fallback:"blocking"
in combination with revalidate
. We do this so that pages that were not generated at build time will not result in 404. And we can check if there's a redirect for it during page re-generation at runtime.
Lines #13-22: We check if a redirect exists, and redirect to that route.
Redirecting inside NextJS middleware.ts
#
If your website has a large number of pages and the redirects are managed by the cms or some backend. It makes sense to place the redirect handle inside middleware.ts. First, create a file middleware.ts
(if you are using TypeScript) or middleware.js
(if you are using javascript) on the project's root directory.
Next, add a config that matches all the routes except API and static files:
// middleware.ts export const config = { matcher: [ /* * Match all request paths except for the ones starting with: * - api (API routes) * - _next/static (static files) * - favicon.ico (favicon file) */ '/((?!api|_next/static|favicon.ico).*)', ], }
123456789101112
Next, let's add the handler that checks if there's a redirect for the specific path:
// middleware.ts ... import { NextResponse } from 'next/server' import type { NextRequest } from 'next/server' import { RedirectItem } from 'data/RedirectItem'; export async function middleware(request: NextRequest) { const source = request.nextUrl.pathname ?? ''; const origin = request.nextUrl.origin; try { const response = await fetch(`${origin}/api/redirect-by-path`, { method: 'POST', body: JSON.stringify({ source: source }) }); const data = await response.json() as { redirect: RedirectItem }; const redirect = data.redirect; if (redirect) { return NextResponse.redirect(`${origin}${redirect.destination}`, { status: redirect.statusCode, }) } } catch (error) { console.log(error) } return NextResponse.next() }
1234567891011121314151617181920212223242526272829
Explanation:
Line #7: first we get the source path to check later if there is a redirect for this path.
Line #8: We get the origin
URL because we have to use full URLs inside middleware.ts
. We use this in lines #8 and #17.
Lines #10-15: We use the fetch API to check if there's a redirect from our backend service. In your case, this might be WordPress CMS, Strapi, Shopify, Sanity, or whatnot.
Lines #18-22: We get the data as JSON and if there's a redirect, we use NextResponse.redirect
and pass in the full URL to redirect to the destination.
What happens if you use relative paths
If you use relative paths inside middleware you get this error:
[Error: URL is malformed "/tutorials/hello-world". Please use only absolute URLs - https://nextjs.org/docs/messages/middleware-relative-urls]
1
This only occurs on production so remember to avoid relative URLs inside middleware.ts
.
NextJS 301 Redirects #
By default NextJS uses 307 and 308 for permanent and temporary redirects. If you want the status code to be 301 or 302, you can do:
export const getStaticProps: GetStaticProps = async (ctx) => { ... return { redirect: { destination: redirect.destination, statusCode: 301 } } ... };
12345678910
Demo and Code #
That's it! To give you more context to the code snippets, I created a NextJS redirect guide repo in GitHub. To simulate a database or external backend, all data and functions are inside the /data
folder.
This is the link to the demo: https://nextjs-redirects-guide.vercel.app/. You can click a link and see where it redirects you.
Conclusion #
We also learned the different places to implement redirects in NextJS. We also learned how to implement redirects on NextJS depending on the situation.
If you like this tutorial, please leave a like or share this article. For future tutorials like this, please subscribe to our newsletter or follow me on Twitter.
Resources #
Related Posts #
Credits: Image by Pexels from Pixabay