next-sanity 12.1.3 → 12.1.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.
Files changed (2) hide show
  1. package/README.md +19 -1114
  2. package/package.json +4 -4
package/README.md CHANGED
@@ -2,55 +2,25 @@
2
2
 
3
3
  The all-in-one [Sanity][sanity] toolkit for production-grade content-editable Next.js applications.
4
4
 
5
- **Features:**
5
+ - [Next.js + Sanity quick start][sanity-next-quickstart]: Get a working Next.js + Sanity project running in minutes, from creating a Sanity project to querying your first content.
6
+ - [`next-sanity` overview][next-sanity-intro]: Explore everything the `next-sanity` package has to offer.
7
+ - [Configure the next-sanity client][sanity-next-client]: Set up the Sanity client with environment variables, CDN caching, and per-request overrides for different fetching contexts.
8
+ - [Query with GROQ][next-queries]: Make type safe queries with GROQ using the included Sanity client.
9
+ - [Visual editing and live preview][app-router-vised]: Enable click-to-edit overlays and real-time content updates in the Presentation Tool using Draft Mode, `defineLive`, and the `<VisualEditing />` component.
10
+ - [Caching and revalidation][sanity-next-caching]: Control content freshness with time-based, tag-based, and path-based revalidation strategies for applications that need fine-grained cache management.
11
+ - [Reference documentation][sanity-reference-docs]: Browse the full `next-sanity` API reference for detailed type signatures and configuration options.
6
12
 
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]
13
+ **Quicklinks**: [Sanity docs][sanity-next-docs] | [Next.js docs][next-docs] | [Clean starter template][sanity-next-clean-starter] | [Fully-featured starter template][sanity-next-featured-starter]
14
14
 
15
15
  ## Table of contents<!-- omit in toc -->
16
16
 
