How to implement redirects in your Next.js 14 app

September 12th, 202413 mins read

How to implement redirects in your Next.js 14 app

I recently revamped my website to use Next.js 14 and Tailwind. As part of the upgrade, I decided to change the way my blog post URLs worked. Instead of using /posts for blog posts, I thought, why not switch to a cleaner, simpler /blog?

Great idea, right? Well, not so fast.

Imagine my horror when I realized that all the blog posts that were already indexed on Google now led my visitors to a 404 page. Yep, the dreaded "page not found." Not exactly the kind of first impression I was going for.

That’s when I knew I needed to implement redirects quickly. To save others from this embarrassing situation, I’m going to show you exactly how to do it yourself in Next.js 14.

Redirects in Next.js: The basics

The easiest and most common way to create redirects in Next.js is through the next.config.js file, which should be located at the root of your project. If you don’t have this file, go ahead and create it.

Let’s start with a basic example of how a redirect looks in Next.js. The following code shows how you can set up a simple redirect, where users navigating to /old-route will automatically be sent to /new-route:

/** @type {import('next').NextConfig} */
const nextConfig = {
	// Add your other configurations here, if any

	async redirects() {
		return [
			{
				source: '/old-route',
				destination: '/new-route',
				permanent: true,
			},
		];
	},
};

export default nextConfig;

In this example:

  • source: The original path you want to redirect from.
  • destination: The new path where users will be redirected.
  • permanent: true: This tells search engines that the redirect is permanent (301 redirects), which is great for SEO because it signals that the original URL has been permanently moved.

Now that you understand the basic structure, let's explore more complex scenarios, such as handling dynamic URLs with slugs.

Redirects with path matching

In real-world applications, you might need to redirect URLs with dynamic segments.

For example, if you’re changing the structure of blog post URLs from /posts/:slug to /blog/:slug, you can easily set up a slug-based redirect.

async redirects() {
  return [
    {
      source: '/posts/:slug',
      destination: '/blog/:slug',
      permanent: true,
    },
  ];
},

This method allows you to preserve the individual slugs for each blog post, so users and search engines can easily find the content at its new location.

Handling multiple redirects

In most cases, you’ll need to handle more than one URL change. Adding multiple redirects in Next.js is simple—just list them within the redirects() array:

async redirects() {
  return [
    {
      source: '/posts/:slug',
      destination: '/blog/:slug',
      permanent: true,
    },
    {
      source: '/contents',
      destination: '/content',
      permanent: true,
    },
  ];
},

This way, you can redirect several different paths in one place. It’s clean, effective, and keeps your configurations well-organized.

Redirecting nested routes

Wildcards are extremely useful when you need to redirect multiple paths that share a common structure.

Imagine you have a site section for a product catalog that used to be located under /old-section/. All product categories and individual product pages are nested under this section:

  • /old-section/category1/product1
  • /old-section/category2/product2

Now you’ve restructured your site, moving the entire section to /new-section/. Instead of manually creating a redirect for every individual page, you can use the wildcard :path* to capture all paths under /old-section/ and redirect them to /new-section/, keeping the rest of the URL structure intact.

Here’s how to implement it:

async redirects() {
  return [
    {
      source: '/old-section/:path*',  // The wildcard `*` matches any number of segments
      destination: '/new-section/:path*',
      permanent: true,
    },
  ];
},
  • source: '/old-section/:path*': The :path* wildcard captures everything that follows /old-section/.
  • destination: '/new-section/:path*': The same captured path is used in the destination URL, so /old-section/category1/product1 will automatically redirect to /new-section/category1/product1.

Using regex for fine-grained control

Regex is a powerful tool for controlling redirects when you need more fine-grained control over the URL structure.

With regex, you can match specific patterns in your URLs, such as only redirecting routes that meet certain criteria (like containing only lowercase letters or excluding numbers).

If you have specific URL naming conventions (like only allowing lowercase letters for slugs), you can use regex to match only those valid URLs. In this case, /draft/abc would be redirected to /blog/article, while /draft/abc123 or /draft/ABC would not trigger the redirect.

async redirects() {
  return [
    {
      source: '/draft/:slug(^[a-z]+)',  // Matches only slugs with lowercase letters
      destination: '/blog/article',
      permanent: false,  // Temporary redirect for testing purposes
    },
  ];
},

In the code above:

  • source: '/draft/:slug(^[a-z]+)': The regex ^[a-z]+ ensures that only slugs with lowercase letters are matched.
  • destination: '/blog/article': All matching URLs will redirect to /blog/article.

Handling case-insensitive redirects in Next.js 14

When dealing with URL redirects, a common issue arises from case-sensitive routing. In Next.js, the routing system treats URLs with different cases as distinct, meaning /posts/Example and /posts/example are considered different routes. This can lead to multiple redirects or 404 errors when a user accesses a URL with incorrect capitalization.

To handle this efficiently, you can use a middleware that forces all incoming URLs to lowercase before applying your redirects. This prevents unnecessary multiple redirections and ensures the URL is standardized.

Here’s an example of a middleware you can add to your Next.js 14 project:

import { NextResponse } from 'next/server';

export function middleware(request) {
	const { pathname } = request.nextUrl;

	// Check if the path includes '/posts' and has uppercase letters
	if (pathname.startsWith('/posts') && pathname !== pathname.toLowerCase()) {
		const lowercasePath = pathname.toLowerCase();

		// Redirect to the lowercase path
		return NextResponse.redirect(new URL(lowercasePath, request.url));
	}

	return NextResponse.next();
}

This middleware checks if the incoming URL starts with /posts and contains any uppercase letters. If it does, the middleware converts the entire path to lowercase and redirects the user to that lowercase version. After applying this middleware, your standard redirects (such as /posts/:slug to /blog/:slug) will work without case sensitivity issues.

