Next.js Redirects: A Developer's Guide
Tags: webdev, nextjs, redirects
Published: 2024-04-11 08:58:54

Welcome to our deep dive into Next.js redirects. Think of it this way: you're the architect, and your web application is a complex building. Each redirect is a doorway, guiding your users seamlessly through your structure. Whether you're an experienced developer or a newcomer, mastering the art of Next.js redirects can elevate your application from a basic structure to a well-designed edifice. Let's get started on this journey of discovery together!

From the enchanted lands of `next.config.js` to the dynamic realms of Middleware, and the unexpected twists and turns of `getStaticProps` and `getServerSideProps`, we'll explore every nook and cranny of this magical world. And for the grand finale, we'll unveil the secret of the redirect function in AppRouter, a powerful spell that can change the course of your user's journey in an instant.

So, grab your wand (or keyboard), put on your wizard hat (or thinking cap), and let's embark on this magical journey of discovery together!
Unfortunately, I wasn’t allowed to experiment on production so let's install a clean Next.js app and dig in.

## TL;DR
- If you know which links will be redirected or at least you can extract some pattern – use next.config.js redirects/rewrites (depending on needs)  
- If you need dynamically, according to some data from your CMS, or your redirects filled in the CMS – use Middleware  
- In rare cases, you can use getStaticProps/getServerSideProps for PageRoute or redirect function for AppRouter.
For the next.config.js cheat sheet go to the bottom of the article.

The main goal of this article is to provide a cheat sheet for Next.js redirects, allowing you to quickly decide which one suits your needs.  
So what do we have? There are a few options for redirects:

1. next.config.js
2. Middleware
3. getStaticProps/getServerSideProps for PageRoute
4. redirect function for AppRouter

