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.
See the Next.js documentation about creating redirects
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 importsimport { 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 importsimport { 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
schemaimport { 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
anddestination
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