Max Schmitt

February 23 2024

Next.js, Sanity & TypeScript: A Setup That Works

Most approaches for integrating TypeScript with Sanity and Next.js suck. They make you write a ton of boilerplate code, require a script for generating types or just don't come with the correct fields.

In the end, we want to be able to reference a Sanity schema type to use as a React component prop (for example):

Screenshot of VSCode showing TypeScript autocomplete working for a BlogPost component that takes a blogPost prop which is a Sanity document.

In this post I'll show you a setup that works really well!

What you'll get

  • Easily reference types for your Sanity schemas from Next.js via SanityValues['nameOfType']
  • No build step required for generating types
  • Minimal amounts of boilerplate code

Prerequisites

I'm going to assume the following about your project:

  • You're using Next.js and are already using TypeScript in the project
  • The Sanity studio is embedded in the project via next-sanity/studio
  • Your Sanity schemas and config live in the same repository as your Next.js project

Step 1: Install @sanity-typed/types

In your terminal, run:

# Yarn
yarn add @sanity-typed/types
# npm
npm install @sanity-typed/types

The @sanity-typed/types package will do all the heavy lifting for us.

Step 2: Wrap your Sanity config

Make sure your Sanity config is defined as a TypeScript file and wrap it in defineConfig():

sanity.config.ts

import { defineConfig } from '@sanity-typed/types'
const sanityConfig = defineConfig({
basePath: '/studio',
name: 'my-studio',
// ...
})
export default sanityConfig

Step 3: Create the SanityValues type

In either your sanity.config.ts or a new file, create a type that infers your schema values from your wrapped Sanity config (step 2):

TYPESCRIPT

import { InferSchemaValues } from '@sanity-typed/types'
export type SanityValues = InferSchemaValues<typeof sanityConfig>

Step 4: Update your Sanity schema definitions

If you're already using TypeScript to define your Sanity schemas, you might be doing something like this:

schemas/header.ts

import { defineField, defineType } from 'sanity'
export default defineType({
name: 'header',
title: 'Header',
type: 'object',
fields: [
defineField({
name: 'title',
title: 'Title',
type: 'string',
}),
],
})

Now you need to replace the first line with an import from @sanity-typed/types:

schemas/header.ts

// Replace the 'sanity' import with '@sanity-typed/types'
import { defineField, defineType } from '@sanity-typed/types'

Update all your schema definitions accordingly.

Watch out for array members and block fields!

  • If you're defining an array of objects, you need to use defineArrayMember()
  • If you're defining a block field, you need to use as const

schemas/richText.ts

import { defineArrayMember, defineType } from '@sanity-typed/types'
export default defineType({
title: 'Rich text',
name: 'richText',
type: 'array',
of: [
// 👇 Define array members with defineArrayMember()
defineArrayMember({ type: 'image' }),
defineArrayMember({
title: 'Block',
type: 'block',
styles: [
// Blocks require `as const` 👇
{ title: 'Normal', value: 'normal' } as const,
],
}),
],
})

Done! Now use the types in Next.js / React

Example: Sanity schema type as React prop type

If you have a Video component for a Sanity video type, you can use the type from Sanity like this:

components/Video.tsx

import { SanityValues } from '../sanity.config' // Import SanityValues from wherever you defined it (step 3)
export type VideoProps = {
video?: SanityValues['video']
}
function Video({ className, video, ...rest }: VideoProps) {
// ...
}

Example: TypeScript type when fetching with Sanity client

Here's how you use types when fetching data with the Sanity client (and groq):

pages/index.tsx

import { SanityValues } from '../sanity.config' // Import SanityValues from wherever you defined it (step 3)
export const getStaticProps: <GetStaticProps<IndexPageProps>> = async () => {
const query = groq`*[_type == "indexPage"][0] { ... }`
const page = await sanityClient.fetch<SanityValues['indexPage']>(query)
// ...
}

Conclusion

This is a great TypeScript setup for Sanity and Next.js. All the referenced types have the appropriate fields (including _key and _id).

Note that we're not infering types automatically from groq queries. There is a project that tries to do that but it doesn't work well in practice and it's also overkill in my opinion.

When using TypeScript, it's important to keep the types readable and maintainable. If you get lost tying to type absolutely everything to 100% safety, you're going to have a bad time.

I hope this was helpful! If you have any questions or feedback, feel free to reach out to me on Twitter.