I’ve created a [repository](https://github.com/Kragov/nextjs-redirects-cheatsheet) where you can see some of the redirects mentioned in this article, so you can always go and play with it yourself.

![image.png](https://cdn.sanity.io/images/vftxng62/production/1585ee391263a6ec4225edda398875e11ccc8169-1228x576.tif?w=2000&fit=max&auto=format&dpr=2)

Now we have our experiments field.  

[//]: # (In the documentation, the first mention of redirects is on Building your application/Routing/Route handlers/Redirects. However, it is presented only by a small piece of code for the API folder, with no further explanations of its purpose. )

## next.config.js

Let’s start with **next.config.js** and see what kind of magic we can do there.  
In the context of **next.config.js**, we have two options that essentially do the same job: they show you the content of another page, different from where you came from. However, there is one key difference between them.

[Redirects](https://nextjs.org/docs/app/api-reference/next-config-js/redirects) allow you to redirect an incoming request path to a different destination path. So when you will come to `/old-blog` you will be redirected to `/new-blog` and your URL will be changed.

[Rewrites](https://nextjs.org/docs/app/api-reference/next-config-js/rewrites) allow you to map an incoming request path to a different destination path.  
Rewrites act as a URL proxy and mask the destination path, making it appear the user hasn't changed their location on the site. In other words, when you come to `/old-blog` you will see the content of `/new-blog` but the URL will remain the same.
Here is an example of next.config.js that has both functions.
    
```Javascript
const nextConfig = {
    redirects: async () => {
        return [
            {
                source: '/',
                destination: '/home',
                permanent: true,
            },
        ]
    },
    rewrites: async () => {
        return [
            {
                source: '/home',
                destination: '/rewrite',
            },
        ]
    },
}
```
Both types are recommended to have up to 1000 elements in each array for better performance, but it is not a strict restriction. And here, a question may arise: “Why only 1k? It seems like a small number, especially if you have a blog on your website, particularly one with autogenerated pages.”. This is a legitimate question, but the Next.js config provides an answer for that. Filters. Filters, to be more clear: any object of redirect and rewrite may have 'has' and 'missing' keys, which should help you differentiate which pages should be redirected. Let’s see how these “has” and “missing” keys work for our **redirects/rewrites** functions.

1.  All not nested paths by slug

```Javascript
{
 	source: '/old-blog/:slug',
    // '/old-blog/overwatch' or '/old-blog/starcraft' will work
    // '/old-blog/diablo/four' won't work
    destination: '/news/:slug',
    permanent: true,
}
```

2. All paths including nested

```Javascript
{
    source: '/old-blog/:slug*',
    // Example: '/old-blog/diablo/four/why/people/like/it'
    destination: '/news/:slug*',
    permanent: true,
},
```

3. All paths that match Regex

The following characters `(, )`, `{, }`, `:`, `*`, `+`, `?` are used for regex path matching, so when used in the `source` as non-special values they must be escaped by adding `\\` before them:
source: `/english\\(default\\)/:slug`

```Javascript
{
    source: '/post/:slug(\\d{1,})',
    // Example: '/post/123' but not '/post/abc'
    
    // For nesting don’t forget to add "*” sign. 
    // Example: '/post/:slug(\\d{1,})*'
    destination: '/news/:slug',
	permanent: true,
},
```

4. “has” and “missing” keys in the object. Also objects with the following fields:  
   - `type`: `String` - must be either `header`, `cookie`, `host`, or `query`.
   - `key`: `String` - the key from the selected type to match against.
   - `value`: `String` or `undefined` - the value to check for, if undefined any value will match. A regex-like string can be used to capture a specific part of the value, e.g. if the value `first-(?<paramName>.*)` is used for the `first-second` then the `second` will be usable in the destination with `paramName`.

```Javascript
module.exports = {
  async redirects() {
    return [
      // if the header `x-redirect-me` is present,
      // this redirect will be applied
      {
        source: '/:path((?!another-page$).*)',
        has: [
          {
            type: 'header',
            key: 'x-redirect-me',
          },
        ],
        permanent: false,
        destination: '/another-page',
      },
      // if the header `x-dont-redirect` is present,
      // this redirect will NOT be applied
      {
        source: '/:path((?!another-page$).*)',
        missing: [
          {
            type: 'header',
            key: 'x-do-not-redirect',
          },
        ],
        permanent: false,
        destination: '/another-page',
      },
      // if the source, query, and cookie are matched,
      // this redirect will be applied
      {
        source: '/specific/:path*',
        has: [
          {
            type: 'query',
            key: 'page',
            // the page value will not be available in the
            // destination since value is provided and doesn't
            // use a named capture group e.g. (?<page>home)
            value: 'home',
          },
          {
            type: 'cookie',
            key: 'authorized',
            value: 'true',
          },
        ],
        permanent: false,
        destination: '/another/:path*',
      },
      // if the header `x-authorized` is present and
      // contains a matching value, this redirect will be applied
      {
        source: '/',
        has: [
          {
            type: 'header',
            key: 'x-authorized',
            value: '(?<authorized>yes|true)',
          },
        ],
        permanent: false,
        destination: '/home?authorized=:authorized',
      },
      // if the host is `example.com`,
      // this redirect will be applied
      {
        source: '/:path((?!another-page$).*)',
        has: [
          {
            type: 'host',
            value: 'example.com',
          },
        ],
        permanent: false,
        destination: '/another-page',
      },
    ]
  },
}
```

5. With “basePath” support. When leveraging [`basePath` support](https://nextjs.org/docs/app/api-reference/next-config-js/basePath) with redirects each `source` and `destination` is automatically prefixed with the `basePath` unless you add `basePath: false` to the redirect:

```Javascript
module.exports = {
  basePath: '/docs',
 
  async redirects() {
    return [
      {
        source: '/with-basePath', // automatically becomes /docs/with-basePath
        destination: '/another', // automatically becomes /docs/another
        permanent: false,
      },
      {
        // does not add /docs since basePath: false is set
        source: '/without-basePath',
        destination: 'https://example.com',
        basePath: false,
        permanent: false,
      },
    ]
  },
}
```

6. With i18n support  
   When leveraging [`i18n` support](https://nextjs.org/docs/app/building-your-application/routing/internationalization) with redirects each `source` and `destination` is automatically prefixed to handle the configured `locales` unless you add `locale: false` to the redirect. If `locale: false` is used you must prefix the `source` and `destination` with a locale for it to be matched correctly.

```Javascript
module.exports = {
  i18n: {
    locales: ['en', 'fr', 'de'],
    defaultLocale: 'en',
  },
 
  async redirects() {
    return [
      {
        source: '/with-locale', // automatically handles all locales
        destination: '/another', // automatically passes the locale on
        permanent: false,
      },
      {
        // does not handle locales automatically since locale: false is set
        source: '/nl/with-locale-manual',
        destination: '/nl/another',
        locale: false,
        permanent: false,
      },
      {
        // this matches '/' since `en` is the defaultLocale
        source: '/en',
        destination: '/en/another',
        locale: false,
        permanent: false,
      },
      // it's possible to match all locales even when locale: false is set
      {
        source: '/:locale/page',
        destination: '/en/newpage',
        permanent: false,
        locale: false,
      },
      {
        // this gets converted to /(en|fr|de)/(.*) so will not match the top-level
        // `/` or `/fr` routes like /:path* would
        source: '/(.*)',
        destination: '/another',
        permanent: false,
      },
    ]
  },
}
```

## Middleware

Middleware allows you to run code before a request is completed. Then, based on the incoming request, you can modify the response by rewriting, redirecting, modifying the request or response headers, or responding directly.  
By convention use the file `middleware.ts` (or `.js`) in the root of your project to define Middleware. For example, at the same level as `pages` or `app`, or inside `src` if applicable.

```Javascript
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
 
// This function can be marked `async` if using `await` inside
export function middleware(request: NextRequest) {
  return NextResponse.redirect(new URL('/home', request.url))
}
 
// See "Matching Paths" below to learn more
export const config = {
  matcher: '/about/:path*',
}
```

### Matching Paths
#### Matcher

`matcher` allows you to filter Middleware to run on specific paths.

```Javascript
export const config = {
  matcher: '/about/:path*',
}
```

You can match a single path or multiple paths with an array syntax:

```Javascript
export const config = {
  matcher: ['/about/:path*', '/blog/:path*'],
}
```

The matcher config allows full regex so matching like negative lookaheads or character matching is supported. An example of a negative lookahead to match all except specific paths can be seen here:

```Javascript
export const config = {
  matcher: [
    /*
     * Match all request paths except for the ones starting with:
     * - api (API routes)
     * - _next/static (static files)
     * - _next/image (image optimization files)
     * - favicon.ico (favicon file)
     */
    '/((?!api|_next/static|_next/image|favicon.ico).*)',
  ],
}
```

Configured matchers:

1. MUST start with `/`
2. Can include named parameters: `/about/:path` matches `/about/a` and `/about/b` but not `/about/a/c`
3. Can have modifiers on named parameters (starting with `:`): `/about/:path*` matches `/about/a/b/c` because `*` is zero or more. `?` is zero or one and `+` one or more
4. Can use regular expression enclosed in parentheses: `/about/(.*)` is the same as `/about/:path*`

Read more details on [path-to-regexp](https://github.com/pillarjs/path-to-regexp#path-to-regexp-1) documentation.

#### Conditional Statements

You can use conditional statements to match paths. For example, you can match all paths except for the ones starting with `/api`:

```Javascript
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
 
export function middleware(request: NextRequest) {
  if (request.nextUrl.pathname.startsWith('/about')) {
    return NextResponse.rewrite(new URL('/about-2', request.url))
  }
 
  if (request.nextUrl.pathname.startsWith('/dashboard')) {
    return NextResponse.rewrite(new URL('/dashboard/user', request.url))
  }
}
```

## getStaticProps, getServerSideProps, and redirects functions

### getStaticProps and getServerSideProps

These functions are used to fetch data at build time and runtime respectively. They are not intended to be used for redirects, but you can use them for that purpose.

```Javascript
export async function getStaticProps(context) {
  const res = await fetch(`https://...`);
  const data = await res.json();

  if (!data) {
    return {
      redirect: {
        destination: "/",
        permanent: false,
      },
    };
  }

  return {
    props: { data },
  };
}
```

Here is an example of how it works for the `getStaticProps` function. As you can see, the only limitation is your imagination and the task description in the dashboard your team is using. For instance, you can empower your content creators with the ability to fill in a list of redirects themselves. Subsequently, you can fetch this list and check if the current page is included.

The same principle applies to `getServerSideProps` with only one difference – where you should use it. If you need dynamic redirects at the user request level, use `getServerSideProps`. If you need redirects at the stage of page generation or revalidation, use `getStaticProps`.

### redirect function for AppRouter

What about the `redirect` function we have in AppRouter? It works similarly, but you need to import the `redirect` function and use it in your page code. Here’s an example:

```Javascript
import { redirect } from "next/navigation";

async function fetchTeam(id) {
  const res = await fetch("https://...");
 // Here you fetch your data
  if (!res.ok) return undefined;
  return res.json();
}

export default async function Profile({ params }) {
  const team = await fetchTeam(params.id);
 // Here you decide using your data is page need redirects
  if (!team) {
    redirect("/login");
  }

  // ...
}
```

As you can see you just need to call this function and pass the destination for the `redirect`.
Yes, that’s simple.

## Conclusion

In conclusion, Next.js provides a variety of methods to handle redirects in your web application. The `next.config.js` file offers `redirects` and `rewrites` functions that can be used to redirect or rewrite URLs based on certain patterns. Middleware provides a way to run code before a request is completed, allowing for dynamic redirects based on the incoming request. The `getStaticProps` and `getServerSideProps` functions, although not primarily intended for redirects, can be used for this purpose when fetching data at build time or runtime respectively. Lastly, the redirect function in AppRouter can be used within your page code to perform redirects. Each method has its own use cases and advantages, and the choice of method depends on the specific needs of your application.