CoursesSEO optimized content with Next.jsImplementing redirects
Track
Work-ready Next.js

SEO optimized content with Next.js

Lesson
4

Implementing redirects

Redirects are a critical component of SEO and site maintenance. While they may appear straightforward at first, improper implementation can lead to complex redirect chains and degraded site performance.
Log in to mark your progress for each Lesson and Task

Let's go through best practices for implementing redirects with Next.js and Sanity.

You will create a redirect system that:

  • Is configured with documents in Sanity Studio
  • Can be managed by your content team
  • Won't create a maintenance headache later

Let's start with your redirect schema type first. You want to make this as editor friendly as possible. The goal is to build a non-technical solution that can be managed by your content team, and output to your Next.js configuration.

Below is a simplified document schema, which you'll make much smarter with validation rules later in the lesson.

Create a new document schema type for redirects
import { defineField, defineType } from "sanity";
import { LinkIcon } from "@sanity/icons";
export const redirectType = defineType({
name: "redirect",
title: "Redirect",
type: "document",
icon: LinkIcon,
fields: [
defineField({
name: "source",
type: "string",
}),
defineField({
name: "destination",
type: "string",
}),
defineField({
name: "permanent",
type: "boolean",
initialValue: true,
}),
defineField({
name: "isEnabled",
description: "Toggle this redirect on or off",
type: "boolean",
initialValue: true,
}),
],
});

Don't forget to register it to your Studio schema types

// ...all other imports
import { redirectType } from "./redirectType";
export const schema: { types: SchemaTypeDefinition[] } = {
types: [
// ...all other schema types
redirectType,
],
};

The redirect documents created in Sanity Studio will need to be queried into our Next.js config file.

Update queries.ts to include a GROQ query for redirect documents
// ...all other queries
export const REDIRECTS_QUERY = defineQuery(`
*[_type == "redirect" && isEnabled == true] {
source,
destination,
permanent
}
`);
Create a new utility to fetch all redirect documents
import { client } from "./client";
import { REDIRECTS_QUERY } from "./queries";
export async function fetchRedirects() {
return client.fetch(REDIRECTS_QUERY);
}

Since you've added schema types and a new query to the application, don't forget to generate Types.

npm run typegen
  • Vercel has a limit of 1,024 redirects in Next.js config.
  • For large numbers of redirects (1000+), use a custom middleware solution instead. See Vercel's documentation on managing redirects at scale for more details.

Now we can use Next.js's built-in redirects configuration in next.config.ts. This allows us to define redirects that will be applied at build time. Note that redirects defined in next.config.ts run before any middleware, should you use it in the future.

Update your next.config.ts file to include redirects
// ...other imports
import { fetchRedirects } from "@/sanity/lib/fetchRedirects";
const nextConfig: NextConfig = {
// ...other config
async redirects() {
return await fetchRedirects();
},
};
export default nextConfig;

Validation is critical as invalid redirects can break future builds. Without validation, authors could publish a redirect that prevents your application from deploying—or create a deployment with circular redirects.

  • Source paths must start with /
  • Never create circular redirects like A -> B -> A

Here's the validation logic, yes it's a bit complex but it's worth it to avoid hours of debugging, when your build breaks because of a missing slash.

Update the source field in the redirectType schema
import { defineField, defineType, SanityDocumentLike } from "sanity";
import { LinkIcon } from "@sanity/icons";
function isValidInternalPath(value: string | undefined) {
if (!value) {
return "Value is required";
} else if (!value.startsWith("/")) {
return "Internal paths must start with /";
} else if (/[^a-zA-Z0-9\-_/:]/.test(value)) {
return "Source path contains invalid characters";
} else if (/:[^/]+:/.test(value)) {
return "Parameters can only contain one : directly after /";
} else if (
value.split("/").some((part) => part.includes(":") && !part.startsWith(":"))
) {
return "The : character can only appear directly after /";
}
return true;
}
function isValidUrl(value: string | undefined) {
try {
new URL(value || "");
return true;
} catch {
return "Invalid URL";
}
}
export const redirectType = defineType({
name: "redirect",
title: "Redirect",
type: "document",
icon: LinkIcon,
validation: (Rule) =>
Rule.custom((doc: SanityDocumentLike | undefined) => {
if (doc && doc.source === doc.destination) {
return ["source", "destination"].map((field) => ({
message: "Source and destination cannot be the same",
path: [field],
}));
}
return true;
}),
fields: [
defineField({
name: "source",
type: "string",
validation: (Rule) => Rule.required().custom(isValidInternalPath),
}),
defineField({
name: "destination",
type: "string",
validation: (Rule) =>
Rule.required().custom((value: string | undefined) => {
const urlValidation = isValidUrl(value);
const pathValidation = isValidInternalPath(value);
if (urlValidation === true || pathValidation === true) {
return true;
}
return typeof urlValidation === "boolean"
? urlValidation
: pathValidation;
}),
}),
defineField({
name: "permanent",
description: "Should the redirect be permanent (301) or temporary (302)",
type: "boolean",
initialValue: true,
}),
defineField({
name: "isEnabled",
description: "Toggle this redirect on or off",
type: "boolean",
initialValue: true,
}),
],
});

The additional validation logic now thoroughly checks:

  • If the source is a valid internal path
  • If the destination is a valid URL, or valid internal path
  • If the source and destination values are different
  • Keep an eye on redirect chains, they can cause "too many redirects" errors
  • Clean up old redirects periodically
  • Consider logging redirects if you need to track usage
  • Adjust the cache duration based on how often you update redirects

Next up, you'll learn how to generate Open Graph images using Tailwind CSS and Vercel edge functions.

You have 5 uncompleted tasks in this lesson
0 of 5