# Implementing redirects https://www.sanity.io/learn/course/seo-optimization/implementing-redirects.md 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. Let's go through best practices for implementing redirects with Next.js and Sanity. ## Learning objectives 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 ## Creating the schema 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. 1. See the Next.js [documentation about creating redirects](https://nextjs.org/docs/app/building-your-application/routing/redirecting#redirects-in-nextconfigjs) Below is a simplified document schema, which you'll make much smarter with validation rules later in the lesson. 1. **Create** a new document schema type for redirects ```typescript:src/sanity/schemaTypes/redirectType.ts 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 ```typescript:src/sanity/schemaTypes/index.ts // ...all other imports import { redirectType } from "./redirectType"; export const schema: { types: SchemaTypeDefinition[] } = { types: [ // ...all other schema types redirectType, ], }; ``` 1. **Update** your structure builder configuration to add the redirect document type: ```typescript:src/sanity/structure.ts // add this line S.documentTypeListItem('redirect').title('Redirects') ``` ## Fetching the redirects The redirect documents created in Sanity Studio will need to be queried into our Next.js config file. 1. **Update** `queries.ts` to include a GROQ query for redirect documents ```typescript:src/sanity/lib/queries.ts // ...all other queries export const REDIRECTS_QUERY = defineQuery(` *[_type == "redirect" && isEnabled == true] { source, destination, permanent } `); ``` 1. **Create** a new utility to fetch all redirect documents ```typescript:src/sanity/lib/fetchRedirects.ts 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. ```sh:Terminal pnpm run typegen ``` ### Things to note * 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](https://nextjs.org/docs/app/building-your-application/routing/redirecting#managing-redirects-at-scale-advanced) for more details. ## Add redirects in your Next.js config 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. 1. **Update** your `next.config.ts` file to include redirects ```typescript:next.config.ts // ...other imports import { fetchRedirects } from "@/sanity/lib/fetchRedirects"; const nextConfig: NextConfig = { // ...other config async redirects() { return await fetchRedirects(); }, }; export default nextConfig; ``` ## Validation rules 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. 1. **Update** the `source` field in the `redirectType` schema ```typescript:src/sanity/schemaTypes/redirectType.ts 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 ## Pro tips from experience * 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 * You may need to redeploy your site as new redirects are added or existing redirects are modified Next up, you'll learn how to generate Open Graph images using Tailwind CSS and Vercel edge functions.