17
- - [Installation](#installation)
18
17
  - [Quick Start](#quick-start)
19
18
  - [Manual installation](#manual-installation)
20
19
  - [Install `next-sanity`](#install-next-sanity)
21
20
  - [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
- - [Using query result types](#using-query-result-types)
26
- - [Query content from Sanity Content Lake](#query-content-from-sanity-content-lake)
27
- - [Configuring Sanity Client](#configuring-sanity-client)
28
- - [Fetching in App Router Components](#fetching-in-app-router-components)
29
- - [Fetching in Page Router Components](#fetching-in-page-router-components)
30
- - [Should `useCdn` be `true` or `false`?](#should-usecdn-be-true-or-false)
31
- - [How does `apiVersion` work?](#how-does-apiversionwork)
32
- - [Caching and revalidation](#caching-and-revalidation)
33
- - [`sanityFetch()` helper function](#sanityfetch-helper-function)
34
- - [Time-based revalidation](#time-based-revalidation)
35
- - [Path-based revalidation](#path-based-revalidation)
36
- - [Tag-based revalidation](#tag-based-revalidation)
37
- - [Debugging caching and revalidation](#debugging-caching-and-revalidation)
38
- - [Example implementation](#example-implementation)
39
- - [Visual Editing](#visual-editing)
40
- - [Live Content API](#live-content-api)
41
- - [Setup](#setup)
42
- - [How does it revalidate and refresh in real time](#how-does-it-revalidate-and-refresh-in-real-time)
43
- - [Embedded Sanity Studio](#embedded-sanity-studio)
44
- - [Creating a Studio route](#creating-a-studio-route)
45
- - [Automatic installation of embedded Studio](#automatic-installation-of-embedded-studio)
46
- - [Manual installation of embedded Studio](#manual-installation-of-embedded-studio)
47
- - [Studio route with App Router](#studio-route-with-app-router)
48
- - [Lower-level control with `StudioProvider` and `StudioLayout`](#lower-level-control-with-studioprovider-and-studiolayout)
49
21
  - [Migration guides](#migration-guides)
50
22
  - [License](#license)
51
23
 
52
- ## Installation
53
-
54
24
  ## Quick Start
55
25
 
56
26
  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_:
@@ -59,7 +29,7 @@ Instantly create a new free Sanity project – or link to an existing one – fr
59
29
  npx sanity@latest init
60
30
  ```
61
31
 
62
- 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.
32
+ If you do not yet have a Sanity account you will be prompted to create one. This command will create the 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-studio] guide.
63
33
 
64
34
  ## Manual installation
65
35
 
@@ -101,1059 +71,10 @@ When using `npm` newer than `v7`, or `pnpm` newer than `v8`, you should end up w
101
71
  npx install-peerdeps --yarn next-sanity
102
72
  ```
103
73
 
104
- ### Manual configuration
105
-
106
- 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.
107
-
108
- **Create** this file at the root of your Next.js application if it does not already exist.
109
-
110
- ```bash
111
- # .env.local
112
-
113
- NEXT_PUBLIC_SANITY_PROJECT_ID=<your-project-id>
114
- NEXT_PUBLIC_SANITY_DATASET=<your-dataset-name>
115
- ```
116
-
117
- **Create** a file to access and export these values
118
-
119
- ```ts
120
- // ./src/sanity/env.ts
121
-
122
- export const dataset = process.env.NEXT_PUBLIC_SANITY_DATASET!
123
- export const projectId = process.env.NEXT_PUBLIC_SANITY_PROJECT_ID!
124
-
125
- // Values you may additionally want to configure globally
126
- export const apiVersion = process.env.NEXT_PUBLIC_SANITY_API_VERSION || '2024-07-11'
127
- ```
128
-
129
- Remember to add these environment variables to your hosting provider's environment as well.
130
-
131
- ### Write GROQ queries
132
-
133
- `next-sanity` exports the `defineQuery` function which will give you syntax highlighting in [VS Code with the Sanity extension installed][vs-code-extension]. It’s also used for GROQ query result type generation with [Sanity TypeGen][sanity-typegen].
134
-
135
- ```ts
136
- // ./src/sanity/lib/queries.ts
137
-
138
- import {defineQuery} from 'next-sanity'
139
-
140
- export const POSTS_QUERY = defineQuery(`*[_type == "post" && defined(slug.current)][0...12]{
141
- _id, title, slug
142
- }`)
143
-
144
- export const POST_QUERY = defineQuery(`*[_type == "post" && slug.current == $slug][0]{
145
- title, body, mainImage
146
- }`)
147
- ```
148
-
149
- ### Generate TypeScript Types
150
-
151
- 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.
152
-
153
- > [!TIP]
154
- > Sanity TypeGen will [create Types for queries][sanity-typegen-queries] that are assigned to a variable and use the `groq` template literal or `defineQuery` function.
155
-
156
- 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].
157
-
158
- **Create** a `sanity-typegen.json` file at the root of your project to configure Sanity TypeGen:
159
-
160
- ```json
161
- // sanity-typegen.json
162
- {
163
- "path": "./src/**/*.{ts,tsx,js,jsx}",
164
- "schema": "./src/sanity/extract.json",
165
- "generates": "./src/sanity/types.ts"
166
- }
167
- ```
168
-
169
- Note: This configuration is strongly opinionated that the generated Types and the schema extraction are both within the `/src/sanity` directory, not the root which is the default. This configuration is complimented by setting the path of the schema extraction in the updated package.json scripts below.
170
-
171
- **Run** the following command in your terminal to extract your Sanity Studio schema to a JSON file
172
-
173
- ```bash
174
- # Run this each time your schema types change
175
- npx sanity@latest schema extract
176
- ```
177
-
178
- **Run** the following command in your terminal to generate TypeScript types for both your Sanity Studio schema and GROQ queries
179
-
180
- ```bash
181
- # Run this each time your schema types or GROQ queries change
182
- npx sanity@latest typegen generate
183
- ```
184
-
185
- **Update** your Next.js project's `package.json` to perform both of these commands by running `npm run typegen`
186
-
187
- ```json
188
- "scripts": {
189
- "predev": "npm run typegen",
190
- "dev": "next",
191
- "prebuild": "npm run typegen",
192
- "build": "next build",
193
- "start": "next start",
194
- "lint": "next lint",
195
- "typegen": "sanity schema extract --path=src/sanity/extract.json && sanity typegen generate"
196
- },
197
- ```
198
-
199
- ### Using query result types
200
-
201
- Sanity TypeGen creates TypeScript types for the results of your GROQ queries, which _can_ be used as generics like this:
202
-
203
- ```ts
204
- import {client} from '@/sanity/lib/client'
205
- import {POSTS_QUERY} from '@/sanity/lib/queries'
206
- import {POSTS_QUERYResult} from '@/sanity/types'
207
-
208
- const posts = await client.fetch<POSTS_QUERYResult>(POSTS_QUERY)
209
- // ^? const post: POST_QUERYResult
210
- ```
211
-
212
- However, it is much simpler to use automatic type inference. So long as your GROQ queries are wrapped in `defineQuery`, the results should be inferred automatically:
213
-
214
- ```ts
215
- import {client} from '@/sanity/lib/client'
216
- import {POSTS_QUERY} from '@/sanity/lib/queries'
217
-
218
- const posts = await client.fetch(POSTS_QUERY)
219
- // ^? const post: POST_QUERYResult
220
- ```
221
-
222
- ## Query content from Sanity Content Lake
223
-
224
- Sanity content is typically queried with GROQ queries from a configured Sanity Client. [Sanity also supports GraphQL][sanity-graphql].
225
-
226
- ### Configuring Sanity Client
227
-
228
- To interact with Sanity content in a Next.js application, we recommend creating a `client.ts` file:
229
-
230
- ```ts
231
- // ./src/sanity/lib/client.ts
232
- import {createClient} from 'next-sanity'
233
-
234
- import {apiVersion, dataset, projectId} from '../env'
235
-
236
- export const client = createClient({
237
- projectId,
238
- dataset,
239
- apiVersion, // https://www.sanity.io/docs/api-versioning
240
- useCdn: true, // Set to false if statically generating pages, using ISR or tag-based revalidation
241
- })
242
- ```
243
-
244
- ### Fetching in App Router Components
245
-
246
- 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:
247
-
248
- ```tsx
249
- // ./src/app/page.tsx
250
-
251
- import {client} from '@/sanity/lib/client'
252
- import {POSTS_QUERY} from '@/sanity/lib/queries'
253
-
254
- export default async function PostIndex() {
255
- const posts = await client.fetch(POSTS_QUERY)
256
-
257
- return (
258
- <ul>
259
- {posts.map((post) => (
260
- <li key={post._id}>
261
- <a href={`/posts/${post?.slug.current}`}>{post?.title}</a>
262
- </li>
263
- ))}
264
- </ul>
265
- )
266
- }
267
- ```
268
-
269
- ### Fetching in Page Router Components
270
-
271
- If you're using the [Pages Router][pages-router] you can await results from Sanity Client inside a `getStaticProps` function:
272
-
273
- ```tsx
274
- // ./src/pages/index.tsx
275
-
276
- import {client} from '@/sanity/lib/client'
277
- import {POSTS_QUERY} from '@/sanity/lib/queries'
278
-
279
- export async function getStaticProps() {
280
- const posts = await client.fetch(POSTS_QUERY)
281
-
282
- return {posts}
283
- }
284
-
285
- export default async function PostIndex({posts}) {
286
- return (
287
- <ul>
288
- {posts.map((post) => (
289
- <li key={post._id}>
290
- <a href={`/posts/${post?.slug.current}`}>{post?.title}</a>
291
- </li>
292
- ))}
293
- </ul>
294
- )
295
- }
296
- ```
297
-
298
- ### Should `useCdn` be `true` or `false`?
299
-
300
- 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.
301
-
302
- In general, set `useCdn` to `true` when:
303
-
304
- - 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.
305
- - Server-side rendered (SSR) data fetching is dynamic and has a high number of unique requests per visitor, for example, a "For You" feed.
306
-
307
- Set `useCdn` to `false` when:
308
-
309
- - Used in a static site generation context, for example, `getStaticProps` or `getStaticPaths`.
310
- - Used in an ISR on-demand webhook responder.
311
- - Good `stale-while-revalidate` caching is in place that keeps API requests on a consistent low, even if traffic to Next.js spikes.
312
- - For Preview or Draft modes as part of an editorial workflow, you need to ensure that the latest content is always fetched.
313
-
314
- ### How does `apiVersion` work?
315
-
316
- 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.
317
-
318
- ## Caching and revalidation
319
-
320
- 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.
321
-
322
- > [!NOTE]
323
- > 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].
324
-
325
- ### `sanityFetch()` helper function
326
-
327
- 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.
328
-
329
- 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).
330
-
331
- In short:
332
-
333
- - Time-based `revalidate` is good enough for most applications.
334
- - Any page can be automatically purged from the cache using [`revalidatePath()`](#path-based-revalidation).
335
- - Content-based `tags` will give you more fine-grained control for complex applications.
336
- - Pages cached by tags must be purged using [`revalidateTag()`](#tag-based-revalidation).
337
-
338
- ```ts
339
- // ./src/sanity/lib/client.ts
340
-
341
- import {createClient, type QueryParams} from 'next-sanity'
342
-
343
- import {apiVersion, dataset, projectId} from '../env'
344
-
345
- export const client = createClient({
346
- projectId,
347
- dataset,
348
- apiVersion, // https://www.sanity.io/docs/api-versioning
349
- useCdn: true, // Set to false if statically generating pages, using ISR or tag-based revalidation
350
- })
351
-
352
- export async function sanityFetch<const QueryString extends string>({
353
- query,
354
- params = {},
355
- revalidate = 60, // default revalidation time in seconds
356
- tags = [],
357
- }: {
358
- query: QueryString
359
- params?: QueryParams
360
- revalidate?: number | false
361
- tags?: string[]
362
- }) {
363
- return client.fetch(query, params, {
364
- cache: 'force-cache', // on next v14 it's force-cache by default, in v15 it has to be set explicitly
365
- next: {
366
- revalidate: tags.length ? false : revalidate, // for simple, time-based revalidation
367
- tags, // for tag-based revalidation
368
- },
369
- })
370
- }
371
- ```
372
-
373
- 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].
374
-
375
- ### Time-based revalidation
376
-
377
- Time-based revalidation is often good enough for the majority of applications.
378
-
379
- Increase the `revalidate` setting for longer-lived and less frequently modified content.
380
-
381
- ```tsx
382
- // ./src/app/pages/index.tsx
383
-
384
- import {sanityFetch} from '@/sanity/lib/client'
385
- import {POSTS_QUERY} from '@/sanity/lib/queries'
386
-
387
- export default async function PostIndex() {
388
- const posts = await sanityFetch({
389
- query: POSTS_QUERY,
390
- revalidate: 3600, // update cache at most once every hour
391
- })
392
-
393
- return (
394
- <ul>
395
- {posts.map((post) => (
396
- <li key={post._id}>
397
- <a href={`/posts/${post?.slug.current}`}>{post?.title}</a>
398
- </li>
399
- ))}
400
- </ul>
401
- )
402
- }
403
- ```
404
-
405
- ### Path-based revalidation
406
-
407
- 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.
408
-
409
- **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.
410
-
411
- ```bash
412
- # .env.local
413
-
414
- SANITY_REVALIDATE_SECRET=<some-random-string>
415
- ```
416
-
417
- **Create** a new API route in your Next.js application
418
-
419
- 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
420
-
421
- ```ts
422
- // ./src/app/api/revalidate-path/route.ts
423
-
424
- import {revalidatePath} from 'next/cache'
425
- import {type NextRequest, NextResponse} from 'next/server'
426
- import {parseBody} from 'next-sanity/webhook'
427
-
428
- type WebhookPayload = {path?: string}
429
-
430
- export async function POST(req: NextRequest) {
431
- try {
432
- if (!process.env.SANITY_REVALIDATE_SECRET) {
433
- return new Response('Missing environment variable SANITY_REVALIDATE_SECRET', {status: 500})
434
- }
435
-
436
- const {isValidSignature, body} = await parseBody<WebhookPayload>(
437
- req,
438
- process.env.SANITY_REVALIDATE_SECRET,
439
- )
440
-
441
- if (!isValidSignature) {
442
- const message = 'Invalid signature'
443
- return new Response(JSON.stringify({message, isValidSignature, body}), {status: 401})
444
- } else if (!body?.path) {
445
- const message = 'Bad Request'
446
- return new Response(JSON.stringify({message, body}), {status: 400})
447
- }
448
-
449
- revalidatePath(body.path)
450
- const message = `Updated route: ${body.path}`
451
- return NextResponse.json({body, message})
452
- } catch (err) {
453
- console.error(err)
454
- return new Response(err.message, {status: 500})
455
- }
456
- }
457
- ```
458
-
459
- **Create** a new GROQ-powered webhook in your Sanity project.
460
-
461
- You can [copy this template][webhook-template-revalidate-path] to quickly add the webhook to your Sanity project.
462
-
463
- 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.
464
-
465
- ```groq
466
- {
467
- "path": select(
468
- _type == "post" => "/posts/" + slug.current,
469
- "/" + slug.current
470
- )
471
- }
472
- ```
473
-
474
- > [!TIP]
475
- > If you wish to revalidate _all routes_ on demand, create an API route that calls `revalidatePath('/', 'layout')`
476
-
477
- ### Tag-based revalidation
478
-
479
- Tag-based revalidation is preferable for instances where many pages are affected by a single document being created, updated or deleted.
480
-
481
- 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.
482
-
483
- ```tsx
484
- // ./src/app/pages/index.tsx
485
-
486
- import {sanityFetch} from '@/sanity/lib/client'
487
- import {POSTS_QUERY} from '@/sanity/lib/queries'
488
-
489
- export default async function PostIndex() {
490
- const posts = await sanityFetch({
491
- query: POSTS_QUERY,
492
- tags: ['post', 'author'], // revalidate all pages with the tags 'post' and 'author'
493
- })
494
-
495
- return (
496
- <ul>
497
- {posts.map((post) => (
498
- <li key={post._id}>
499
- <a href={`/posts/${post?.slug.current}`}>{post?.title}</a>
500
- </li>
501
- ))}
502
- </ul>
503
- )
504
- }
505
- ```
506
-
507
- **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.
508
-
509
- ```bash
510
- # .env.local
511
-
512
- SANITY_REVALIDATE_SECRET=<some-random-string>
513
- ```
514
-
515
- **Create** a new API route in your Next.js application
516
-
517
- 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
518
-
519
- ```ts
520
- // ./src/app/api/revalidate-tag/route.ts
521
-
522
- import {revalidateTag} from 'next/cache'
523
- import {type NextRequest, NextResponse} from 'next/server'
524
- import {parseBody} from 'next-sanity/webhook'
525
-
526
- type WebhookPayload = {
527
- _type: string
528
- }
529
-
530
- export async function POST(req: NextRequest) {
531
- try {
532
- if (!process.env.SANITY_REVALIDATE_SECRET) {
533
- return new Response('Missing environment variable SANITY_REVALIDATE_SECRET', {status: 500})
534
- }
535
-
536
- const {isValidSignature, body} = await parseBody<WebhookPayload>(
537
- req,
538
- process.env.SANITY_REVALIDATE_SECRET,
539
- )
540
-
541
- if (!isValidSignature) {
542
- const message = 'Invalid signature'
543
- return new Response(JSON.stringify({message, isValidSignature, body}), {status: 401})
544
- } else if (!body?._type) {
545
- const message = 'Bad Request'
546
- return new Response(JSON.stringify({message, body}), {status: 400})
547
- }
548
-
549
- // If the `_type` is `post`, then all `client.fetch` calls with
550
- // `{next: {tags: ['post']}}` will be revalidated
551
- revalidateTag(body._type)
552
-
553
- return NextResponse.json({body})
554
- } catch (err) {
555
- console.error(err)
556
- return new Response(err.message, {status: 500})
557
- }
558
- }
559
- ```
560
-
561
- **Create** a new GROQ-powered webhook in your Sanity project.
562
-
563
- You can [copy this template][webhook-template-revalidate-tag] to quickly add the webhook to your Sanity project.
564
-
565
- ### Debugging caching and revalidation
566
-
567
- To aid in debugging and understanding what's in the cache, revalidated, skipped, and more, add the following to your Next.js configuration file:
568
-
569
- ```js
570
- // ./next.config.js
571
- module.exports = {
572
- logging: {
573
- fetches: {
574
- fullUrl: true,
575
- },
576
- },
577
- }
578
- ```
579
-
580
- ### Example implementation
581
-
582
- Check out the [Personal website template][personal-website-template] to see a feature-complete example of how `revalidateTag` is used together with Visual Editing.
583
-
584
- ## Visual Editing
585
-
586
- 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.
587
-
588
- > [!TIP]
589
- > Visual Editing is available on all Sanity plans and can be enabled on all hosting environments.
590
-
591
- > [!NOTE]
592
- > 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.
593
-
594
- 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.
595
-
596
- ## Live Content API
597
-
598
- [The Live Content API][live-content-api] can be used to receive real time updates in your application when viewing both draft content in contexts like Presentation tool, and published content in your user-facing production application.
599
-
600
- ### Setup
601
-
602
- #### 1. Configure `defineLive`
603
-
604
- Use `defineLive` to enable automatic revalidation and refreshing of your fetched content.
605
-
606
- ```tsx
607
- // src/sanity/lib/live.ts
608
-
609
- import {createClient} from 'next-sanity'
610
- import {defineLive} from 'next-sanity/live'
611
-
612
- const client = createClient({
613
- projectId: process.env.NEXT_PUBLIC_SANITY_PROJECT_ID,
614
- dataset: process.env.NEXT_PUBLIC_SANITY_DATASET,
615
- useCdn: true,
616
- apiVersion: 'v2025-03-04',
617
- stega: {studioUrl: '/studio'},
618
- })
619
-
620
- const token = process.env.SANITY_API_READ_TOKEN
621
- if (!token) {
622
- throw new Error('Missing SANITY_API_READ_TOKEN')
623
- }
624
-
625
- export const {sanityFetch, SanityLive} = defineLive({
626
- client,
627
- serverToken: token,
628
- browserToken: token,
629
- })
630
- ```
631
-
632
- The `token` passed to `defineLive` needs [Viewer rights](https://www.sanity.io/docs/roles#e2daad192df9) in order to fetch draft content.
633
-
634
- The same token can be used as both `browserToken` and `serverToken`, as the `browserToken` is only shared with the browser when Draft Mode is enabled. Draft Mode can only be initiated by either Sanity's Presentation Tool or the Vercel Toolbar.
635
-
636
- > Good to know:
637
- > Enterprise plans allow the creation of custom roles with more restricted access rights than the `Viewer` role, enabling the use of a `browserToken` specifically for authenticating the Live Content API. We're working to extend this capability to all Sanity price plans.
638
-
639
- #### 2. Render `<SanityLive />` in the root `layout.tsx`
640
-
641
- ```tsx
642
- // src/app/layout.tsx
643
-
644
- import {VisualEditing} from 'next-sanity/visual-editing'
645
- import {SanityLive} from '@/sanity/lib/live'
646
-
647
- export default function RootLayout({children}: {children: React.ReactNode}) {
648
- return (
649
- <html lang="en">
650
- <body>
651
- {children}
652
- <SanityLive />
653
- {(await draftMode()).isEnabled && <VisualEditing />}
654
- </body>
655
- </html>
656
- )
657
- }
658
- ```
659
-
660
- The `<SanityLive>` component is responsible for making all `sanityFetch` calls in your application _live_, so should always be rendered. This differs from the `<VisualEditing />` component, which should only be rendered when Draft Mode is enabled.
661
-
662
- #### 3. Fetching data with `sanityFetch`
663
-
664
- Use `sanityFetch` to fetch data in any server component.
665
-
666
- ```tsx
667
- // src/app/products.tsx
668
-
669
- import {defineQuery} from 'next-sanity'
670
- import {sanityFetch} from '@/sanity/lib/live'
671
-
672
- const PRODUCTS_QUERY = defineQuery(`*[_type == "product" && defined(slug.current)][0...$limit]`)
673
-
674
- export default async function Page() {
675
- const {data: products} = await sanityFetch({
676
- query: PRODUCTS_QUERY,
677
- params: {limit: 10},
678
- })
679
-
680
- return (
681
- <section>
682
- {products.map((product) => (
683
- <article key={product._id}>
684
- <a href={`/product/${product.slug}`}>{product.title}</a>
685
- </article>
686
- ))}
687
- </section>
688
- )
689
- }
690
- ```
691
-
692
- ### Using `generateMetadata`, `generateStaticParams` and more
693
-
694
- `sanityFetch` can also be used in functions like `generateMetadata` in order to make updating the page title, or even its favicon, _live_.
695
-
696
- ```ts
697
- import {sanityFetch} from '@/sanity/lib/live'
698
- import type {Metadata} from 'next'
699
-
700
- export async function generateMetadata(): Promise<Metadata> {
701
- const {data} = await sanityFetch({
702
- query: SETTINGS_QUERY,
703
- // Metadata should never contain stega
704
- stega: false,
705
- })
706
- return {
707
- title: {
708
- template: `%s | ${data.title}`,
709
- default: data.title,
710
- },
711
- }
712
- }
713
- ```
714
-
715
- > Good to know:
716
- > Always set `stega: false` when calling `sanityFetch` within these:
717
- >
718
- > - `generateMetadata`
719
- > - `generateViewport`
720
- > - `generateSitemaps`
721
- > - `generateImageMetadata`
722
-
723
- ```ts
724
- import {sanityFetch} from '@/sanity/lib/live'
725
-
726
- export async function generateStaticParams() {
727
- const {data} = await sanityFetch({
728
- query: POST_SLUGS_QUERY,
729
- // Use the published perspective in generateStaticParams
730
- perspective: 'published',
731
- stega: false,
732
- })
733
- return data
734
- }
735
- ```
736
-
737
- ### 4. Integrating with Next.js Draft Mode and Vercel Toolbar's Edit Mode
738
-
739
- To support previewing draft content when Draft Mode is enabled, the `serverToken` passed to `defineLive` should be assigned the Viewer role, which has the ability to fetch content using the `drafts` perspective.
740
-
741
- Click the Draft Mode button in the Vercel toolbar to enable draft content:
742
-
743
- ![image](https://github.com/user-attachments/assets/5aa3ed30-929e-48f1-a16c-8246309ec099)
744
-
745
- With drafts enabled, you'll see the Edit Mode button show up if your Vercel plan is eligible:
746
-
747
- ![img](https://github.com/user-attachments/assets/6ca7a9f5-e2d1-4915-83d0-8928a0a563de)
748
-
749
- Ensure that `browserToken` is setup if you want draft content that isn't yet published to also update live.
750
-
751
- ### 5. Integrating with Sanity Presentation Tool & Visual Editing
752
-
753
- The `defineLive` API also supports Presentation Tool and Sanity Visual Editing.
754
-
755
- Setup an API route that uses `defineEnableDraftMode` in your app:
756
-
757
- ```ts
758
- // src/app/api/draft-mode/enable/route.ts
759
-
760
- import {client} from '@/sanity/lib/client'
761
- import {token} from '@/sanity/lib/token'
762
- import {defineEnableDraftMode} from 'next-sanity/draft-mode'
763
-
764
- export const {GET} = defineEnableDraftMode({
765
- client: client.withConfig({token}),
766
- })
767
- ```
768
-
769
- The main benefit of `defineEnableDraftMode` is that it fully implements all of Sanity Presentation Tool's features, including the perspective switcher:
770
-
771
- <img width="530" alt="image" src="https://github.com/user-attachments/assets/774d8f92-527f-4478-8089-2fb7e6a5c618">
772
-
773
- And the Preview URL Sharing feature:
774
-
775
- <img width="450" alt="image" src="https://github.com/user-attachments/assets/d11b38eb-389b-448f-862c-b39b3adbb7e3">
776
-
777
- In your `sanity.config.ts`, set the `previewMode.enable` option for `presentationTool`:
778
-
779
- ```ts
780
- // sanity.config.ts
781
- 'use client'
782
-
783
- import {defineConfig} from 'sanity'
784
- import {presentationTool} from 'next-sanity'
785
-
786
- export default defineConfig({
787
- // ...
788
- plugins: [
789
- // ...
790
- presentationTool({
791
- previewUrl: {
792
- // ...
793
- previewMode: {
794
- enable: '/api/draft-mode/enable',
795
- },
796
- },
797
- }),
798
- ],
799
- })
800
- ```
801
-
802
- Ensuring you have a valid viewer token setup for `defineLive.serverToken` and `defineEnableDraftMode` allows Presentation Tool to auto enable Draft Mode, and your application to pull in draft content that refreshes in real time.
803
-
804
- The `defineLive.browserToken` option isn't required, but is recommended as it enables a faster live preview experience, both standalone and when using Presentation Tool.
805
-
806
- ### 6. Enabling standalone live preview of draft content
807
-
808
- Standalone live preview has the following requirements:
809
-
810
- - `defineLive.serverToken` must be defined, otherwise only published content is fetched.
811
- - At least one integration (Sanity Presentation Tool or Vercel Toolbar) must be setup, so Draft Mode can be enabled in your application on demand.
812
- - `defineLive.browserToken` must be defined with a valid token.
813
-
814
- You can verify if live preview is enabled with the `useIsLivePreview` hook:
815
-
816
- ```tsx
817
- 'use client'
818
-
819
- import {useIsLivePreview} from 'next-sanity/hooks'
820
-
821
- export function DebugLivePreview() {
822
- const isLivePreview = useIsLivePreview()
823
- if (isLivePreview === null) return 'Checking Live Preview...'
824
- return isLivePreview ? 'Live Preview Enabled' : 'Live Preview Disabled'
825
- }
826
- ```
827
-
828
- The following hooks can also be used to provide information about the application's current environment:
829
-
830
- ```ts
831
- import {
832
- useIsPresentationTool,
833
- useDraftModeEnvironment,
834
- useDraftModePerspective,
835
- useVisualEditingEnvironment,
836
- } from 'next-sanity/hooks'
837
- ```
838
-
839
- ### Handling Layout Shift
840
-
841
- Live components will re-render automatically as content changes. This can cause jarring layout shifts in production when items appear or disappear from a list.
842
-
843
- To provide a better user experience, we can animate these layout changes. The following example uses `framer-motion@12.0.0-alpha.1`, which supports React Server Components:
844
-
845
- ```tsx
846
- // src/app/products.tsx
847
-
848
- import {AnimatePresence} from 'framer-motion'
849
- import * as motion from 'framer-motion/client'
850
- import {defineQuery} from 'next-sanity'
851
- import {sanityFetch} from '@/sanity/lib/live'
852
-
853
- const PRODUCTS_QUERY = defineQuery(`*[_type == "product" && defined(slug.current)][0...$limit]`)
854
-
855
- export default async function Page() {
856
- const {data: products} = await sanityFetch({
857
- query: PRODUCTS_QUERY,
858
- params: {limit: 10},
859
- })
860
-
861
- return (
862
- <section>
863
- <AnimatePresence mode="popLayout">
864
- {products.map((product) => (
865
- <motion.article
866
- key={product._id}
867
- layout="position"
868
- animate={{opacity: 1}}
869
- exit={{opacity: 0}}
870
- >
871
- <a href={`/product/${product.slug}`}>{product.title}</a>
872
- </motion.article>
873
- ))}
874
- </AnimatePresence>
875
- </section>
876
- )
877
- }
878
- ```
879
-
880
- Whilst this is an improvement, it may still lead to users attempting to click on an item as it shifts position, potentially resulting in the selection of an unintended item. We can instead require users to opt-in to changes before a layout update is triggered.
881
-
882
- To preserve the ability to render everything on the server, we can make use of a Client Component wrapper. This can defer showing changes to the user until they've explicitly clicked to "Refresh". The example below uses `sonner` to provide toast functionality:
883
-
884
- ```tsx
885
- // src/app/products/products-layout-shift.tsx
886
-
887
- 'use client'
888
-
889
- import {useCallback, useState, useEffect} from 'react'
890
- import isEqual from 'react-fast-compare'
891
- import {toast} from 'sonner'
892
-
893
- export function ProductsLayoutShift(props: {children: React.ReactNode; ids: string[]}) {
894
- const [children, pending, startViewTransition] = useDeferredLayoutShift(props.children, props.ids)
895
-
896
- /**
897
- * We need to suspend layout shift for user opt-in.
898
- */
899
- useEffect(() => {
900
- if (!pending) return
901
-
902
- toast('Products have been updated', {
903
- action: {
904
- label: 'Refresh',
905
- onClick: () => startViewTransition(),
906
- },
907
- })
908
- }, [pending, startViewTransition])
909
-
910
- return children
911
- }
912
-
913
- function useDeferredLayoutShift(children: React.ReactNode, dependencies: unknown[]) {
914
- const [pending, setPending] = useState(false)
915
- const [currentChildren, setCurrentChildren] = useState(children)
916
- const [currentDependencies, setCurrentDependencies] = useState(dependencies)
917
-
918
- if (!pending) {
919
- if (isEqual(currentDependencies, dependencies)) {
920
- if (currentChildren !== children) {
921
- setCurrentChildren(children)
922
- }
923
- } else {
924
- setCurrentDependencies(dependencies)
925
- setPending(true)
926
- }
927
- }
928
-
929
- const startViewTransition = useCallback(() => {
930
- setCurrentDependencies(dependencies)
931
- setPending(false)
932
- }, [dependencies])
933
-
934
- return [pending ? currentChildren : children, pending, startViewTransition] as const
935
- }
936
- ```
937
-
938
- This Client Component is used to wrap the layout that should only be updated after the user has clicked the refresh button:
939
-
940
- ```diff
941
- // src/app/products/page.tsx
942
-
943
- import { AnimatePresence } from "framer-motion";
944
- import * as motion from "framer-motion/client";
945
- import {defineQuery} from 'next-sanity'
946
- import { sanityFetch } from "@/sanity/lib/live";
947
- +import {ProductsLayoutShift} from './products-page-layout-shift.tsx'
948
-
949
- const PRODUCTS_QUERY = defineQuery(`*[_type == "product" && defined(slug.current)][0...$limit]`)
950
-
951
- export default async function Page() {
952
- const {data: products} = await sanityFetch({ query: PRODUCTS_QUERY, params: {limit: 10} });
953
- + // If the list over ids change, it'll trigger the toast asking the user to opt-in to refresh
954
- + // but if a product title has changed, perhaps to fix a typo, we update that right away
955
- + const ids = products.map((product) => product._id)
956
- return (
957
- <section>
958
- + <ProductsLayoutShift ids={ids}>
959
- <AnimatePresence mode="popLayout">
960
- {products.map((product) => (
961
- <motion.article
962
- key={product._id}
963
- layout="position"
964
- animate={{ opacity: 1 }}
965
- exit={{ opacity: 0 }}
966
- >
967
- <a href={`/product/${product.slug}`}>{product.title}</a>
968
- </motion.article>
969
- ))}
970
- </AnimatePresence>
971
- + </ProductsLayoutShift>
972
- </section>
973
- );
974
- }
975
- ```
976
-
977
- With this approach we've limited the use of client components to just a single component. All the server components within `<ProductsLayoutShift>` remain as server components, with all their benefits.
978
-
979
- ## How does the Live Content API revalidate and refresh in real-time?
980
-
981
- The architecture for `defineLive` works as follows:
982
-
983
- 1. `sanityFetch` automatically sets `fetch.next.tags` for you using opaque tags generated by our backend, prefixed with `sanity:`.
984
- 2. `<SanityLive />` listens to change events using the Sanity Live Content API (LCAPI).
985
- 3. When the LCAPI emits an event, `<SanityLive />` invokes a Server Function that calls `revalidateTag(`sanity:${tag}`)`.
986
- 4. Since it's a Server Function, Next.js will evict data fetches associated with the revalidated tag. The page is seamlessly updated with fresh content, which future visitors will also see thanks to `revalidateTag` integrating with ISR.
987
-
988
- With this setup, as long as one visitor accesses your Next.js app after a content change, the cache is updated globally for all users, regardless of the specific URL they visit.
989
-
990
- ### Revalidating content changes from automations
991
-
992
- If your content operations involve scenarios where you might not always have a visitor to trigger the `revalidateTag` event, there are two ways to ensure your content is never stale:
993
-
994
- #### A) Use a GROQ powered webhook to call `revalidateTag(sanity)`
995
-
996
- All queries made using `sanityFetch` include the `sanity` tag in their `fetch.next.tags` array. You can use this to call `revalidateTag('sanity')` in an API route that handles a GROQ webhook payload.
997
-
998
- This approach can be considered a "heavy hammer" so it's important to limit the webhook events that trigger it. You could also implement this in a custom component to manually purge the cache if content gets stuck.
999
-
1000
- #### B) Setup a server-side `<SanityLive />` alternative
1001
-
1002
- You can setup your own long-running server, using Express for example, to listen for change events using the Sanity Live Content API. Then, create an API route in your Next.js app:
1003
-
1004
- ```ts
1005
- // src/app/api/revalidate-tag/route.ts
1006
- import {revalidateTag} from 'next/cache'
1007
-
1008
- export const POST = async (request) => {
1009
- const {tags, isValid} = await validateRequest(request)
1010
- if (!isValid) return new Response('No no no', {status: 400})
1011
- for (const _tag of tags) {
1012
- const tag = `sanity:${_tag}`
1013
- revalidateTag(tag)
1014
- console.log(`revalidated tag: ${tag}`)
1015
- }
1016
- }
1017
- ```
1018
-
1019
- Your Express app can then forward change events to this endpoint, ensuring your content is always up-to-date. This method guarantees that stale content is never served, even if no browser is actively viewing your app!
1020
-
1021
- ## Embedded Sanity Studio
1022
-
1023
- 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.
1024
-
1025
- This opens up many possibilities including dynamic configuration of your Sanity Studio based on a network request or user input.
1026
-
1027
- > [!WARNING]
1028
- > 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.
1029
-
1030
- ### Creating a Studio route
1031
-
1032
- `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.
1033
-
1034
- ### Automatic installation of embedded Studio
1035
-
1036
- 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.
1037
-
1038
- ```bash
1039
- npx sanity@latest init
1040
- ```
1041
-
1042
- ### Manual installation of embedded Studio
1043
-
1044
- **Create** a file `sanity.config.ts` in the project's root and copy the example below:
1045
-
1046
- ```ts
1047
- // ./sanity.config.ts
1048
- 'use client'
1049
-
1050
- import {defineConfig} from 'sanity'
1051
- import {structureTool} from 'sanity/structure'
1052
-
1053
- const projectId = process.env.NEXT_PUBLIC_SANITY_PROJECT_ID!
1054
- const dataset = process.env.NEXT_PUBLIC_SANITY_DATASET!
1055
-
1056
- export default defineConfig({
1057
- basePath: '/studio', // `basePath` must match the route of your Studio
1058
- projectId,
1059
- dataset,
1060
- plugins: [structureTool()],
1061
- schema: {types: []},
1062
- })
1063
- ```
1064
-
1065
- 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:
1066
-
1067
- ```ts
1068
- // ./sanity.cli.ts
1069
-
1070
- import {defineCliConfig} from 'sanity/cli'
1071
-
1072
- const projectId = process.env.NEXT_PUBLIC_SANITY_PROJECT_ID!
1073
- const dataset = process.env.NEXT_PUBLIC_SANITY_DATASET!
1074
-
1075
- export default defineCliConfig({api: {projectId, dataset}})
1076
- ```
1077
-
1078
- Now you can run commands like `npx sanity cors add`. Run `npx sanity help` for a full list of what you can do.
1079
-
1080
- ### Studio route with App Router
1081
-
1082
- 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)
1083
-
1084
- **Create** a new route to render the Studio, with the default metadata and viewport configuration:
1085
-
1086
- ```tsx
1087
- // ./src/app/studio/[[...tool]]/page.tsx
1088
-
1089
- import {NextStudio} from 'next-sanity/studio'
1090
- import config from '../../../../sanity.config'
1091
-
1092
- export const dynamic = 'force-static'
1093
-
1094
- export {metadata, viewport} from 'next-sanity/studio'
1095
-
1096
- export default function StudioPage() {
1097
- return <NextStudio config={config} />
1098
- }
1099
- ```
1100
-
1101
- The default meta tags exported by `next-sanity` can be customized if necessary:
1102
-
1103
- ```tsx
1104
- // ./src/app/studio/[[...tool]]/page.tsx
1105
-
1106
- import type {Metadata, Viewport} from 'next'
1107
- import {metadata as studioMetadata, viewport as studioViewport} from 'next-sanity/studio'
1108
-
1109
- // Set the correct `viewport`, `robots` and `referrer` meta tags
1110
- export const metadata: Metadata = {
1111
- ...studioMetadata,
1112
- // Overrides the title until the Studio is loaded
1113
- title: 'Loading Studio...',
1114
- }
1115
-
1116
- export const viewport: Viewport = {
1117
- ...studioViewport,
1118
- // Overrides the viewport to resize behavior
1119
- interactiveWidget: 'resizes-content',
1120
- }
1121
-
1122
- export default function StudioPage() {
1123
- return <NextStudio config={config} />
1124
- }
1125
- ```
1126
-
1127
- ### Lower-level control with `StudioProvider` and `StudioLayout`
1128
-
1129
- If you need even more control over the Studio, you can pass `StudioProvider` and `StudioLayout` from `sanity` as `children`:
1130
-
1131
- ```tsx
1132
- // ./src/app/studio/[[...tool]]/page.tsx
1133
-
1134
- 'use client'
1135
-
1136
- import {NextStudio} from 'next-sanity/studio'
1137
- import {StudioProvider, StudioLayout} from 'sanity'
1138
-
1139
- import config from '../../../sanity.config'
1140
-
1141
- function StudioPage() {
1142
- return (
1143
- <NextStudio config={config}>
1144
- <StudioProvider config={config}>
1145
- {/* Put components here and you'll have access to the same React hooks as Studio gives you when writing plugins */}
1146
- <StudioLayout />
1147
- </StudioProvider>
1148
- </NextStudio>
1149
- )
1150
- }
1151
- ```
1152
-
1153
74
  ## Migration guides
1154
75
 
1155
76
  > [!IMPORTANT]
1156
- > You're looking at the README for v12, the README for [v11 is available here](https://github.com/sanity-io/next-sanity/tree/v11?tab=readme-ov-file#next-sanity) as well as an [migration guide][migrate-v11-to-v12].
77
+ > You're looking at the README for v12, the README for [v11 is available here](https://github.com/sanity-io/next-sanity/tree/v11?tab=readme-ov-file#next-sanity) as well as a [migration guide][migrate-v11-to-v12].
1157
78
 
1158
79
  - [From `v11` to `v12`][migrate-v11-to-v12]
1159
80
  - [From `v10` to `v11`][migrate-v10-to-v11]
@@ -1171,12 +92,7 @@ function StudioPage() {
1171
92
 
1172
93
  MIT-licensed. See [LICENSE][LICENSE].
1173
94
 
1174
- [api-versioning]: https://www.sanity.io/docs/api-versioning?utm_source=github&utm_medium=readme&utm_campaign=next-sanity
1175
- [app-router]: https://nextjs.org/docs/app/building-your-application/routing
1176
- [cdn]: https://www.sanity.io/docs/asset-cdn?utm_source=github&utm_medium=readme&utm_campaign=next-sanity
1177
- [groq-syntax-highlighting]: https://marketplace.visualstudio.com/items?itemName=sanity-io.vscode-sanity
1178
- [groq-webhook]: https://www.sanity.io/docs/webhooks?utm_source=github&utm_medium=readme&utm_campaign=next-sanity
1179
- [image-url]: https://www.sanity.io/docs/presenting-images?utm_source=github&utm_medium=readme&utm_campaign=next-sanity
95
+ [embedded-studio]: https://www.sanity.io/docs/nextjs/embedding-sanity-studio-in-nextjs
1180
96
  [LICENSE]: LICENSE
1181
97
  [migrate-v1-to-v4]: https://github.com/sanity-io/next-sanity/blob/main/packages/next-sanity/MIGRATE-v1-to-v4.md
1182
98
  [migrate-v4-to-v5-app]: https://github.com/sanity-io/next-sanity/blob/main/packages/next-sanity/MIGRATE-v4-to-v5-app-router.md
@@ -1188,26 +104,15 @@ MIT-licensed. See [LICENSE][LICENSE].
1188
104
  [migrate-v9-to-v10]: https://github.com/sanity-io/next-sanity/blob/main/packages/next-sanity/MIGRATE-v9-to-v10.md
1189
105
  [migrate-v10-to-v11]: https://github.com/sanity-io/next-sanity/blob/main/packages/next-sanity/MIGRATE-v10-to-v11.md
1190
106
  [migrate-v11-to-v12]: https://github.com/sanity-io/next-sanity/blob/main/packages/next-sanity/MIGRATE-v11-to-v12.md
1191
- [next-cache]: https://nextjs.org/docs/app/building-your-application/caching
1192
107
  [next-docs]: https://nextjs.org/docs
1193
- [next-revalidate-docs]: https://nextjs.org/docs/app/api-reference/functions/fetch#optionsnextrevalidate
1194
- [pages-router]: https://nextjs.org/docs/pages/building-your-application/routing
1195
- [personal-website-template]: https://github.com/sanity-io/template-nextjs-personal-website
1196
- [portable-text]: https://portabletext.org
1197
- [sanity-client]: https://www.sanity.io/docs/js-client?utm_source=github&utm_medium=readme&utm_campaign=next-sanity
1198
108
  [sanity]: https://www.sanity.io?utm_source=github&utm_medium=readme&utm_campaign=next-sanity
1199
- [visual-editing]: https://www.sanity.io/docs/introduction-to-visual-editing?utm_source=github&utm_medium=readme&utm_campaign=next-sanity
1200
- [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
1201
- [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
1202
- [sanity-typegen]: https://www.sanity.io/docs/sanity-typegen?utm_source=github&utm_medium=readme&utm_campaign=next-sanity
1203
- [sanity-typegen-monorepo]: https://www.sanity.io/docs/sanity-typegen#1a6a147d6737?utm_source=github&utm_medium=readme&utm_campaign=next-sanity
1204
- [sanity-typegen-queries]: https://www.sanity.io/docs/sanity-typegen#c3ef15d8ad39?utm_source=github&utm_medium=readme&utm_campaign=next-sanity
1205
- [sanity-docs]: https://www.sanity.io/docs
1206
- [sanity-graphql]: https://www.sanity.io/docs/graphql?utm_source=github&utm_medium=readme&utm_campaign=next-sanity
1207
- [vs-code-extension]: https://marketplace.visualstudio.com/items?itemName=sanity-io.vscode-sanity
1208
- [sanity-studio]: https://www.sanity.io/docs/sanity-studio?utm_source=github&utm_medium=readme&utm_campaign=next-sanity
1209
- [groq-functions]: https://www.sanity.io/docs/groq-functions?utm_source=github&utm_medium=readme&utm_campaign=next-sanity
1210
- [vercel-content-link]: https://vercel.com/docs/workflow-collaboration/edit-mode#content-link?utm_source=github&utm_medium=readme&utm_campaign=next-sanity
1211
109
  [sanity-next-clean-starter]: https://www.sanity.io/templates/nextjs-sanity-clean
1212
110
  [sanity-next-featured-starter]: https://www.sanity.io/templates/personal-website-with-built-in-content-editing
1213
- [live-content-api]: https://www.sanity.io/docs/live-content-api?utm_source=github&utm_medium=readme&utm_campaign=next-sanity
111
+ [sanity-next-quickstart]: https://www.sanity.io/docs/next-js-quickstart/setting-up-your-studio
112
+ [sanity-next-docs]: https://www.sanity.io/docs/nextjs
113
+ [sanity-next-client]: https://www.sanity.io/docs/nextjs/configure-sanity-client-nextjs
114
+ [app-router-vised]: https://www.sanity.io/docs/visual-editing/visual-editing-with-next-js-app-router
115
+ [sanity-reference-docs]: https://reference.sanity.io/next-sanity/
116
+ [sanity-next-caching]: https://www.sanity.io/docs/nextjs/caching-and-revalidation-in-nextjs
117
+ [next-queries]: https://www.sanity.io/docs/nextjs/query-content-nextjs
118
+ [next-sanity-intro]: https://www.sanity.io/docs/nextjs/introduction
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "next-sanity",
3
- "version": "12.1.3",
3
+ "version": "12.1.4",
4
4
  "description": "Sanity.io toolkit for Next.js",
5
5
  "keywords": [
6
6
  "live",
@@ -59,7 +59,7 @@
59
59
  },
60
60
  "dependencies": {
61
61
  "@portabletext/react": "^6.0.3",
62
- "@sanity/client": "^7.18.0",
62
+ "@sanity/client": "^7.20.0",
63
63
  "@sanity/comlink": "^4.0.1",
64
64
  "@sanity/presentation-comlink": "^2.0.1",
65
65
  "@sanity/preview-url-secret": "^4.0.4",
@@ -79,7 +79,7 @@
79
79
  "@vitejs/plugin-react": "^5.2.0",
80
80
  "@vitest/coverage-v8": "^4.1.0",
81
81
  "js-yaml": "^4.1.1",
82
- "next": "16.2.1-canary.1",
82
+ "next": "16.2.1-canary.5",
83
83
  "publint": "^0.3.18",
84
84
  "react": "^19.2.4",
85
85
  "react-dom": "^19.2.4",
@@ -91,7 +91,7 @@
91
91
  "vitest-package-exports": "^1.2.0"
92
92
  },
93
93
  "peerDependencies": {
94
- "@sanity/client": "^7.18.0",
94
+ "@sanity/client": "^7.20.0",
95
95
  "next": "^16.0.0-0",
96
96
  "react": "^19.2.3",
97
97
  "react-dom": "^19.2.3",