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):
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:
# Yarnyarn add @sanity-typed/types# npmnpm 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.