This approach improves the user experience by avoiding multiple redirects and ensuring consistent URLs.

Using has and missing in Next.js redirects

In Next.js, you can conditionally apply redirects based on whether certain conditions are met. These conditions can include the presence (or absence) of headers, cookies, hosts, or query strings.

You use the has field to specify when a redirect should happen and the missing field to specify when it shouldn’t.

This is not the same as regex matching within the URL path. Instead, this method allows you to create redirects based on other request attributes, such as query strings, headers, or even cookies. Let’s dive into some examples.

Redirect based on query parameters

Sometimes, you only want to apply a redirect when a specific query parameter is present in the URL.

For example, let’s say you launched a new promotional page, and you want to redirect users only if they arrive with the ?ref=oldsite query string.

Here’s how you can implement that:

async redirects() {
  return [
    {
      source: '/promo/:slug*',
      destination: '/new-promo',
      permanent: true,
      has: [
        {
          type: 'query',
          key: 'ref',
          value: 'oldsite',  // Only redirect if ?ref=oldsite is present
        },
      ],
    },
  ];
},

The has field ensures the redirect only occurs when the query string ref=oldsite is present. If the query parameter isn’t there, the redirect won’t happen.

Redirect based on headers

In some cases, you might want to redirect users based on specific HTTP headers.

For example, let’s say you want to redirect users only if a custom header (x-redirect-me) is present in the request:

async redirects() {
  return [
    {
      source: '/:path((?!another-page$).*)',
      destination: '/another-page',
      permanent: false,
      has: [
        {
          type: 'header',
          key: 'x-redirect-me',  // Only apply redirect if this header is present
        },
      ],
    },
  ];
},

This redirect will only apply when that header is present, ensuring the user is sent to /another-page.

Redirect based on cookies

Let’s say you want to redirect users who are authorized (indicated by a cookie) to a special page, while others stay on the current page.

You can use the has field with type: 'cookie' to check for a specific cookie value.

async redirects() {
  return [
    {
      source: '/restricted-area',
      destination: '/login',
      permanent: false,
      has: [
        {
          type: 'cookie',
          key: 'authorized',  // Look for the 'authorized' cookie
          value: 'false',  // Only redirect if the cookie is set to false
        },
      ],
    },
  ];
},

If users attempt to access a restricted area without being authorized (determined by the cookie authorized=false), you can redirect them to a login page.

**Redirect based on hosts You might also want to redirect users based on the host (the domain they’re accessing).

For example, let’s say you manage multiple domains, and you want visitors to example.com to automatically be redirected to /another-page. This setup ensures that only visitors from that specific host will trigger the redirect.

async redirects() {
  return [
    {
      source: '/:path*',
      destination: '/another-page',
      permanent: false,
      has: [
        {
          type: 'host',
          value: 'example.com',  // Only apply redirect if the host is example.com
        },
      ],
    },
  ];
},

Using missing to prevent redirects

While the has field ensures a redirect only happens if certain conditions are met, the missing field prevents a redirect if certain headers, cookies, query strings, or hosts are present.

You might have certain requests (e.g., from a monitoring service) that include a x-do-not-redirect header to bypass any redirects in your app. The missing field allows you to handle these scenarios by preventing the redirect when that header is detected.

async redirects() {
  return [
    {
      source: '/:path((?!another-page$).*)',
      destination: '/another-page',
      permanent: false,
      missing: [
        {
          type: 'header',
          key: 'x-do-not-redirect',  // Don't apply redirect if this header is present
        },
      ],
    },
  ];
},

Combining has and missing for conditional redirects

In some cases, you may need to combine has and missing to apply complex conditional redirects.

For example, you might want to redirect users only if a query parameter exists and a certain cookie is not set:

async redirects() {
  return [
    {
      source: '/promo/:slug*',
      destination: '/new-promo',
      permanent: true,
      has: [
        {
          type: 'query',
          key: 'ref',
          value: 'oldsite',
        },
      ],
      missing: [
        {
          type: 'cookie',
          key: 'authorized',
          value: 'true',
        },
      ],
    },
  ];
},

Base path support in redirects

Sometimes, your Next.js project might have a base path configured. A base path is a prefix added to all URLs in your app, typically used when your site is hosted in a subdirectory (e.g., /content).

Next.js allows you to set up redirects that either include the base path or ignore it. Let’s look at both scenarios.

module.exports = {
	basePath: '/content', // This is the base path for all URLs

	async redirects() {
		return [
			{
				source: '/old-route',
				destination: '/new-route',
				permanent: true,
			},
		];
	},
};

In this configuration above, the base path /content will automatically be applied to the redirect. So, /content/old-route will redirect to /content/new-route. This is useful if your site is hosted in a subdirectory (like https://example.com/content/) and you want all redirects to reflect that.

Ignoring the base path in redirects

Sometimes, you might want to ignore the base path for certain redirects. This can be achieved by setting basePath: false.

The basePath: false tells Next.js to ignore the base path for this particular redirect, so the URLs are redirected without the /content prefix.

module.exports = {
	basePath: '/content',

	async redirects() {
		return [
			{
				source: '/old-path',
				destination: '/new-path',
				permanent: true,
				basePath: false, // Ignore base path for this redirect
			},
		];
	},
};

Conclusion

Redirects are essential to maintaining seamless navigation and preserving your SEO when changing your site’s structure.

In Next.js 14, the redirect configuration allows you to handle everything from simple URL changes to more complex conditional redirects.

Whether you're redirecting based on complex rules or adjusting simple URLs, Next.js 14 gives you all the control you need to handle any redirect scenario efficiently.