next-sanity 5.5.0 → 5.5.1

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 +469 -183
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,43 +1,89 @@
1
1
  # next-sanity<!-- omit in toc -->
2
2
 
3
- [Sanity.io](https://www.sanity.io/?utm_source=github&utm_medium=readme&utm_campaign=next-sanity) toolkit for Next.js.
3
+ The official [Sanity.io][sanity] toolkit for Next.js apps.
4
4
 
5
5
  **Features:**
6
6
 
7
- - [Client-side live real-time preview for authenticated users](#live-real-time-preview)
8
- - [GROQ syntax highlighting](https://marketplace.visualstudio.com/items?itemName=sanity-io.vscode-sanity)
9
- - [Embed](#next-sanitystudio) [Studio v3](https://www.sanity.io/studio-v3) in [Next.js](https://nextjs.org/) apps
7
+ - The [Sanity Client][sanity-client] fully compatible with [Next.js’ caching features][next-cache]
8
+ - [Live Preview mode][preview-kit]
9
+ - [Visual Editing](#visual-editing-with-content-source-maps)
10
+ - [GROQ syntax highlighting][groq-syntax-highlighting]
11
+ - Embedded Sanity Studio
10
12
 
11
13
  ## Table of contents
12
14
 
15
+ - [Table of contents](#table-of-contents)
13
16
  - [Installation](#installation)
14
- - [`next-sanity` Running groq queries](#next-sanity-running-groq-queries)
15
- - [Using Perspectives](#using-perspectives)
17
+ - [Common dependencies](#common-dependencies)
18
+ - [Peer dependencies for embedded Sanity Sudio](#peer-dependencies-for-embedded-sanity-sudio)
19
+ - [Usage](#usage)
20
+ - [Quick start](#quick-start)
21
+ - [App Router Components](#app-router-components)
22
+ - [Page Router Components](#page-router-components)
16
23
  - [Should `useCdn` be `true` or `false`?](#should-usecdn-be-true-or-false)
17
- - [`app-router`, React Server Components and caching](#app-router-react-server-components-and-caching)
18
- - [`next-sanity` Visual Editing with Content Source Maps](#next-sanity-visual-editing-with-content-source-maps)
19
- - [`next-sanity/preview` Preview drafts, hot reload on changes](#next-sanitypreview-preview-drafts-hot-reload-on-changes)
20
- - [`next-sanity/studio`](#next-sanitystudio)
21
- - [`next-sanity/webhook`](#next-sanitywebhook)
24
+ - [How does `apiVersion` work?](#how-does-apiversionwork)
25
+ - [Cache revalidation](#cache-revalidation)
26
+ - [Time-based revalidation](#time-based-revalidation)
27
+ - [Tag-based revalidation webhook](#tag-based-revalidation-webhook)
28
+ - [Slug-based revalidation for the Pages Router](#slug-based-revalidation-for-the-pages-router)
29
+ - [Working example implementation](#working-example-implementation)
30
+ - [Debugging caching and revalidation](#debugging-caching-and-revalidation)
31
+ - [Preview](#preview)
32
+ - [Using Perspectives](#using-perspectives)
33
+ - [Live Preview](#live-preview)
34
+ - [Using `draftMode()` to de/activate previews](#using-draftmode-to-deactivate-previews)
35
+ - [Visual Editing with Content Source Maps](#visual-editing-with-content-source-maps)
36
+ - [Embedded Sanity Studio](#embedded-sanity-studio)
37
+ - [Configuring Sanity Studio on a route](#configuring-sanity-studio-on-a-route)
38
+ - [Manual installation](#manual-installation)
39
+ - [Studio route with App Router](#studio-route-with-app-router)
40
+ - [Studio Routes with Pages Router](#studio-routes-with-pages-router)
41
+ - [Lower level control with `StudioProvider` and `StudioLayout`](#lower-level-control-with-studioprovider-and-studiolayout)
22
42
  - [Migration guides](#migration-guides)
23
43
  - [Release new version](#release-new-version)
24
44
  - [License](#license)
25
45
 
26
46
  ## Installation
27
47
 
48
+ For basic functionality, run the following command in the package manager of your choice:
49
+
50
+ ```bash
51
+ npm install next-sanity
52
+ ```
53
+
54
+ ```bash
55
+ yarn add next-sanity
56
+ ```
57
+
58
+ ```bash
59
+ pnpm install next-sanity
60
+ ```
61
+
62
+ ```bash
63
+ bun install next-sanity
64
+ ```
65
+
66
+ ### Common dependencies
67
+
68
+ Building with Sanity and Next.js, you‘re likely to want libraries to handle [On-Demand Image Transformations][image-url] and block content with [Portable Text][portable-text]:
69
+
70
+ ```bash
71
+ npm install @portabletext/react @sanity/image-url
72
+ ```
73
+
28
74
  ```bash
29
- npm install next-sanity @sanity/client @portabletext/react @sanity/image-url
75
+ yarn add @portabletext/react @sanity/image-url
30
76
  ```
31
77
 
32
78
  ```bash
33
- yarn add next-sanity @sanity/client @portabletext/react @sanity/image-url
79
+ pnpm install @portabletext/react @sanity/image-url
34
80
  ```
35
81
 
36
82
  ```bash
37
- pnpm install next-sanity @sanity/client @portabletext/react @sanity/image-url
83
+ bun install @portabletext/react @sanity/image-url
38
84
  ```
39
85
 
40
- ### `next-sanity/studio` peer dependencies
86
+ ### Peer dependencies for embedded Sanity Studio
41
87
 
42
88
  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`:
43
89
 
@@ -45,14 +91,21 @@ When using `npm` newer than `v7`, or `pnpm` newer than `v8`, you should end up w
45
91
  npx install-peerdeps --yarn next-sanity
46
92
  ```
47
93
 
48
- ## `next-sanity` Running groq queries
94
+ ## Usage
95
+
96
+ 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.
97
+
98
+ ### Quick start
99
+
100
+ To start running GROQ queries with `next-sanity`, we recommend creating a `client.ts` file:
49
101
 
50
102
  ```ts
51
- import {createClient, groq} from 'next-sanity'
103
+ // ./src/utils/sanity/client.ts
104
+ import {createClient} from 'next-sanity'
52
105
 
53
106
  const projectId = process.env.NEXT_PUBLIC_SANITY_PROJECT_ID // "pv8y60vp"
54
107
  const dataset = process.env.NEXT_PUBLIC_SANITY_DATASET // "production"
55
- const apiVersion = process.env.NEXT_PUBLIC_SANITY_API_VERSION // "2023-05-03"
108
+ const apiVersion = process.env.NEXT_PUBLIC_SANITY_API_VERSION || '2023-05-03'
56
109
 
57
110
  const client = createClient({
58
111
  projectId,
@@ -60,47 +113,356 @@ const client = createClient({
60
113
  apiVersion, // https://www.sanity.io/docs/api-versioning
61
114
  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
62
115
  })
116
+ ```
117
+
118
+ ### App Router Components
119
+
120
+ To fetch data in a React Server Component using the [App Router][app-router]:
121
+
122
+ ```tsx
123
+ // ./src/components/PostIndex.tsx
124
+ import {client} from '@/src/utils/sanity/client'
125
+
126
+ type Post = {
127
+ _id: string
128
+ title?: string
129
+ slug?: {
130
+ current: string
131
+ }
132
+ }
133
+
134
+ export async function PostIndex() {
135
+ const posts = await client.fetch<Post[]>(`*[_type == "post"]`)
63
136
 
64
- const data = await client.fetch(groq`*[]`)
137
+ return (
138
+ <ul>
139
+ {posts.map((post) => (
140
+ <li key={post._id}>
141
+ <a href={post?.slug.current}>{post?.title}</a>
142
+ </li>
143
+ ))}
144
+ </ul>
145
+ )
146
+ }
65
147
  ```
66
148
 
67
- ### Using Perspectives
149
+ ### Page Router Components
150
+
151
+ If you're using the [Pages Router][pages-router], then you can do the following from a page component:
152
+
153
+ ```tsx
154
+ // ./src/pages/Index.tsx
155
+ import {client} from '@/src/utils/sanity/client'
156
+
157
+ type Post = {
158
+ _id: string
159
+ title?: string
160
+ slug?: {
161
+ current: string
162
+ }
163
+ }
68
164
 
69
- The `perspective` option can be used to specify special filtering behavior for queries. The default value is `raw`, which means no special filtering is applied, while [`published`](#published) and [`previewDrafts`](#previewdrafts) can be used to optimize for specific use cases. Read more about this option:
165
+ export async function getStaticProps() {
166
+ return await client.fetch<Post[]>(`*[_type == "post"]`)
167
+ }
70
168
 
71
- - [Perspectives in Sanity docs][perspectives-docs]
72
- - [Perspectives in @sanity/client README][perspectives-readme]
169
+ export async function HomePage(props) {
170
+ const {posts} = props
171
+
172
+ return (
173
+ <ul>
174
+ {posts.map((post) => (
175
+ <li key={post._id}>
176
+ <a href={post?.slug.current}>{post?.title}</a>
177
+ </li>
178
+ ))}
179
+ </ul>
180
+ )
181
+ }
182
+ ```
73
183
 
74
184
  ### Should `useCdn` be `true` or `false`?
75
185
 
186
+ 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.
187
+
76
188
  The general rule is that `useCdn` should be `true` when:
77
189
 
78
- - Data fetching happens client-side, e.g. in a `useEffect` hook or in response to a user interaction where the `client.fetch` call is made in the browser.
79
- - SSR data fetching is dynamic and have a high number of unique requests per visitor, e.g. a "For You" feed.
190
+ - 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.
191
+ - Server-Side Rendered (SSR) data fetching is dynamic and has a high number of unique requests per visitor, for example, a "For You" feed.
80
192
 
81
193
  And it makes sense to set `useCdn` to `false` when:
82
194
 
83
- - Used in a static site generation context, e.g. `getStaticProps` or `getStaticPaths`.
84
- - Used in a ISR on-demand webhook responder.
195
+ - Used in a static site generation context, for example, `getStaticProps` or `getStaticPaths`.
196
+ - Used in an ISR on-demand webhook responder.
85
197
  - Good `stale-while-revalidate` caching is in place that keeps API requests on a consistent low, even if traffic to Next.js spikes.
86
- - When in Preview or Draft mode as part of an editorial workflow, and you need to ensure that the latest content is always fetched.
198
+ - For Preview or Draft modes as part of an editorial workflow, you need to ensure that the latest content is always fetched.
199
+
200
+ ### How does `apiVersion` work?
87
201
 
88
- ### `app-router`, React Server Components and caching
202
+ 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).
203
+
204
+ ## Cache revalidation
205
+
206
+ 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.
89
207
 
90
208
  > **Note**
91
209
  >
92
- > [`@sanity/client` now fully supports `fetch` based features](https://github.com/sanity-io/client#nextjs-app-router), [including the new `revalidateTag` API](https://nextjs.org/docs/app/api-reference/functions/revalidateTag). Using `React.cache` is unnecessary.
210
+ > 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].
211
+
212
+ ### Time-based revalidation
213
+
214
+ Time-based revalidation is best for less complex cases and where content updates don't need to be immediately available.
215
+
216
+ ```ts
217
+ // ./src/utils/sanity/client.ts
218
+ import {createClient} from 'next-sanity'
219
+
220
+ const projectId = process.env.NEXT_PUBLIC_SANITY_PROJECT_ID // "pv8y60vp"
221
+ const dataset = process.env.NEXT_PUBLIC_SANITY_DATASET // "production"
222
+ const apiVersion = process.env.NEXT_PUBLIC_SANITY_API_VERSION || '2023-05-03'
223
+
224
+ const client = createClient({
225
+ projectId,
226
+ dataset,
227
+ apiVersion, // https://www.sanity.io/docs/api-versioning
228
+ 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
229
+ next: {
230
+ revalidate: 3600, // look for updates to revalidate cache every hour
231
+ },
232
+ })
233
+ ```
234
+
235
+ ### Tag-based revalidation webhook
236
+
237
+ 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.
238
+
239
+ 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).
240
+
241
+ If you're planning to use `revalidateTag`, then remember to set up the webhook (see code below) as well.
242
+
243
+ ```ts
244
+ // ./src/utils/sanity/client.ts
245
+ import 'server-only'
246
+
247
+ import type {QueryParams} from '@sanity/client'
248
+ import {createClient} from 'next-sanity'
249
+
250
+ const projectId = process.env.NEXT_PUBLIC_SANITY_PROJECT_ID // "pv8y60vp"
251
+ const dataset = process.env.NEXT_PUBLIC_SANITY_DATASET // "production"
252
+ const apiVersion = process.env.NEXT_PUBLIC_SANITY_API_VERSION || '2023-05-03'
253
+
254
+ const client = createClient({
255
+ projectId,
256
+ dataset,
257
+ apiVersion, // https://www.sanity.io/docs/api-versioning
258
+ useCdn: false,
259
+ })
260
+
261
+ const DEFAULT_PARAMS = {} as QueryParams
262
+ const DEFAULT_TAGS = [] as string[]
263
+
264
+ export async function sanityFetch<QueryResponse>({
265
+ query,
266
+ params = DEFAULT_PARAMS,
267
+ tags = DEFAULT_TAGS,
268
+ }: {
269
+ query: string
270
+ params?: QueryParams
271
+ tags: string[]
272
+ }): Promise<QueryResponse> {
273
+ return client.fetch<QueryResponse>(query, params, {
274
+ cache: 'force-cache',
275
+ next: {
276
+ //revalidate: 30, // for simple, time-based revalidation
277
+ tags, // for tag-based revalidation
278
+ },
279
+ })
280
+ }
281
+ ```
282
+
283
+ Now you can import the `sanityFetch()` function in any component within the `app` folder, and specify for which document types you want it to revalidate:
284
+
285
+ ```tsx
286
+ // ./src/app/home/layout.tsx
287
+ import { sanityFetch } from '@/src/utils/sanity/client'
288
+ import { PageProps } from '@/src/app/(page)/Page.tsx'
289
+
290
+ type HomePageProps = {
291
+ _id: string
292
+ title?: string
293
+ navItems: PageProps[]
294
+ }
295
+
296
+ export async function HomeLayout({children}) {
297
+ // revalidate if there are changes to either the home document or to a page document (since they're referenced to in navItems)
298
+ const home = await sanityFetch<HomePageProps>({
299
+ query: `*[_id == "home"][0]{...,navItems[]->}`,
300
+ tags: ['home', 'page']
301
+ })
302
+
303
+ return (
304
+ <main>
305
+ <nav>
306
+ <span>{home?.title}</span>
307
+ <ul>
308
+ {home?.navItems.map(navItem => ({
309
+ <li key={navItem._id}><a href={navItem?.slug?.current}>{navItem?.title}</a></li>
310
+ }))}
311
+ </ul>
312
+ </nav>
313
+ {children}
314
+ </main>
315
+ )
316
+ }
317
+ ```
318
+
319
+ 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].
320
+
321
+ You can use this [template][webhook-template] to quickly configure the webhook for your Sanity project.
322
+
323
+ 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:
324
+
325
+ ```ts
326
+ // ./src/app/api/revalidate.ts
327
+ import {revalidateTag} from 'next/cache'
328
+ import {type NextRequest, NextResponse} from 'next/server'
329
+ import {parseBody} from 'next-sanity/webhook'
330
+
331
+ export async function POST(req: NextRequest) {
332
+ try {
333
+ const {isValidSignature, body} = await parseBody<{_type}>(
334
+ req,
335
+ process.env.SANITY_REVALIDATE_SECRET,
336
+ )
337
+
338
+ if (!isValidSignature) {
339
+ const message = 'Invalid signature'
340
+ return new Response(JSON.stringify({message, isValidSignature, body}), {status: 401})
341
+ }
342
+
343
+ if (!body?._type) {
344
+ const message = 'Bad Request'
345
+ return new Response({message, body}, {status: 400})
346
+ }
347
+
348
+ // If the `_type` is `page`, then all `client.fetch` calls with
349
+ // `{next: {tags: ['page']}}` will be revalidated
350
+ await revalidateTag(body._type)
351
+
352
+ return NextResponse.json({body})
353
+ } catch (err) {
354
+ console.error(err)
355
+ return new Response(err.message, {status: 500})
356
+ }
357
+ }
358
+ ```
359
+
360
+ You can choose to match tags based on any field or expression since GROQ-Powered Webhooks allow you to freely define the payload.
361
+
362
+ ### Slug-based revalidation for the Pages Router
363
+
364
+ If you are using the Pages Router and want on-demand revalidation, 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}`.
365
+
366
+ ```ts
367
+ // ./pages/api/revalidate.ts
368
+ import type {NextApiRequest, NextApiResponse} from 'next'
369
+ import {parseBody} from 'next-sanity/webhook'
370
+
371
+ // Export the config from next-sanity to enable validating the request body signature properly
372
+ export {config} from 'next-sanity/webhook'
373
+
374
+ export default async function revalidate(req: NextApiRequest, res: NextApiResponse) {
375
+ try {
376
+ const {isValidSignature, body} = await parseBody(req, process.env.SANITY_REVALIDATE_SECRET)
377
+
378
+ if (!isValidSignature) {
379
+ const message = 'Invalid signature'
380
+ return res.status(401).json({message, isValidSignature, body})
381
+ }
382
+
383
+ const staleRoute = `/${body.slug.current}`
384
+ await res.revalidate(staleRoute)
385
+ const message = `Updated route: ${staleRoute}`
386
+ return res.status(200).json({message, body})
387
+ } catch (err) {
388
+ console.error(err)
389
+ return res.status(500).json({message: err.message})
390
+ }
391
+ }
392
+ ```
393
+
394
+ ### Working example implementation
395
+
396
+ Check out our [Personal website template][personal-website-template] to see a feature-complete example of how `revalidateTag` is used together with Live Previews.
397
+
398
+ ### Debugging caching and revalidation
399
+
400
+ To aid in debugging and understanding what's in the cache, revalidated, skipped, and more, add the following to your Next.js configuration file:
401
+
402
+ ```js
403
+ // ./next.config.js
404
+ module.exports = {
405
+ experimental: {
406
+ logging: 'verbose',
407
+ },
408
+ }
409
+ ```
410
+
411
+ ## Preview
412
+
413
+ There are different ways to set up content previews with Sanity and Next.js.
414
+
415
+ ### Using Perspectives
416
+
417
+ [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.
418
+
419
+ ```ts
420
+ // ./src/utils/sanity/client.ts
421
+ import {createClient} from 'next-sanity'
422
+
423
+ const projectId = process.env.NEXT_PUBLIC_SANITY_PROJECT_ID // "pv8y60vp"
424
+ const dataset = process.env.NEXT_PUBLIC_SANITY_DATASET // "production"
425
+ const apiVersion = process.env.NEXT_PUBLIC_SANITY_API_VERSION || '2023-05-03'
426
+ const token = process.env.SECRET_SANITY_VIEW_TOKEN
427
+
428
+ const client = createClient({
429
+ projectId,
430
+ dataset,
431
+ apiVersion, // https://www.sanity.io/docs/api-versioning
432
+ 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
433
+ token,
434
+ perspective: 'published', // prevent drafts from leaking through even though requests are authenticated
435
+ })
436
+ ```
437
+
438
+ ### Live Preview
439
+
440
+ 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.
441
+
442
+ Router-specific setup guides for Live Preview:
443
+
444
+ - [`app-router`][preivew-app-router]
445
+ - [`pages-router`][preview-pages-router]
446
+
447
+ 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].
448
+ The [same is true][preview-kit-livequery] for `next-sanity/preview/live-query`.
449
+
450
+ ### Using `draftMode()` to de/activate previews
451
+
452
+ Next.js gives you [a built-in `draftMode` variable][draft-mode] that can be used to activate features like Visual Edit or any preview implementation.
93
453
 
94
454
  ```ts
455
+ // ./src/utils/sanity/client.ts
95
456
  import 'server-only'
96
457
 
458
+ import {draftMode} from 'next/headers'
97
459
  import type {QueryParams} from '@sanity/client'
98
460
  import {createClient, groq} from 'next-sanity'
99
461
  import {draftMode} from 'next/headers'
100
462
 
101
463
  const projectId = process.env.NEXT_PUBLIC_SANITY_PROJECT_ID // "pv8y60vp"
102
464
  const dataset = process.env.NEXT_PUBLIC_SANITY_DATASET // "production"
103
- const apiVersion = process.env.NEXT_PUBLIC_SANITY_API_VERSION // "2023-05-03"
465
+ const apiVersion = process.env.NEXT_PUBLIC_SANITY_API_VERSION || '2023-05-03'
104
466
 
105
467
  const client = createClient({
106
468
  projectId,
@@ -142,37 +504,9 @@ export async function sanityFetch<QueryResponse>({
142
504
  },
143
505
  })
144
506
  }
145
-
146
- // Inside a Server Component data is easily fetched and tagged
147
- async function HomePageLayout() {
148
- const data = await sanityFetch<HomePageData>({
149
- query: groq`*[_type == "home"][0]`,
150
- // Now calling `revalidateTag('home')` will revalidate this query, and could be done with a simple GROQ webhook
151
- tags: ['home'],
152
- })
153
-
154
- return (
155
- <div>
156
- <h1>{data.title}</h1>
157
- <PortableText blocks={data.body} />
158
- </div>
159
- )
160
- }
161
- ```
162
-
163
- [Checkout our Personal website template to see a feature complete example of how `revalidateTag` is used together with Live Previews.](https://github.com/sanity-io/sanity-template-nextjs-app-router-personal-website)
164
-
165
- To aid in debugging and understanding what's in the cache, revalidated, skipped and more, add this to your `next.config.js`:
166
-
167
- ```js
168
- module.exports = {
169
- experimental: {
170
- logging: 'verbose',
171
- },
172
- }
173
507
  ```
174
508
 
175
- ## `next-sanity` Visual Editing with Content Source Maps
509
+ ## Visual Editing with Content Source Maps
176
510
 
177
511
  > **Note**
178
512
  >
@@ -191,68 +525,69 @@ const client = createClient({
191
525
  projectId,
192
526
  dataset,
193
527
  apiVersion, // https://www.sanity.io/docs/api-versioning
194
- 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
528
+ 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
195
529
  studioUrl: '/studio', // Or: 'https://my-cool-project.sanity.studio'
196
530
  encodeSourceMap: true, // Optional. Default to: process.env.NEXT_PUBLIC_VERCEL_ENV === 'preview',
197
531
  })
198
532
  ```
199
533
 
200
- [Our setup guide walks you through how to customize the experience.][visual-editing]
534
+ Go to our [setup guide][visual-editing] for a walkthrough on how to customize the experience.
201
535
 
202
- ## `next-sanity/preview` Preview drafts, hot reload on changes
536
+ ## Embedded Sanity Studio
203
537
 
204
- Chose a setup guide for the router you're using:
538
+ 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.
205
539
 
206
- - [`app-router`](./PREVIEW-app-router.md)
207
- - [`pages-router`](./PREVIEW-pages-router.md)
540
+ This opens up many possibilities:
208
541
 
209
- 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](https://github.com/sanity-io/preview-kit#sanitypreview-kit-1).
210
- The [same is true](https://github.com/sanity-io/preview-kit#using-the-livequery-wrapper-component-instead-of-the-uselivequery-hook) for `next-sanity/preview/live-query`.
542
+ - Any service that hosts Next.js apps can now host your Studio.
543
+ - Building previews for your content is easier as your Studio lives in the same environment.
544
+ - Use [Data Fetching][next-data-fetching] to configure your Studio.
545
+ - Easy setup of [Preview Mode][next-preview-mode].
211
546
 
212
- ## `next-sanity/studio`
547
+ > [See it live][embedded-studio-demo]
213
548
 
214
- > [See it live](https://next.sanity.build/studio)
549
+ ### Configuring Sanity Studio on a route
215
550
 
216
- The latest version of Sanity Studio allows you to embed a near-infinitely configurable content editing interface into any React application. This opens up many possibilities:
551
+ 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.
217
552
 
218
- - Any service that hosts Next.js apps can now host your Studio.
219
- - Building previews for your content is easier as your Studio lives in the same environment.
220
- - Use [Data Fetching](https://nextjs.org/docs/basic-features/data-fetching/overview) to configure your Studio.
221
- - Easy setup of [Preview Mode](https://nextjs.org/docs/advanced-features/preview-mode).
553
+ To quickly scaffold the embedded studio and a Sanity project, you can run the following command in your project folder:
222
554
 
223
- ### Usage
555
+ ```bash
556
+ npx sanity@latest init
557
+ ```
224
558
 
225
- `NextStudio` 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.
559
+ ### Manual installation
226
560
 
227
- Both the Next `/app` and `/pages` examples uses this config file:
228
- `sanity.config.ts`:
561
+ 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. Both the Next `/app` and `/pages` examples use this config file:
229
562
 
230
563
  ```ts
564
+ // ./sanity.config.ts
231
565
  import {defineConfig} from 'sanity'
232
566
  import {deskTool} from 'sanity/desk'
233
567
 
234
- import {schemaTypes} from './schemas'
568
+ import {schemaTypes} from './src/schema'
235
569
 
236
570
  const projectId = process.env.NEXT_PUBLIC_SANITY_PROJECT_ID!
237
571
  const dataset = process.env.NEXT_PUBLIC_SANITY_DATASET!
238
572
 
239
573
  export default defineConfig({
240
- basePath: '/studio', // <-- important that `basePath` matches the route you're mounting your studio from, it applies to both `/pages` and `/app`
574
+ basePath: '/admin', // <-- important that `basePath` matches the route you're mounting your studio from, it applies to both `/pages` and `/app`
241
575
 
242
576
  projectId,
243
577
  dataset,
244
-
245
578
  plugins: [deskTool()],
246
-
247
579
  schema: {
248
580
  types: schemaTypes,
249
581
  },
250
582
  })
251
583
  ```
252
584
 
253
- To use `sanity.cli.ts` with the same `projectId` and `dataset` as your `sanity.config.ts`:
585
+ 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.
586
+
587
+ 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:
254
588
 
255
589
  ```ts
590
+ // ./sanity.cli.ts
256
591
  /* eslint-disable no-process-env */
257
592
  import {loadEnvConfig} from '@next/env'
258
593
  import {defineCliConfig} from 'sanity/cli'
@@ -266,13 +601,12 @@ const dataset = process.env.NEXT_PUBLIC_SANITY_DATASET
266
601
  export default defineCliConfig({api: {projectId, dataset}})
267
602
  ```
268
603
 
269
- Now you can run commands like `npx sanity cors add`. See `npx sanity help` for a full list of what you can do.
270
-
271
- #### Using `app-router`
604
+ Now you can run commands like `npx sanity cors add`. Run `npx sanity help` for a full list of what you can do.
272
605
 
273
- `app/studio/[[...index]]/page.tsx`:
606
+ ### Studio route with App Router
274
607
 
275
608
  ```tsx
609
+ // ./src/app/studio/[[...index]]/page.tsx
276
610
  import {Studio} from './Studio'
277
611
 
278
612
  // Ensures the Studio route is statically generated
@@ -286,9 +620,8 @@ export default function StudioPage() {
286
620
  }
287
621
  ```
288
622
 
289
- `app/studio/[[...index]]/Studio.tsx`:
290
-
291
623
  ```tsx
624
+ // ./src/app/studio/[[...index]]/Studio.tsx
292
625
  'use client'
293
626
 
294
627
  import {NextStudio} from 'next-sanity/studio'
@@ -301,10 +634,10 @@ export function Studio() {
301
634
  }
302
635
  ```
303
636
 
304
- Customize meta tags
305
- `app/studio/[[...index]]/page.tsx`:
637
+ How to customize meta tags:
306
638
 
307
639
  ```tsx
640
+ // ./src/app/studio/[[...index]]/page.tsx
308
641
  import type {Metadata} from 'next'
309
642
  import {metadata as studioMetadata} from 'next-sanity/studio/metadata'
310
643
 
@@ -322,11 +655,10 @@ export default function StudioPage() {
322
655
  }
323
656
  ```
324
657
 
325
- #### Using `pages-router`
326
-
327
- `/pages/studio/[[...index]].tsx`:
658
+ ### Studio Routes with Pages Router
328
659
 
329
660
  ```tsx
661
+ // ./pages/studio/[[...index]].tsx
330
662
  import Head from 'next/head'
331
663
  import {NextStudio} from 'next-sanity/studio'
332
664
  import {metadata} from 'next-sanity/studio/metadata'
@@ -347,9 +679,9 @@ export default function StudioPage() {
347
679
  }
348
680
  ```
349
681
 
350
- ### Opt-in to using `StudioProvider` and `StudioLayout`
682
+ ### Lower level control with `StudioProvider` and `StudioLayout`
351
683
 
352
- If you want to go lower level and have more control over the studio you can pass `StudioProvider` and `StudioLayout` from `sanity` as `children`:
684
+ 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`:
353
685
 
354
686
  ```tsx
355
687
  import {NextStudio} from 'next-sanity/studio'
@@ -369,96 +701,50 @@ function StudioPage() {
369
701
  }
370
702
  ```
371
703
 
372
- ## `next-sanity/webhook`
373
-
374
- Implements [`@sanity/webhook`](https://github.com/sanity-io/webhook-toolkit) to parse and verify that a [Webhook](https://www.sanity.io/docs/webhooks) is indeed coming from Sanity infrastructure.
375
-
376
- ### App Router
377
-
378
- `app/api/revalidate/route.ts`:
379
-
380
- ```ts
381
- import {revalidateTag} from 'next/cache'
382
- import {type NextRequest, NextResponse} from 'next/server'
383
- import {parseBody} from 'next-sanity/webhook'
384
-
385
- export async function POST(req: NextRequest) {
386
- try {
387
- const {isValidSignature, body} = await parseBody<{_type}>(
388
- req,
389
- process.env.SANITY_REVALIDATE_SECRET,
390
- )
391
-
392
- if (!isValidSignature) {
393
- const message = 'Invalid signature'
394
- return new Response(JSON.stringify({message, isValidSignature, body}), {status: 401})
395
- }
396
-
397
- if (!body?._type) {
398
- const message = 'Bad Request'
399
- return new Response({message, body}, {status: 400})
400
- }
401
-
402
- // If the `_type` is `page`, then all `client.fetch` calls with
403
- // `{next: {tags: ['page']}}` will be revalidated
404
- await revalidateTag(body._type)
405
-
406
- return NextResponse.json({body})
407
- } catch (err) {
408
- console.error(err)
409
- return new Response(err.message, {status: 500})
410
- }
411
- }
412
- ```
413
-
414
- ### Pages Router
415
-
416
- `pages/api/revalidate.ts`:
417
-
418
- ```ts
419
- import type {NextApiRequest, NextApiResponse} from 'next'
420
- import {parseBody} from 'next-sanity/webhook'
421
-
422
- // Export the config from next-sanity to enable validating the request body signature properly
423
- export {config} from 'next-sanity/webhook'
424
-
425
- export default async function revalidate(req: NextApiRequest, res: NextApiResponse) {
426
- try {
427
- const {isValidSignature, body} = await parseBody(req, process.env.SANITY_REVALIDATE_SECRET)
428
-
429
- if (!isValidSignature) {
430
- const message = 'Invalid signature'
431
- return res.status(401).json({message, isValidSignature, body})
432
- }
433
-
434
- const staleRoute = `/${body.slug.current}`
435
- await res.revalidate(staleRoute)
436
- const message = `Updated route: ${staleRoute}`
437
- return res.status(200).json({message, body})
438
- } catch (err) {
439
- console.error(err)
440
- return res.status(500).json({message: err.message})
441
- }
442
- }
443
- ```
444
-
445
704
  ## Migration guides
446
705
 
447
706
  - From `v4` to `v5`
448
- - [`app-router`](./MIGRATE-v4-to-v5-app-router.md)
449
- - [`pages-router`](./MIGRATE-v4-to-v5-pages-router.md)
450
- - [From `<0.4` to `v4`](./MIGRATE-v1-to-v4.md)
707
+ - [`app-router`][migrate-v4-to-v5-app]
708
+ - [`pages-router`][migrate-v4-to-v5-pages]
709
+ - [From `<0.4` to `v4`][migrate-v1-to-v4]
451
710
 
452
711
  ## License
453
712
 
454
- MIT-licensed. See [LICENSE](LICENSE).
455
-
456
- [visual-editing]: https://www.sanity.io/docs/vercel-visual-editing?utm_source=github.com&utm_medium=referral&utm_campaign=may-vercel-launch
457
- [visual-editing-intro]: https://www.sanity.io/blog/visual-editing-sanity-vercel?utm_source=github.com&utm_medium=referral&utm_campaign=may-vercel-launch
458
- [content-source-maps]: https://www.sanity.io/docs/content-source-maps?utm_source=github.com&utm_medium=referral&utm_campaign=may-vercel-launch
459
- [content-source-maps-intro]: https://www.sanity.io/blog/content-source-maps-announce?utm_source=github.com&utm_medium=referral&utm_campaign=may-vercel-launch
713
+ MIT-licensed. See [LICENSE][LICENSE].
714
+
715
+ [api-versioning]: https://www.sanity.io/docs/api-versioning?utm_source=github&utm_medium=readme&utm_campaign=next-sanity
716
+ [app-router]: https://nextjs.org/docs/app/building-your-application/routing
717
+ [cdn]: https://www.sanity.io/docs/asset-cdn?utm_source=github&utm_medium=readme&utm_campaign=next-sanity
718
+ [ci-workflow]: https://github.com/sanity-io/next-sanity/actions/workflows/ci.yml
719
+ [content-source-maps-intro]: https://www.sanity.io/blog/content-source-maps-announce?utm_source=github&utm_medium=readme&utm_campaign=next-sanity
720
+ [content-source-maps]: https://www.sanity.io/docs/content-source-maps?utm_source=github&utm_medium=readme&utm_campaign=next-sanity
721
+ [draft-mode]: https://nextjs.org/docs/app/building-your-application/configuring/draft-mode
722
+ [embedded-studio-demo]: https://next.sanity.build/studio
723
+ [enterprise-cta]: https://www.sanity.io/enterprise?utm_source=github&utm_medium=readme&utm_campaign=next-sanity
724
+ [groq-syntax-highlighting]: https://marketplace.visualstudio.com/items?itemName=sanity-io.vscode-sanity
725
+ [groq-webhook]: https://www.sanity.io/docs/webhooks?utm_source=github&utm_medium=readme&utm_campaign=next-sanity
726
+ [image-url]: https://www.sanity.io/docs/presenting-images?utm_source=github&utm_medium=readme&utm_campaign=next-sanity
727
+ [LICENSE]: LICENSE
728
+ [migrate-v1-to-v4]: ./MIGRATE-v1-to-v4.md
729
+ [migrate-v4-to-v5-app]: ./MIGRATE-v4-to-v5-pages-app-router.md
730
+ [migrate-v4-to-v5-pages]: ./MIGRATE-v4-to-v5-pages-pages-router.md
731
+ [next-cache]: https://nextjs.org/docs/app/building-your-application/caching
732
+ [next-data-fetching]: https://nextjs.org/docs/basic-features/data-fetching/overview
733
+ [next-preview-mode]: https://nextjs.org/docs/advanced-features/preview-mode
734
+ [pages-router]: https://nextjs.org/docs/pages/building-your-application/routing
735
+ [personal-website-template]: https://github.com/sanity-io/sanity-template-nextjs-app-router-personal-website
736
+ [perspectives-docs]: https://www.sanity.io/docs/perspectives?utm_source=github&utm_medium=readme&utm_campaign=next-sanity
737
+ [portable-text]: https://portabletext.org
738
+ [preivew-app-router]: ./PREVIEW-app-router.md
460
739
  [preview-kit-client]: https://github.com/sanity-io/preview-kit#sanitypreview-kitclient
461
- [sales-cta]: https://www.sanity.io/contact/sales?utm_source=github.com&utm_medium=referral&utm_campaign=may-vercel-launch
462
- [enterprise-cta]: https://www.sanity.io/enterprise?utm_source=github.com&utm_medium=referral&utm_campaign=may-vercel-launch
463
- [perspectives-docs]: https://www.sanity.io/docs/perspectives
464
- [perspectives-readme]: https://github.com/sanity-io/client/#performing-queries
740
+ [preview-kit-documentation]: https://github.com/sanity-io/preview-kit#sanitypreview-kit-1
741
+ [preview-kit-livequery]: https://github.com/sanity-io/preview-kit#using-the-livequery-wrapper-component-instead-of-the-uselivequery-hook
742
+ [preview-kit]: https://github.com/sanity-io/preview-kit
743
+ [preview-pages-router]: ./PREVIEW-pages-router.md
744
+ [revalidate-tag]: https://nextjs.org/docs/app/api-reference/functions/revalidateTag
745
+ [sales-cta]: https://www.sanity.io/contact/sales?utm_source=github&utm_medium=readme&utm_campaign=next-sanity
746
+ [sanity-client]: https://www.sanity.io/docs/js-client?utm_source=github&utm_medium=readme&utm_campaign=next-sanity
747
+ [sanity]: https://www.sanity.io?utm_source=github&utm_medium=readme&utm_campaign=next-sanity
748
+ [visual-editing-intro]: https://www.sanity.io/blog/visual-editing-sanity-vercel?utm_source=github&utm_medium=readme&utm_campaign=next-sanity
749
+ [visual-editing]: https://www.sanity.io/docs/vercel-visual-editing?utm_source=github&utm_medium=readme&utm_campaign=next-sanity
750
+ [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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "next-sanity",
3
- "version": "5.5.0",
3
+ "version": "5.5.1",
4
4
  "description": "Sanity.io toolkit for Next.js",
5
5
  "keywords": [
6
6
  "sanity",