next-sanity 9.4.2 → 9.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,147 +1,225 @@
1
1
  # next-sanity<!-- omit in toc -->
2
2
 
3
- The official [Sanity.io][sanity] toolkit for Next.js apps.
4
-
5
- > [!IMPORTANT]
6
- > You're looking at the README for v9, the README for [v8 is available here](https://github.com/sanity-io/next-sanity/tree/v8?tab=readme-ov-file#next-sanity) as well as an [migration guide][migrate-v8-to-v9].
3
+ The all-in-one [Sanity][sanity] toolkit for production-grade content-editable Next.js applications.
7
4
 
8
5
  **Features:**
9
6
 
10
- - The [Sanity Client][sanity-client] fully compatible with [Next.js caching features][next-cache]
11
- - [Live Preview mode][preview-kit]
12
- - [Visual Editing](#visual-editing-with-content-source-maps)
13
- - [GROQ syntax highlighting][groq-syntax-highlighting]
14
- - Embedded Sanity Studio
15
- - Block content with [Portable Text][portable-text]
7
+ - [Sanity Client][sanity-client] for queries and mutations, fully compatible with the [Next.js cache][next-cache]
8
+ - [Visual Editing][visual-editing] for interactive live preview of draft content
9
+ - [Embedded Sanity Studio][sanity-studio], a deeply-configurable content editing dashboard
10
+ - [GROQ][groq-syntax-highlighting] for powerful content querying with type generation and syntax highlighting
11
+ - [Portable Text][portable-text] for rendering rich text and block content
12
+
13
+ **Quicklinks**: [Sanity docs][sanity-docs] | [Next.js docs][next-docs] | [Clean starter template][sanity-next-clean-starter] | [Fully-featured starter template][sanity-next-featured-starter]
16
14
 
17
- ## Table of contents
15
+ ## Table of contents<!-- omit in toc -->
18
16
 
19
- - [Table of contents](#table-of-contents)
20
17
  - [Installation](#installation)
21
- - [Common dependencies](#common-dependencies)
22
- - [Peer dependencies for embedded Sanity Studio](#peer-dependencies-for-embedded-sanity-studio)
23
- - [Usage](#usage)
24
- - [Quick start](#quick-start)
25
- - [App Router Components](#app-router-components)
26
- - [Page Router Components](#page-router-components)
18
+ - [Quick Start](#quick-start)
19
+ - [Manual installation](#manual-installation)
20
+ - [Install `next-sanity`](#install-next-sanity)
21
+ - [Optional: peer dependencies for embedded Sanity Studio](#optional-peer-dependencies-for-embedded-sanity-studio)
22
+ - [Manual configuration](#manual-configuration)
23
+ - [Write GROQ queries](#write-groq-queries)
24
+ - [Generate TypeScript Types](#generate-typescript-types)
25
+ - [Query content from Sanity Content Lake](#query-content-from-sanity-content-lake)
26
+ - [Configuring Sanity Client](#configuring-sanity-client)
27
+ - [Fetching in App Router Components](#fetching-in-app-router-components)
28
+ - [Fetching in Page Router Components](#fetching-in-page-router-components)
27
29
  - [Should `useCdn` be `true` or `false`?](#should-usecdn-be-true-or-false)
28
30
  - [How does `apiVersion` work?](#how-does-apiversionwork)
29
- - [Cache revalidation](#cache-revalidation)
31
+ - [Caching and revalidation](#caching-and-revalidation)
32
+ - [`sanityFetch()` helper function](#sanityfetch-helper-function)
30
33
  - [Time-based revalidation](#time-based-revalidation)
31
- - [Tag-based revalidation webhook](#tag-based-revalidation-webhook)
32
- - [Slug-based revalidation webhook](#slug-based-revalidation-webhook)
33
- - [Working example implementation](#working-example-implementation)
34
+ - [Path-based revalidation](#path-based-revalidation)
35
+ - [Tag-based revalidation](#tag-based-revalidation)
34
36
  - [Debugging caching and revalidation](#debugging-caching-and-revalidation)
35
- - [Preview](#preview)
36
- - [Using Perspectives](#using-perspectives)
37
- - [Live Preview](#live-preview)
38
- - [Using `draftMode()` to de/activate previews](#using-draftmode-to-deactivate-previews)
39
- - [Using `cache` and `revalidation` at the same time](#using-cache-and-revalidation-at-the-same-time)
40
- - [Visual Editing with Content Source Maps](#visual-editing-with-content-source-maps)
37
+ - [Example implementation](#example-implementation)
38
+ - [Visual Editing](#visual-editing)
41
39
  - [Embedded Sanity Studio](#embedded-sanity-studio)
42
- - [Configuring Sanity Studio on a route](#configuring-sanity-studio-on-a-route)
43
- - [Manual installation](#manual-installation)
40
+ - [Creating a Studio route](#creating-a-studio-route)
41
+ - [Automatic installation of embedded Studio](#automatic-installation-of-embedded-studio)
42
+ - [Manual installation of embedded Studio](#manual-installation-of-embedded-studio)
44
43
  - [Studio route with App Router](#studio-route-with-app-router)
45
- - [Lower level control with `StudioProvider` and `StudioLayout`](#lower-level-control-with-studioprovider-and-studiolayout)
44
+ - [Lower-level control with `StudioProvider` and `StudioLayout`](#lower-level-control-with-studioprovider-and-studiolayout)
46
45
  - [Migration guides](#migration-guides)
47
46
  - [License](#license)
48
47
 
49
48
  ## Installation
50
49
 
51
- For basic functionality, run the following command in the package manager of your choice:
50
+ ## Quick Start
51
+
52
+ Instantly create a new free Sanity project – or link to an existing one – from the command line and connect it to your Next.js application by the following terminal command _in your Next.js project folder_:
52
53
 
53
54
  ```bash
54
- npm install next-sanity
55
+ npx sanity@latest init
55
56
  ```
56
57
 
58
+ If you do not yet have a Sanity account you will be prompted to create one. This command will create basic utilities required to query content from Sanity. And optionally embed Sanity Studio - a configurable content management system - at a route in your Next.js application. See the [Embedded Sanity Studio](#embedded-sanity-studio) section.
59
+
60
+ ## Manual installation
61
+
62
+ If you do not yet have a Next.js application, you can create one with the following command:
63
+
57
64
  ```bash
58
- yarn add next-sanity
65
+ npx create-next-app@latest
59
66
  ```
60
67
 
68
+ This README assumes you have chosen all of the default options, but should be fairly similar for most bootstrapped Next.js projects.
69
+
70
+ ### Install `next-sanity`
71
+
72
+ Inside your Next.js application, run the following command in the package manager of your choice to install the next-sanity toolkit:
73
+
61
74
  ```bash
62
- pnpm install next-sanity
75
+ npm install next-sanity @sanity/image-url
63
76
  ```
64
77
 
65
78
  ```bash
66
- bun install next-sanity
79
+ yarn add next-sanity @sanity/image-url
67
80
  ```
68
81
 
69
- ### Common dependencies
70
-
71
- Building with Sanity and Next.js, you‘re likely to want libraries to handle [On-Demand Image Transformations][image-url] and [Visual Editing](https://www.sanity.io/docs/loaders-and-overlays):
72
-
73
82
  ```bash
74
- npm install @sanity/image-url @sanity/react-loader
83
+ pnpm install next-sanity @sanity/image-url
75
84
  ```
76
85
 
77
86
  ```bash
78
- yarn add @sanity/image-url @sanity/react-loader
87
+ bun install next-sanity @sanity/image-url
79
88
  ```
80
89
 
90
+ This also installs `@sanity/image-url` for [On-Demand Image Transformations][image-url] to render images from Sanity's CDN.
91
+
92
+ ### Optional: peer dependencies for embedded Sanity Studio
93
+
94
+ When using `npm` newer than `v7`, or `pnpm` newer than `v8`, you should end up with needed dependencies like `sanity` and `styled-components` when you installed `next-sanity`. In `yarn` `v1` you can use `install-peerdeps`:
95
+
81
96
  ```bash
82
- pnpm install @sanity/image-url @sanity/react-loader
97
+ npx install-peerdeps --yarn next-sanity
83
98
  ```
84
99
 
100
+ ### Manual configuration
101
+
102
+ The `npx sanity@latest init` command offers to write some configuration files for your Next.js application. Most importantly is one that writes your chosen Sanity project ID and dataset name to your local environment variables. Note that unlike access tokens, the project ID and dataset name are **not** considered sensitive information.
103
+
104
+ **Create** this file at the root of your Next.js application if it does not already exist.
105
+
85
106
  ```bash
86
- bun install @sanity/image-url @sanity/react-loader
107
+ # .env.local
108
+
109
+ NEXT_PUBLIC_SANITY_PROJECT_ID=<your-project-id>
110
+ NEXT_PUBLIC_SANITY_DATASET=<your-dataset-name>
111
+ ```
112
+
113
+ **Create** a file to access and export these values
114
+
115
+ ```ts
116
+ // ./src/sanity/env.ts
117
+
118
+ export const dataset = process.env.NEXT_PUBLIC_SANITY_DATASET!
119
+ export const projectId = process.env.NEXT_PUBLIC_SANITY_PROJECT_ID!
120
+
121
+ // Values you may additionally want to configure globally
122
+ export const apiVersion = process.env.NEXT_PUBLIC_SANITY_API_VERSION || '2024-07-11'
123
+ ```
124
+
125
+ Remember to add these environment variables to your hosting provider's environment as well.
126
+
127
+ ### Write GROQ queries
128
+
129
+ `next-sanity` exports the `groq` template literal which will give you syntax highlighting in [VS Code with the Sanity extension installed][vs-code-extension]. It’s also required for GROQ query result type generation with [Sanity TypeGen][sanity-typegen].
130
+
131
+ ```ts
132
+ // ./src/sanity/lib/queries.ts
133
+
134
+ import {groq} from 'next-sanity'
135
+
136
+ export const POSTS_QUERY = groq`*[_type == "post" && defined(slug.current)][0...12]{
137
+ _id, title, slug
138
+ }`
139
+
140
+ export const POST_QUERY = groq`*[_type == "post" && slug.current == $slug][0]{
141
+ title, body, mainImage
142
+ }`
87
143
  ```
88
144
 
89
- ### Peer dependencies for embedded Sanity Studio
145
+ ### Generate TypeScript Types
90
146
 
91
- When using `npm` newer than `v7`, or `pnpm` newer than `v8`, you should end up with needed dependencies like `sanity` and `styled-components` when you `npm install next-sanity`. It also works in `yarn` `v1` using `install-peerdeps`:
147
+ You can use [Sanity TypeGen to generate TypeScript types][sanity-typegen] for your schema types and GROQ query results in your Next.js application. It should be readily available if you have used `sanity init` and chosen the embedded Studio.
148
+
149
+ > [!TIP]
150
+ > Sanity TypeGen will [create Types for queries][sanity-typegen-queries] that are assigned to a variable and use the `groq` template literal.
151
+
152
+ If your Sanity Studio schema types are in a different project or repository, you can [configure Sanity TypeGen to write types to your Next.js project][sanity-typegen-monorepo].
153
+
154
+ **Run** the following command in your terminal to create a `schema.json` file at the root of your project.
92
155
 
93
156
  ```bash
94
- npx install-peerdeps --yarn next-sanity
157
+ # Run this each time your schema types change
158
+ npx sanity@latest schema extract
95
159
  ```
96
160
 
97
- ## Usage
161
+ **Run** the following command in your terminal to generate TypeScript types and create a `sanity.types.ts` file at the root of your project.
98
162
 
99
- There are different ways to integrate Sanity with Next.js depending on your usage and needs for features like Live Preview, tag-based revalidation, and so on. It's possible to start simple and add more functionality as your project progresses.
163
+ ```bash
164
+ # Run this each time your schema types or GROQ queries change
165
+ npx sanity@latest typegen generate
166
+ ```
100
167
 
101
- ### Quick start
168
+ You can also allocate these commands to an npm script in your Next.js project's `package.json`:
169
+
170
+ ```json
171
+ "scripts": {
172
+ "predev": "npm run typegen",
173
+ "dev": "next",
174
+ "prebuild": "npm run typegen",
175
+ "build": "next build",
176
+ "start": "next start",
177
+ "lint": "next lint",
178
+ "typegen": "sanity schema extract && sanity typegen generate"
179
+ },
180
+ ```
181
+
182
+ ## Query content from Sanity Content Lake
183
+
184
+ Sanity content is typically queried with GROQ queries from a configured Sanity Client. [Sanity also supports GraphQL][sanity-graphql].
185
+
186
+ ### Configuring Sanity Client
102
187
 
103
- To start running GROQ queries with `next-sanity`, we recommend creating a `client.ts` file:
188
+ To interact with Sanity content in a Next.js application, we recommend creating a `client.ts` file:
104
189
 
105
190
  ```ts
106
- // ./src/utils/sanity/client.ts
191
+ // ./src/sanity/lib/client.ts
107
192
  import {createClient} from 'next-sanity'
108
193
 
109
- const projectId = process.env.NEXT_PUBLIC_SANITY_PROJECT_ID // "pv8y60vp"
110
- const dataset = process.env.NEXT_PUBLIC_SANITY_DATASET // "production"
111
- const apiVersion = process.env.NEXT_PUBLIC_SANITY_API_VERSION || '2023-05-03'
194
+ import {apiVersion, dataset, projectId} from '../env'
112
195
 
113
196
  export const client = createClient({
114
197
  projectId,
115
198
  dataset,
116
199
  apiVersion, // https://www.sanity.io/docs/api-versioning
117
- useCdn: true, // if you're using ISR or only static generation at build time then you can set this to `false` to guarantee no stale content
200
+ useCdn: true, // Set to false if statically generating pages, using ISR or tag-based revalidation
118
201
  })
119
202
  ```
120
203
 
121
- ### App Router Components
204
+ ### Fetching in App Router Components
122
205
 
123
- To fetch data in a React Server Component using the [App Router][app-router]:
206
+ To fetch data in a React Server Component using the [App Router][app-router] you can await results from the Sanity Client inside a server component:
124
207
 
125
208
  ```tsx
126
209
  // ./src/app/page.tsx
127
- import {client} from '@/src/utils/sanity/client'
128
210
 
129
- type Post = {
130
- _id: string
131
- title?: string
132
- slug?: {
133
- current: string
134
- }
135
- }
211
+ import {client} from '@/sanity/lib/client'
212
+ import {POSTS_QUERY} from '@/sanity/lib/queries'
213
+ import {POSTS_QUERYResult} from '../../sanity.types'
136
214
 
137
- export async function PostIndex() {
138
- const posts = await client.fetch<Post[]>(`*[_type == "post"]`)
215
+ export default async function PostIndex() {
216
+ const posts = await client.fetch<POSTS_QUERYResult>(POSTS_QUERY)
139
217
 
140
218
  return (
141
219
  <ul>
142
220
  {posts.map((post) => (
143
221
  <li key={post._id}>
144
- <a href={post?.slug.current}>{post?.title}</a>
222
+ <a href={`/posts/${post?.slug.current}`}>{post?.title}</a>
145
223
  </li>
146
224
  ))}
147
225
  </ul>
@@ -149,34 +227,29 @@ export async function PostIndex() {
149
227
  }
150
228
  ```
151
229
 
152
- ### Page Router Components
230
+ ### Fetching in Page Router Components
153
231
 
154
- If you're using the [Pages Router][pages-router], then you can do the following from a page component:
232
+ If you're using the [Pages Router][pages-router] you can await results from Sanity Client inside a `getStaticProps` function:
155
233
 
156
234
  ```tsx
157
235
  // ./src/pages/index.tsx
158
- import {client} from '@/src/utils/sanity/client'
159
236
 
160
- type Post = {
161
- _id: string
162
- title?: string
163
- slug?: {
164
- current: string
165
- }
166
- }
237
+ import {client} from '@/sanity/lib/client'
238
+ import {POSTS_QUERY} from '@/sanity/lib/queries'
239
+ import {POSTS_QUERYResult} from '../../sanity.types'
167
240
 
168
241
  export async function getStaticProps() {
169
- return await client.fetch<Post[]>(`*[_type == "post"]`)
170
- }
242
+ const posts = await client.fetch<POSTS_QUERYResult>(POSTS_QUERY)
171
243
 
172
- export async function HomePage(props) {
173
- const {posts} = props
244
+ return {posts}
245
+ }
174
246
 
247
+ export default async function PostIndex({posts}) {
175
248
  return (
176
249
  <ul>
177
250
  {posts.map((post) => (
178
251
  <li key={post._id}>
179
- <a href={post?.slug.current}>{post?.title}</a>
252
+ <a href={`/posts/${post?.slug.current}`}>{post?.title}</a>
180
253
  </li>
181
254
  ))}
182
255
  </ul>
@@ -186,14 +259,14 @@ export async function HomePage(props) {
186
259
 
187
260
  ### Should `useCdn` be `true` or `false`?
188
261
 
189
- You might notice that you have to set the `useCdn` to `true` or `false` in the client configuration. Sanity offers [caching on a CDN for content queries][cdn]. Since Next.js often comes with its own caching, it might not be necessary, but there are some exceptions.
262
+ You might notice that you have to set the `useCdn` to `true` or `false` in the client configuration. Sanity offers [caching on a CDN for queries][cdn]. Since Next.js has its own caching, using the Sanity CDN might not be necessary, but there are some exceptions.
190
263
 
191
- The general rule is that `useCdn` should be `true` when:
264
+ In general, set `useCdn` to `true` when:
192
265
 
193
266
  - Data fetching happens client-side, for example, in a `useEffect` hook or in response to a user interaction where the `client.fetch` call is made in the browser.
194
- - Server-Side Rendered (SSR) data fetching is dynamic and has a high number of unique requests per visitor, for example, a "For You" feed.
267
+ - Server-side rendered (SSR) data fetching is dynamic and has a high number of unique requests per visitor, for example, a "For You" feed.
195
268
 
196
- And it makes sense to set `useCdn` to `false` when:
269
+ Set `useCdn` to `false` when:
197
270
 
198
271
  - Used in a static site generation context, for example, `getStaticProps` or `getStaticPaths`.
199
272
  - Used in an ISR on-demand webhook responder.
@@ -202,152 +275,126 @@ And it makes sense to set `useCdn` to `false` when:
202
275
 
203
276
  ### How does `apiVersion` work?
204
277
 
205
- Sanity uses [date-based API versioning][api-versioning]. The tl;dr is that you can send the implementation date in a YYYY-MM-DD format, and it will automatically fall back on the latest API version of that time. Then, if a breaking change is introduced later, it won't break your application and give you time to test before upgrading (by setting the value to a date past the breaking change).
278
+ Sanity uses [date-based API versioning][api-versioning]. You can configure the date in a `YYYY-MM-DD` format, and it will automatically fall back on the latest API version of that time. Then, if a breaking change is introduced later, it won't break your application and give you time to test before upgrading.
206
279
 
207
- ## Cache revalidation
280
+ ## Caching and revalidation
208
281
 
209
- This toolkit includes the [`@sanity/client`][sanity-client] that fully supports Next.js `fetch` based features, [including the `revalidateTag` API][revalidate-tag]. It‘s _not necessary_ to use the `React.cache` method like with many other third-party SDKs. This gives you tools to ensure great performance while preventing stale content in a way that's native to Next.js.
282
+ This toolkit includes the [`@sanity/client`][sanity-client] which fully supports Next.js `fetch` based features for caching and revalidation. This ensures great performance while preventing stale content in a way that's native to Next.js.
210
283
 
211
- > **Note**
212
- >
213
- > Some hosts (like Vercel) will keep the content cache in a dedicated data layer and not part of the static app bundle, which means that it might not be revalidated from re-deploying the app like it has done earlier. We recommend reading up on [caching behavior in the Next.js docs][next-cache].
284
+ > [!NOTE]
285
+ > Some hosts (like Vercel) will keep the content cache in a dedicated data layer and not part of the static app bundle, which means re-deploying the app will not purge the cache. We recommend reading up on [caching behavior in the Next.js docs][next-cache].
214
286
 
215
- ### Time-based revalidation
287
+ ### `sanityFetch()` helper function
216
288
 
217
- Time-based revalidation is best for less complex cases and where content updates don't need to be immediately available.
289
+ It can be beneficial to set revalidation defaults for all queries. In all of the following examples, a `sanityFetch()` helper function is used for this purpose.
218
290
 
219
- ```tsx
220
- // ./src/app/home/layout.tsx
221
- import { client } from '@/src/utils/sanity/client'
222
- import { PageProps } from '@/src/app/(page)/Page.tsx'
223
-
224
- type HomePageProps = {
225
- _id: string
226
- title?: string
227
- navItems: PageProps[]
228
- }
291
+ While this function is written to accept _both_ Next.js caching options `revalidate` and `tags`, your application should only rely on one. For this reason, if `tags` are supplied, the `revalidate` setting will be set to `false` (cache indefinitely) and you will need to bust the cache for these pages using [`revalidateTag()`](#tag-based-revalidation).
229
292
 
230
- export async function HomeLayout({children}) {
231
- const home = await client.fetch<HomePageProps>(`*[_id == "home"][0]{...,navItems[]->}`,
232
- {},
233
- {next: {
234
- revalidate: 3600 // look for updates to revalidate cache every hour
235
- }}
236
- })
293
+ In short:
237
294
 
238
- return (
239
- <main>
240
- <nav>
241
- <span>{home?.title}</span>
242
- <ul>
243
- {home?.navItems.map(navItem => ({
244
- <li key={navItem._id}><a href={navItem?.slug?.current}>{navItem?.title}</a></li>
245
- }))}
246
- </ul>
247
- </nav>
248
- {children}
249
- </main>
250
- )
251
- }
252
- ```
253
-
254
- ### Tag-based revalidation webhook
255
-
256
- Tag-based or on-demand revalidation gives you more fine-grained and precise control for when to revalidate content. This is great for pulling content from the same source across components and when content freshness is important.
257
-
258
- Below is an example configuration that ensures the client is only bundled server-side and comes with some defaults. It‘s also easier to adapt for Live Preview functionality (see below).
259
-
260
- If you're planning to use `revalidateTag`, then remember to set up the webhook (see code below) as well.
295
+ - Time-based `revalidate` is good enough for most applications.
296
+ - Any page can be automatically purged from the cache using [`revalidatePath()`](#path-based-revalidation).
297
+ - Content-based `tags` will give you more fine-grained control for complex applications.
298
+ - Pages cached by tags must be purged using [`revalidateTag()`](#tag-based-revalidation).
261
299
 
262
300
  ```ts
263
- // ./src/utils/sanity/client.ts
264
- import 'server-only'
301
+ // ./src/sanity/lib/client.ts
265
302
 
266
303
  import {createClient, type QueryParams} from 'next-sanity'
267
304
 
268
- const projectId = process.env.NEXT_PUBLIC_SANITY_PROJECT_ID // "pv8y60vp"
269
- const dataset = process.env.NEXT_PUBLIC_SANITY_DATASET // "production"
270
- const apiVersion = process.env.NEXT_PUBLIC_SANITY_API_VERSION || '2023-05-03'
305
+ import {apiVersion, dataset, projectId} from '../env'
271
306
 
272
- const client = createClient({
307
+ export const client = createClient({
273
308
  projectId,
274
309
  dataset,
275
310
  apiVersion, // https://www.sanity.io/docs/api-versioning
276
- useCdn: false,
311
+ useCdn: true, // Set to false if statically generating pages, using ISR or tag-based revalidation
277
312
  })
278
313
 
279
314
  export async function sanityFetch<QueryResponse>({
280
315
  query,
281
316
  params = {},
282
- tags,
317
+ revalidate = 60, // default revalidation time in seconds
318
+ tags = [],
283
319
  }: {
284
320
  query: string
285
321
  params?: QueryParams
322
+ revalidate?: number | false
286
323
  tags?: string[]
287
324
  }) {
288
325
  return client.fetch<QueryResponse>(query, params, {
289
326
  next: {
290
- //revalidate: 30, // for simple, time-based revalidation
327
+ revalidate: tags.length ? false : revalidate, // for simple, time-based revalidation
291
328
  tags, // for tag-based revalidation
292
329
  },
293
330
  })
294
331
  }
295
332
  ```
296
333
 
297
- Now you can import the `sanityFetch()` function in any component within the `appfolder, and specify for which document types you want it to revalidate:
334
+ Be aware that you can get errors if you use `cache` and `revalidate` configurations for Next.js together. See the [Next.js documentation on revalidation][next-revalidate-docs].
335
+
336
+ ### Time-based revalidation
337
+
338
+ Time-based revalidation is often good enough for the majority of applications.
339
+
340
+ Increase the `revalidate` setting for longer-lived and less frequently modified content.
298
341
 
299
342
  ```tsx
300
- // ./src/app/home/layout.tsx
301
- import { sanityFetch } from '@/src/utils/sanity/client'
302
- import { PageProps } from '@/src/app/(page)/Page.tsx'
303
-
304
- type HomePageProps = {
305
- _id: string
306
- title?: string
307
- navItems: PageProps[]
308
- }
343
+ // ./src/app/pages/index.tsx
309
344
 
310
- export async function HomeLayout({children}) {
311
- // revalidate if there are changes to either the home document or to a page document (since they're referenced to in navItems)
312
- const home = await sanityFetch<HomePageProps>({
313
- query: `*[_id == "home"][0]{...,navItems[]->}`,
314
- tags: ['home', 'page']
315
- })
345
+ import {sanityFetch} from '@/sanity/lib/client'
346
+ import {POSTS_QUERY} from '@/sanity/lib/queries'
347
+ import {POSTS_QUERYResult} from '../../sanity.types'
348
+
349
+ export default async function PostIndex() {
350
+ const posts = await sanityFetch<POSTS_QUERYResult>({
351
+ query: POSTS_QUERY,
352
+ revalidate: 3600, // update cache at most once every hour
353
+ })
316
354
 
317
355
  return (
318
- <main>
319
- <nav>
320
- <span>{home?.title}</span>
321
- <ul>
322
- {home?.navItems.map(navItem => ({
323
- <li key={navItem._id}><a href={navItem?.slug?.current}>{navItem?.title}</a></li>
324
- }))}
325
- </ul>
326
- </nav>
327
- {children}
328
- </main>
356
+ <ul>
357
+ {posts.map((post) => (
358
+ <li key={post._id}>
359
+ <a href={`/posts/${post?.slug.current}`}>{post?.title}</a>
360
+ </li>
361
+ ))}
362
+ </ul>
329
363
  )
330
364
  }
331
365
  ```
332
366
 
333
- In order to get `revalidateTag` to work you need to set up an API route in your Next.js app that handles an incoming request, typically made by [a GROQ-Powered Webhook][groq-webhook].
367
+ ### Path-based revalidation
334
368
 
335
- You can use this [template][webhook-template] to quickly configure the webhook for your Sanity project.
369
+ For on-demand revalidation of individual pages, Next.js has a `revalidatePath()` function. You can create an API route in your Next.js application to execute it, and [a GROQ-powered webhook][groq-webhook] in your Sanity Project to instantly request it when content is created, updated or deleted.
336
370
 
337
- The code example below uses the built-in `parseBody` function to validate that the request comes from your Sanity project (using a shared secret + looking at the request headers). Then it looks at the document type information in the webhook payload and matches that against the revalidation tags in your app:
371
+ **Create** a new environment variable `SANITY_REVALIDATE_SECRET` with a random string that is shared between your Sanity project and your Next.js application. This is considered sensitive and should not be committed to your repository.
372
+
373
+ ```bash
374
+ # .env.local
375
+
376
+ SANITY_REVALIDATE_SECRET=<some-random-string>
377
+ ```
378
+
379
+ **Create** a new API route in your Next.js application
380
+
381
+ The code example below uses the built-in `parseBody` function to validate that the request comes from your Sanity project (using a shared secret and looking at the request headers). Then it looks at the document type information in the webhook payload and matches that against the revalidation tags in your application
338
382
 
339
383
  ```ts
340
- // ./src/app/api/revalidate/route.ts
341
- import {revalidateTag} from 'next/cache'
384
+ // ./src/app/api/revalidate-path/route.ts
385
+
386
+ import {revalidatePath} from 'next/cache'
342
387
  import {type NextRequest, NextResponse} from 'next/server'
343
388
  import {parseBody} from 'next-sanity/webhook'
344
389
 
345
- type WebhookPayload = {
346
- _type: string
347
- }
390
+ type WebhookPayload = {path?: string}
348
391
 
349
392
  export async function POST(req: NextRequest) {
350
393
  try {
394
+ if (!process.env.SANITY_REVALIDATE_SECRET) {
395
+ return new Response('Missing environment variable SANITY_REVALIDATE_SECRET', {status: 500})
396
+ }
397
+
351
398
  const {isValidSignature, body} = await parseBody<WebhookPayload>(
352
399
  req,
353
400
  process.env.SANITY_REVALIDATE_SECRET,
@@ -356,18 +403,14 @@ export async function POST(req: NextRequest) {
356
403
  if (!isValidSignature) {
357
404
  const message = 'Invalid signature'
358
405
  return new Response(JSON.stringify({message, isValidSignature, body}), {status: 401})
359
- }
360
-
361
- if (!body?._type) {
406
+ } else if (!body?.path) {
362
407
  const message = 'Bad Request'
363
408
  return new Response(JSON.stringify({message, body}), {status: 400})
364
409
  }
365
410
 
366
- // If the `_type` is `page`, then all `client.fetch` calls with
367
- // `{next: {tags: ['page']}}` will be revalidated
368
- revalidateTag(body._type)
369
-
370
- return NextResponse.json({body})
411
+ revalidatePath(body.path)
412
+ const message = `Updated route: ${body.path}`
413
+ return NextResponse.json({body, message})
371
414
  } catch (err) {
372
415
  console.error(err)
373
416
  return new Response(err.message, {status: 500})
@@ -375,27 +418,84 @@ export async function POST(req: NextRequest) {
375
418
  }
376
419
  ```
377
420
 
378
- You can choose to match tags based on any field or expression since GROQ-Powered Webhooks allow you to freely define the payload.
421
+ **Create** a new GROQ-powered webhook in your Sanity project.
422
+
423
+ You can [copy this template][webhook-template-revalidate-path] to quickly add the webhook to your Sanity project.
424
+
425
+ The Projection uses [GROQ's `select()` function][groq-functions] to dynamically create paths for nested routes like `/posts/[slug]`, you can extend this example your routes and other document types.
426
+
427
+ ```groq
428
+ {
429
+ "path": select(
430
+ _type == "post" => "/posts/" + slug.current,
431
+ "/" + slug.current
432
+ )
433
+ }
434
+ ```
435
+
436
+ > [!TIP]
437
+ > If you wish to revalidate _all routes_ on demand, create an API route that calls `revalidatePath('/', 'layout')`
438
+
439
+ ### Tag-based revalidation
379
440
 
380
- ### Slug-based revalidation webhook
441
+ Tag-based revalidation is preferable for instances where many pages are affected by a single document being created, updated or deleted.
381
442
 
382
- If you want on-demand revalidation, without using tags, you'll have to do this by targeting the URLs/slugs for the pages you want to revalidate. If you have nested routes, you will need to adopt the logic to accommodate for that. For example, using `_type` to determine the first segment: `/${body?._type}/${body?.slug.current}`.
443
+ For on-demand revalidation of many pages, Next.js has a `revalidateTag()` function. You can create an API route in your Next.js application to execute it, and [a GROQ-powered webhook][groq-webhook] in your Sanity Project to instantly request it when content is created, updated or deleted.
444
+
445
+ ```tsx
446
+ // ./src/app/pages/index.tsx
447
+
448
+ import {sanityFetch} from '@/sanity/lib/client'
449
+ import {POSTS_QUERY} from '@/sanity/lib/queries'
450
+ import {POSTS_QUERYResult} from '../../sanity.types'
451
+
452
+ export default async function PostIndex() {
453
+ const posts = await sanityFetch<POSTS_QUERYResult>({
454
+ query: POSTS_QUERY,
455
+ tags: ['post', 'author'], // revalidate all pages with the tags 'post' and 'author'
456
+ })
457
+
458
+ return (
459
+ <ul>
460
+ {posts.map((post) => (
461
+ <li key={post._id}>
462
+ <a href={`/posts/${post?.slug.current}`}>{post?.title}</a>
463
+ </li>
464
+ ))}
465
+ </ul>
466
+ )
467
+ }
468
+ ```
469
+
470
+ **Create** a new environment variable `SANITY_REVALIDATE_SECRET` with a random string that is shared between your Sanity project and your Next.js application. This is considered sensitive and should not be committed to your repository.
471
+
472
+ ```bash
473
+ # .env.local
474
+
475
+ SANITY_REVALIDATE_SECRET=<some-random-string>
476
+ ```
477
+
478
+ **Create** a new API route in your Next.js application
479
+
480
+ The code example below uses the built-in `parseBody` function to validate that the request comes from your Sanity project (using a shared secret and looking at the request headers). Then it looks at the document type information in the webhook payload and matches that against the revalidation tags in your application
383
481
 
384
482
  ```ts
385
- // ./src/app/api/revalidate/route.ts
386
- import {revalidatePath} from 'next/cache'
483
+ // ./src/app/api/revalidate-tag/route.ts
484
+
485
+ import {revalidateTag} from 'next/cache'
387
486
  import {type NextRequest, NextResponse} from 'next/server'
388
487
  import {parseBody} from 'next-sanity/webhook'
389
488
 
390
489
  type WebhookPayload = {
391
490
  _type: string
392
- slug?: {
393
- current?: string
394
- }
395
491
  }
396
492
 
397
493
  export async function POST(req: NextRequest) {
398
494
  try {
495
+ if (!process.env.SANITY_REVALIDATE_SECRET) {
496
+ return new Response('Missing environment variable SANITY_REVALIDATE_SECRET', {status: 500})
497
+ }
498
+
399
499
  const {isValidSignature, body} = await parseBody<WebhookPayload>(
400
500
  req,
401
501
  process.env.SANITY_REVALIDATE_SECRET,
@@ -404,17 +504,16 @@ export async function POST(req: NextRequest) {
404
504
  if (!isValidSignature) {
405
505
  const message = 'Invalid signature'
406
506
  return new Response(JSON.stringify({message, isValidSignature, body}), {status: 401})
407
- }
408
-
409
- if (!body?._type || !body?.slug?.current) {
507
+ } else if (!body?._type) {
410
508
  const message = 'Bad Request'
411
509
  return new Response(JSON.stringify({message, body}), {status: 400})
412
510
  }
413
511
 
414
- const staleRoute = `/${body.slug.current}`
415
- revalidatePath(staleRoute)
416
- const message = `Updated route: ${staleRoute}`
417
- return NextResponse.json({body, message})
512
+ // If the `_type` is `post`, then all `client.fetch` calls with
513
+ // `{next: {tags: ['post']}}` will be revalidated
514
+ revalidateTag(body._type)
515
+
516
+ return NextResponse.json({body})
418
517
  } catch (err) {
419
518
  console.error(err)
420
519
  return new Response(err.message, {status: 500})
@@ -422,9 +521,9 @@ export async function POST(req: NextRequest) {
422
521
  }
423
522
  ```
424
523
 
425
- ### Working example implementation
524
+ **Create** a new GROQ-powered webhook in your Sanity project.
426
525
 
427
- Check out our [Personal website template][personal-website-template] to see a feature-complete example of how `revalidateTag` is used together with Live Previews.
526
+ You can [copy this template][webhook-template-revalidate-tag] to quickly add the webhook to your Sanity project.
428
527
 
429
528
  ### Debugging caching and revalidation
430
529
 
@@ -441,198 +540,74 @@ module.exports = {
441
540
  }
442
541
  ```
443
542
 
444
- ## Preview
445
-
446
- There are different ways to set up content previews with Sanity and Next.js.
447
-
448
- ### Using Perspectives
449
-
450
- [Perspectives][perspectives-docs] is a feature for Sanity Content Lake that lets you run the same queries but pull the right content variations for any given experience. The default value is `raw`, which means no special filtering is applied, while `published` and `previewDrafts` can be used to optimize for preview and ensure that no draft data leaks into production for authenticated requests.
451
-
452
- ```ts
453
- // ./src/utils/sanity/client.ts
454
- import {createClient} from 'next-sanity'
455
-
456
- const projectId = process.env.NEXT_PUBLIC_SANITY_PROJECT_ID // "pv8y60vp"
457
- const dataset = process.env.NEXT_PUBLIC_SANITY_DATASET // "production"
458
- const apiVersion = process.env.NEXT_PUBLIC_SANITY_API_VERSION || '2023-05-03'
459
- const token = process.env.SECRET_SANITY_VIEW_TOKEN
460
-
461
- const client = createClient({
462
- projectId,
463
- dataset,
464
- apiVersion, // https://www.sanity.io/docs/api-versioning
465
- useCdn: true, // if you're using ISR or only static generation at build time then you can set this to `false` to guarantee no stale content
466
- token,
467
- perspective: 'published', // prevent drafts from leaking through even though requests are authenticated
468
- })
469
- ```
470
-
471
- ### Live Preview
472
-
473
- Live Preview gives you real-time preview across your whole app for your Sanity project members. The Live Preview can be set up to give the preview experience across the whole app. Live Preview works on the data layer and doesn't require specialized components or data attributes. However, it needs a thin component wrapper to load server-side components into client-side, in order to rehydrate on changes.
474
-
475
- Router-specific setup guides for Live Preview:
476
-
477
- - [`app-router`][preivew-app-router]
478
- - [`pages-router`][preview-pages-router]
479
-
480
- Since `next-sanity/preview` is simply re-exporting `LiveQueryProvider` and `useLiveQuery` from [`@sanity/preview-kit`, you'll find advanced usage and comprehensive docs in its README][preview-kit-documentation].
481
- The [same is true][preview-kit-livequery] for `next-sanity/preview/live-query`.
482
-
483
- ### Using `draftMode()` to de/activate previews
484
-
485
- Next.js gives you [a built-in `draftMode` variable][draft-mode] that can activate features like Visual Edit or any preview implementation.
486
-
487
- ```ts
488
- // ./src/utils/sanity/client.ts
489
- import 'server-only'
490
-
491
- import {draftMode} from 'next/headers'
492
- import {createClient, type QueryOptions, type QueryParams} from 'next-sanity'
493
-
494
- const projectId = process.env.NEXT_PUBLIC_SANITY_PROJECT_ID // "pv8y60vp"
495
- const dataset = process.env.NEXT_PUBLIC_SANITY_DATASET // "production"
496
- const apiVersion = process.env.NEXT_PUBLIC_SANITY_API_VERSION || '2023-05-03'
497
-
498
- const client = createClient({
499
- projectId,
500
- dataset,
501
- apiVersion, // https://www.sanity.io/docs/api-versioning
502
- useCdn: false,
503
- })
504
-
505
- // Used by `PreviewProvider`
506
- export const token = process.env.SANITY_API_READ_TOKEN
507
-
508
- export async function sanityFetch<QueryResponse>({
509
- query,
510
- params = {},
511
- tags,
512
- }: {
513
- query: string
514
- params?: QueryParams
515
- tags: string[]
516
- }) {
517
- const isDraftMode = draftMode().isEnabled
518
- if (isDraftMode && !token) {
519
- throw new Error('The `SANITY_API_READ_TOKEN` environment variable is required.')
520
- }
521
-
522
- const REVALIDATE_SKIP_CACHE = 0
523
- const REVALIDATE_CACHE_FOREVER = false
524
-
525
- return client.fetch<QueryResponse>(query, params, {
526
- ...(isDraftMode &&
527
- ({
528
- token: token,
529
- perspective: 'previewDrafts',
530
- } satisfies QueryOptions)),
531
- next: {
532
- revalidate: isDraftMode ? REVALIDATE_SKIP_CACHE : REVALIDATE_CACHE_FOREVER,
533
- tags,
534
- },
535
- })
536
- }
537
- ```
538
-
539
- #### Using `cache` and `revalidation` at the same time
543
+ ### Example implementation
540
544
 
541
- Be aware that you can get errors if you use the `cache` and the `revalidate` configurations for Next.js cache at the same time. Go to [the Next.js docs][next-revalidate-docs] to learn more.
545
+ Check out the [Personal website template][personal-website-template] to see a feature-complete example of how `revalidateTag` is used together with Visual Editing.
542
546
 
543
- ## Visual Editing with Content Source Maps
547
+ ## Visual Editing
544
548
 
545
- > **Note**
546
- >
547
- > [Vercel Visual Editing][visual-editing] is available on [Vercel's Pro and Enterprise plans][vercel-enterprise] and on all Sanity plans.
548
-
549
- The `createClient` method in `next-sanity` supports [visual editing][visual-editing-intro], it supports all the same options as [`@sanity/preview-kit/client`][preview-kit-client]. Add `studioUrl` to your client configuration and it'll automatically show up on Vercel Preview Deployments:
550
-
551
- ```tsx
552
- import {createClient, groq} from 'next-sanity'
549
+ Interactive live previews of draft content are the best way for authors to find and edit content with the least amount of effort and the most confidence to press publish.
553
550
 
554
- const projectId = process.env.NEXT_PUBLIC_SANITY_PROJECT_ID // "pv8y60vp"
555
- const dataset = process.env.NEXT_PUBLIC_SANITY_DATASET // "production"
556
- const apiVersion = process.env.NEXT_PUBLIC_SANITY_API_VERSION // "2023-05-03"
551
+ > [!TIP]
552
+ > Visual Editing is available on all Sanity plans and can be enabled on all hosting environments.
557
553
 
558
- const client = createClient({
559
- projectId,
560
- dataset,
561
- apiVersion, // https://www.sanity.io/docs/api-versioning
562
- useCdn: true, // if you're using ISR or only static generation at build time, then you can set this to `false` to guarantee no stale content
563
- stega: {
564
- enabled: NEXT_PUBLIC_VERCEL_ENV === 'preview', // this can also be controlled in `client.fetch(query, params, {stega: boolean})`
565
- studioUrl: '/studio', // Or: 'https://my-cool-project.sanity.studio'
566
- },
567
- })
568
- ```
554
+ > [!NOTE]
555
+ > Vercel ["Content Link"][vercel-content-link] adds an "edit" button to the Vercel toolbar on preview builds and is available on Vercel Pro and Enterprise plans.
569
556
 
570
- Go to our [setup guide][visual-editing] for a walkthrough on how to customize the experience.
557
+ An end-to-end tutorial of [how to configure Sanity and Next.js for Visual Editing](https://www.sanity.io/guides/nextjs-app-router-live-preview) using the same patterns demonstrated in this README is available on the Sanity Exchange.
571
558
 
572
559
  ## Embedded Sanity Studio
573
560
 
574
- Sanity Studio allows you to embed a near-infinitely configurable content editing interface into any React application. For Next.js, you can embed the Studio on a route (like `/admin`). The Studio will still require authentication and be available only for members of your Sanity project.
561
+ Sanity Studio is a near-infinitely configurable content editing interface that can be embedded into any React application. For Next.js, you can embed the Studio on a route (like `/studio`). The Studio will still require authentication and be available only for members of your Sanity project.
575
562
 
576
- This opens up many possibilities:
563
+ This opens up many possibilities including dynamic configuration of your Sanity Studio based on a network request or user input.
577
564
 
578
- - Any service that hosts Next.js apps can now host your Studio.
579
- - Building previews for your content is easier as your Studio lives in the same environment.
580
- - Use [Data Fetching][next-data-fetching] to configure your Studio.
581
- - Easy setup of [Preview Mode][next-preview-mode].
565
+ > [!WARNING]
566
+ > The convenience of co-locating the Studio with your Next.js application is appealing, but it can also influence your content model to be too website-centric, and potentially make collaboration with other developers more difficult. Consider a standalone or monorepo Studio repository for larger projects and teams.
582
567
 
583
- > [See it live][embedded-studio-demo]
568
+ ### Creating a Studio route
584
569
 
585
- ### Configuring Sanity Studio on a route
570
+ `next-sanity` exports a `<NextStudio />` component to load Sanity's `<Studio />` component wrapped in a Next.js friendly layout. `metadata` specifies the necessary `<meta>` tags for making the Studio adapt to mobile devices and prevents the route from being indexed by search engines.
586
571
 
587
- The `NextStudio` component loads up the `import {Studio} from 'sanity'` component for you and wraps it in a Next-friendly layout. `metadata` specifies the necessary `<meta>` tags for making the Studio adapt to mobile devices, and prevents the route from being indexed by search engines.
572
+ ### Automatic installation of embedded Studio
588
573
 
589
- To quickly scaffold the embedded studio and a Sanity project, you can run the following command in your project folder:
574
+ To quickly connect an existing - or create a new - Sanity project to your Next.js application, run the following command in your terminal. You will be prompted to create a route for the Studio during setup.
590
575
 
591
576
  ```bash
592
577
  npx sanity@latest init
593
578
  ```
594
579
 
595
- ### Manual installation
580
+ ### Manual installation of embedded Studio
596
581
 
597
- Make a file called `sanity.config.ts` (or `.js` for non-TypeScript projects) in the project's root (same place as `next.config.ts`) and copy the example below:
582
+ **Create** a file `sanity.config.ts` in the project's root and copy the example below:
598
583
 
599
584
  ```ts
600
585
  // ./sanity.config.ts
586
+
601
587
  import {defineConfig} from 'sanity'
602
588
  import {structureTool} from 'sanity/structure'
603
589
 
604
- import {schemaTypes} from './src/schema'
605
-
606
590
  const projectId = process.env.NEXT_PUBLIC_SANITY_PROJECT_ID!
607
591
  const dataset = process.env.NEXT_PUBLIC_SANITY_DATASET!
608
592
 
609
593
  export default defineConfig({
610
- basePath: '/admin', // <-- important that `basePath` matches the route you're mounting your studio from
611
-
594
+ basePath: '/studio', // `basePath` must match the route of your Studio
612
595
  projectId,
613
596
  dataset,
614
597
  plugins: [structureTool()],
615
- schema: {
616
- types: schemaTypes,
617
- },
598
+ schema: {types: []},
618
599
  })
619
600
  ```
620
601
 
621
- This example assumes that there is a `src/schema/index.ts` file that exports the schema definitions for Sanity Studio. However, you are free to structure Studio files as you see fit.
622
-
623
- To run Sanity CLI commands, add a `sanity.cli.ts` with the same `projectId` and `dataset` as your `sanity.config.ts` to the project root:
602
+ Optionally, **create** a `sanity.cli.ts` with the same `projectId` and `dataset` as your `sanity.config.ts` to the project root so that you can run `npx sanity <command>` from the terminal inside your Next.js application:
624
603
 
625
604
  ```ts
626
605
  // ./sanity.cli.ts
627
- /* eslint-disable no-process-env */
628
- import {loadEnvConfig} from '@next/env'
629
- import {defineCliConfig} from 'sanity/cli'
630
606
 
631
- const dev = process.env.NODE_ENV !== 'production'
632
- loadEnvConfig(__dirname, dev, {info: () => null, error: console.error})
607
+ import {defineCliConfig} from 'sanity/cli'
633
608
 
634
- const projectId = process.env.NEXT_PUBLIC_SANITY_PROJECT_ID
635
- const dataset = process.env.NEXT_PUBLIC_SANITY_DATASET
609
+ const projectId = process.env.NEXT_PUBLIC_SANITY_PROJECT_ID!
610
+ const dataset = process.env.NEXT_PUBLIC_SANITY_DATASET!
636
611
 
637
612
  export default defineCliConfig({api: {projectId, dataset}})
638
613
  ```
@@ -641,51 +616,38 @@ Now you can run commands like `npx sanity cors add`. Run `npx sanity help` for a
641
616
 
642
617
  ### Studio route with App Router
643
618
 
644
- Even if the rest of your app is using Pages Router, you should still mount the Studio on an App Router route. [Next supports both routers in the same app.](https://nextjs.org/docs/app/building-your-application/upgrading/app-router-migration#migrating-from-pages-to-app)
619
+ Even if the rest of your app is using Pages Router, you can and should mount the Studio on an App Router route. [Next.js supports both routers in the same app.](https://nextjs.org/docs/app/building-your-application/upgrading/app-router-migration#migrating-from-pages-to-app)
620
+
621
+ **Create** a new route to render the Studio, with the default metadata and viewport configuration:
645
622
 
646
623
  ```tsx
647
- // ./src/app/studio/[[...index]]/page.tsx
648
- import {Studio} from './Studio'
624
+ // ./src/app/studio/[[...tool]]/page.tsx
625
+
626
+ import {NextStudio} from 'next-sanity/studio'
627
+ import config from '../../../../sanity.config'
649
628
 
650
- // Ensures the Studio route is statically generated
651
629
  export const dynamic = 'force-static'
652
630
 
653
- // Set the right `viewport`, `robots` and `referer` meta tags
654
631
  export {metadata, viewport} from 'next-sanity/studio'
655
632
 
656
633
  export default function StudioPage() {
657
- return <Studio />
658
- }
659
- ```
660
-
661
- ```tsx
662
- // ./src/app/studio/[[...index]]/Studio.tsx
663
- 'use client'
664
-
665
- import {NextStudio} from 'next-sanity/studio'
666
-
667
- import config from '../../../sanity.config'
668
-
669
- export function Studio() {
670
- // Supports the same props as `import {Studio} from 'sanity'`, `config` is required
671
634
  return <NextStudio config={config} />
672
635
  }
673
636
  ```
674
637
 
675
- How to customize meta tags:
638
+ The default meta tags exported by `next-sanity` can be customized if necessary:
676
639
 
677
640
  ```tsx
678
- // ./src/app/studio/[[...index]]/page.tsx
641
+ // ./src/app/studio/[[...tool]]/page.tsx
642
+
679
643
  import type {Metadata, Viewport} from 'next'
680
644
  import {metadata as studioMetadata, viewport as studioViewport} from 'next-sanity/studio'
681
645
 
682
- import {Studio} from './Studio'
683
-
684
- // Set the right `viewport`, `robots` and `referer` meta tags
646
+ // Set the correct `viewport`, `robots` and `referrer` meta tags
685
647
  export const metadata: Metadata = {
686
648
  ...studioMetadata,
687
649
  // Overrides the title until the Studio is loaded
688
- title: 'Loading Studio',
650
+ title: 'Loading Studio...',
689
651
  }
690
652
 
691
653
  export const viewport: Viewport = {
@@ -695,15 +657,17 @@ export const viewport: Viewport = {
695
657
  }
696
658
 
697
659
  export default function StudioPage() {
698
- return <Studio />
660
+ return <NextStudio config={config} />
699
661
  }
700
662
  ```
701
663
 
702
- ### Lower level control with `StudioProvider` and `StudioLayout`
664
+ ### Lower-level control with `StudioProvider` and `StudioLayout`
703
665
 
704
- If you want to go to a lower level and have more control over the Studio, you can pass `StudioProvider` and `StudioLayout` from `sanity` as `children`:
666
+ If you need even more control over the Studio, you can pass `StudioProvider` and `StudioLayout` from `sanity` as `children`:
705
667
 
706
668
  ```tsx
669
+ // ./src/app/studio/[[...tool]]/page.tsx
670
+
707
671
  'use client'
708
672
 
709
673
  import {NextStudio} from 'next-sanity/studio'
@@ -725,6 +689,9 @@ function StudioPage() {
725
689
 
726
690
  ## Migration guides
727
691
 
692
+ > [!IMPORTANT]
693
+ > You're looking at the README for v9, the README for [v8 is available here](https://github.com/sanity-io/next-sanity/tree/v8?tab=readme-ov-file#next-sanity) as well as an [migration guide][migrate-v8-to-v9].
694
+
728
695
  - [From `v8` to `v9`][migrate-v8-to-v9]
729
696
  - [From `v7` to `v8`][migrate-v7-to-v8]
730
697
  - [From `v6` to `v7`][migrate-v6-to-v7]
@@ -741,12 +708,6 @@ MIT-licensed. See [LICENSE][LICENSE].
741
708
  [api-versioning]: https://www.sanity.io/docs/api-versioning?utm_source=github&utm_medium=readme&utm_campaign=next-sanity
742
709
  [app-router]: https://nextjs.org/docs/app/building-your-application/routing
743
710
  [cdn]: https://www.sanity.io/docs/asset-cdn?utm_source=github&utm_medium=readme&utm_campaign=next-sanity
744
- [ci-workflow]: https://github.com/sanity-io/next-sanity/actions/workflows/ci.yml
745
- [content-source-maps-intro]: https://www.sanity.io/blog/content-source-maps-announce?utm_source=github&utm_medium=readme&utm_campaign=next-sanity
746
- [content-source-maps]: https://www.sanity.io/docs/content-source-maps?utm_source=github&utm_medium=readme&utm_campaign=next-sanity
747
- [draft-mode]: https://nextjs.org/docs/app/building-your-application/configuring/draft-mode
748
- [embedded-studio-demo]: https://next.sanity.build/studio
749
- [enterprise-cta]: https://www.sanity.io/enterprise?utm_source=github&utm_medium=readme&utm_campaign=next-sanity
750
711
  [groq-syntax-highlighting]: https://marketplace.visualstudio.com/items?itemName=sanity-io.vscode-sanity
751
712
  [groq-webhook]: https://www.sanity.io/docs/webhooks?utm_source=github&utm_medium=readme&utm_campaign=next-sanity
752
713
  [image-url]: https://www.sanity.io/docs/presenting-images?utm_source=github&utm_medium=readme&utm_campaign=next-sanity
@@ -759,24 +720,24 @@ MIT-licensed. See [LICENSE][LICENSE].
759
720
  [migrate-v7-to-v8]: https://github.com/sanity-io/next-sanity/blob/main/packages/next-sanity/MIGRATE-v7-to-v8.md
760
721
  [migrate-v8-to-v9]: https://github.com/sanity-io/next-sanity/blob/main/packages/next-sanity/MIGRATE-v8-to-v9.md
761
722
  [next-cache]: https://nextjs.org/docs/app/building-your-application/caching
762
- [next-data-fetching]: https://nextjs.org/docs/basic-features/data-fetching/overview
763
- [next-preview-mode]: https://nextjs.org/docs/advanced-features/preview-mode
723
+ [next-docs]: https://nextjs.org/docs
764
724
  [next-revalidate-docs]: https://nextjs.org/docs/app/api-reference/functions/fetch#optionsnextrevalidate
765
725
  [pages-router]: https://nextjs.org/docs/pages/building-your-application/routing
766
726
  [personal-website-template]: https://github.com/sanity-io/template-nextjs-personal-website
767
- [perspectives-docs]: https://www.sanity.io/docs/perspectives?utm_source=github&utm_medium=readme&utm_campaign=next-sanity
768
727
  [portable-text]: https://portabletext.org
769
- [preivew-app-router]: https://github.com/sanity-io/next-sanity/blob/main/packages/next-sanity/PREVIEW-app-router.md
770
- [preview-kit-client]: https://github.com/sanity-io/preview-kit#sanitypreview-kitclient
771
- [preview-kit-documentation]: https://github.com/sanity-io/preview-kit#sanitypreview-kit-1
772
- [preview-kit-livequery]: https://github.com/sanity-io/preview-kit#using-the-livequery-wrapper-component-instead-of-the-uselivequery-hook
773
- [preview-kit]: https://github.com/sanity-io/preview-kit
774
- [preview-pages-router]: https://github.com/sanity-io/next-sanity/blob/main/packages/next-sanity/PREVIEW-pages-router.md
775
- [revalidate-tag]: https://nextjs.org/docs/app/api-reference/functions/revalidateTag
776
- [sales-cta]: https://www.sanity.io/contact/sales?utm_source=github&utm_medium=readme&utm_campaign=next-sanity
777
728
  [sanity-client]: https://www.sanity.io/docs/js-client?utm_source=github&utm_medium=readme&utm_campaign=next-sanity
778
729
  [sanity]: https://www.sanity.io?utm_source=github&utm_medium=readme&utm_campaign=next-sanity
779
- [visual-editing-intro]: https://www.sanity.io/blog/visual-editing-sanity-vercel?utm_source=github&utm_medium=readme&utm_campaign=next-sanity
780
- [visual-editing]: https://www.sanity.io/docs/vercel-visual-editing?utm_source=github&utm_medium=readme&utm_campaign=next-sanity
781
- [webhook-template]: https://www.sanity.io/manage/webhooks/share?name=Tag-based+Revalidation+Hook+for+Next.js+13%2B&description=%5B%C2%A0%5D+Replace+URL+with+the+production*+URL+for+your+revalidation+handler+in+your+Next.js+app%0A%5B+%5D%C2%A0Insert%2Freplace+the+document+types+you+want+to+be+able+to+make+tags+for+in+the+Filter+array%0A%5B+%5D%C2%A0Make+a+Secret+that+you+also+add+to+your+app%27s+environment+variables+%28SANITY_REVALIDATE_SECRET%29%0A%0A*Or+preview+URL+for+preliminary+testing%0A%0AFor+complete+instructions%2C+see+the+README+on%3A%0Ahttps%3A%2F%2Fgithub.com%2Fsanity-io%2Fnext-sanity&url=https%3A%2F%2FYOUR-PRODUCTION-URL.TLD%2Fapi%2Frevalidate&on=create&on=update&on=delete&filter=_type+in+%5B%22post%22%2C+%22home%22%2C+%22OTHER_DOCUMENT_TYPE%22%5D&projection=%7B_type%7D&httpMethod=POST&apiVersion=v2021-03-25&includeDrafts=&headers=%7B%7D
782
- [vercel-enterprise]: https://vercel.com/docs/accounts/plans/enterprise
730
+ [visual-editing]: https://www.sanity.io/docs/visual-editing?utm_source=github&utm_medium=readme&utm_campaign=next-sanity
731
+ [webhook-template-revalidate-tag]: https://www.sanity.io/manage/webhooks/share?name=Tag-based+Revalidation+Hook+for+Next.js+13+&description=1.+Replace+URL+with+the+preview+or+production+URL+for+your+revalidation+handler+in+your+Next.js+app%0A2.%C2%A0Insert%2Freplace+the+document+types+you+want+to+be+able+to+make+tags+for+in+the+Filter+array%0A3.%C2%A0Make+a+Secret+that+you+also+add+to+your+app%27s+environment+variables+%28SANITY_REVALIDATE_SECRET%29%0A%0AFor+complete+instructions%2C+see+the+README+on%3A%0Ahttps%3A%2F%2Fgithub.com%2Fsanity-io%2Fnext-sanity&url=https%3A%2F%2FYOUR-PRODUCTION-URL.TLD%2Fapi%2Frevalidate-tag&on=create&on=update&on=delete&filter=_type+in+%5B%22post%22%2C+%22home%22%2C+%22OTHER_DOCUMENT_TYPE%22%5D&projection=%7B_type%7D&httpMethod=POST&apiVersion=v2021-03-25&includeDrafts=&headers=%7B%7D
732
+ [webhook-template-revalidate-path]: https://www.sanity.io/manage/webhooks/share?name=Path-based+Revalidation+Hook+for+Next.js&description=1.+Replace+URL+with+the+preview+or+production+URL+for+your+revalidation+handler+in+your+Next.js+app%0A2.%C2%A0Insert%2Freplace+the+document+types+you+want+to+be+able+to+make+tags+for+in+the+Filter+array%0A3.%C2%A0Make+a+Secret+that+you+also+add+to+your+app%27s+environment+variables+%28SANITY_REVALIDATE_SECRET%29%0A%0AFor+complete+instructions%2C+see+the+README+on%3A%0Ahttps%3A%2F%2Fgithub.com%2Fsanity-io%2Fnext-sanity&url=https%3A%2F%2FYOUR-PRODUCTION-URL.TLD%2Fapi%2Frevalidate-path&on=create&on=update&on=delete&filter=_type+in+%5B%22post%22%2C+%22home%22%2C+%22OTHER_DOCUMENT_TYPES%22%5D&projection=%7B%0A++%22path%22%3A+select%28%0A++++_type+%3D%3D+%22post%22+%3D%3E+%22%2Fposts%2F%22+%2B+slug.current%2C%0A++++slug.current%0A++%29%0A%7D&httpMethod=POST&apiVersion=v2021-03-25&includeDrafts=&headers=%7B%7D
733
+ [sanity-typegen]: https://www.sanity.io/docs/sanity-typegen?utm_source=github&utm_medium=readme&utm_campaign=next-sanity
734
+ [sanity-typegen-monorepo]: https://www.sanity.io/docs/sanity-typegen#1a6a147d6737?utm_source=github&utm_medium=readme&utm_campaign=next-sanity
735
+ [sanity-typegen-queries]: https://www.sanity.io/docs/sanity-typegen#c3ef15d8ad39?utm_source=github&utm_medium=readme&utm_campaign=next-sanity
736
+ [sanity-docs]: https://www.sanity.io/docs
737
+ [sanity-graphql]: https://www.sanity.io/docs/graphql?utm_source=github&utm_medium=readme&utm_campaign=next-sanity
738
+ [vs-code-extension]: https://marketplace.visualstudio.com/items?itemName=sanity-io.vscode-sanity
739
+ [sanity-studio]: https://www.sanity.io/docs/sanity-studio?utm_source=github&utm_medium=readme&utm_campaign=next-sanity
740
+ [groq-functions]: https://www.sanity.io/docs/groq-functions?utm_source=github&utm_medium=readme&utm_campaign=next-sanity
741
+ [vercel-content-link]: https://vercel.com/docs/workflow-collaboration/edit-mode#content-link?utm_source=github&utm_medium=readme&utm_campaign=next-sanity
742
+ [sanity-next-clean-starter]: https://www.sanity.io/templates/nextjs-sanity-clean
743
+ [sanity-next-featured-starter]: https://www.sanity.io/templates/personal-website-with-built-in-content-editing