next-sanity 5.5.0 → 5.5.2

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 +484 -184
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,43 +1,88 @@
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 Studio](#peer-dependencies-for-embedded-sanity-studio)
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
- - [Release new version](#release-new-version)
24
43
  - [License](#license)
25
44
 
26
45
  ## Installation
27
46
 
47
+ For basic functionality, run the following command in the package manager of your choice:
48
+
49
+ ```bash
50
+ npm install next-sanity
51
+ ```
52
+
53
+ ```bash
54
+ yarn add next-sanity
55
+ ```
56
+
57
+ ```bash
58
+ pnpm install next-sanity
59
+ ```
60
+
61
+ ```bash
62
+ bun install next-sanity
63
+ ```
64
+
65
+ ### Common dependencies
66
+
67
+ 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]:
68
+
69
+ ```bash
70
+ npm install @portabletext/react @sanity/image-url
71
+ ```
72
+
28
73
  ```bash
29
- npm install next-sanity @sanity/client @portabletext/react @sanity/image-url
74
+ yarn add @portabletext/react @sanity/image-url
30
75
  ```
31
76
 
32
77
  ```bash
33
- yarn add next-sanity @sanity/client @portabletext/react @sanity/image-url
78
+ pnpm install @portabletext/react @sanity/image-url
34
79
  ```
35
80
 
36
81
  ```bash
37
- pnpm install next-sanity @sanity/client @portabletext/react @sanity/image-url
82
+ bun install @portabletext/react @sanity/image-url
38
83
  ```
39
84
 
40
- ### `next-sanity/studio` peer dependencies
85
+ ### Peer dependencies for embedded Sanity Studio
41
86
 
42
87
  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
88
 
@@ -45,14 +90,21 @@ When using `npm` newer than `v7`, or `pnpm` newer than `v8`, you should end up w
45
90
  npx install-peerdeps --yarn next-sanity
46
91
  ```
47
92
 
48
- ## `next-sanity` Running groq queries
93
+ ## Usage
94
+
95
+ 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.
96
+
97
+ ### Quick start
98
+
99
+ To start running GROQ queries with `next-sanity`, we recommend creating a `client.ts` file:
49
100
 
50
101
  ```ts
51
- import {createClient, groq} from 'next-sanity'
102
+ // ./src/utils/sanity/client.ts
103
+ import {createClient} from 'next-sanity'
52
104
 
53
105
  const projectId = process.env.NEXT_PUBLIC_SANITY_PROJECT_ID // "pv8y60vp"
54
106
  const dataset = process.env.NEXT_PUBLIC_SANITY_DATASET // "production"
55
- const apiVersion = process.env.NEXT_PUBLIC_SANITY_API_VERSION // "2023-05-03"
107
+ const apiVersion = process.env.NEXT_PUBLIC_SANITY_API_VERSION || '2023-05-03'
56
108
 
57
109
  const client = createClient({
58
110
  projectId,
@@ -60,47 +112,371 @@ const client = createClient({
60
112
  apiVersion, // https://www.sanity.io/docs/api-versioning
61
113
  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
114
  })
115
+ ```
116
+
117
+ ### App Router Components
118
+
119
+ To fetch data in a React Server Component using the [App Router][app-router]:
120
+
121
+ ```tsx
122
+ // ./src/app/page.tsx
123
+ import {client} from '@/src/utils/sanity/client'
124
+
125
+ type Post = {
126
+ _id: string
127
+ title?: string
128
+ slug?: {
129
+ current: string
130
+ }
131
+ }
132
+
133
+ export async function PostIndex() {
134
+ const posts = await client.fetch<Post[]>(`*[_type == "post"]`)
63
135
 
64
- const data = await client.fetch(groq`*[]`)
136
+ return (
137
+ <ul>
138
+ {posts.map((post) => (
139
+ <li key={post._id}>
140
+ <a href={post?.slug.current}>{post?.title}</a>
141
+ </li>
142
+ ))}
143
+ </ul>
144
+ )
145
+ }
65
146
  ```
66
147
 
67
- ### Using Perspectives
148
+ ### Page Router Components
149
+
150
+ If you're using the [Pages Router][pages-router], then you can do the following from a page component:
151
+
152
+ ```tsx
153
+ // ./src/pages/index.tsx
154
+ import {client} from '@/src/utils/sanity/client'
155
+
156
+ type Post = {
157
+ _id: string
158
+ title?: string
159
+ slug?: {
160
+ current: string
161
+ }
162
+ }
68
163
 
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:
164
+ export async function getStaticProps() {
165
+ return await client.fetch<Post[]>(`*[_type == "post"]`)
166
+ }
70
167
 
71
- - [Perspectives in Sanity docs][perspectives-docs]
72
- - [Perspectives in @sanity/client README][perspectives-readme]
168
+ export async function HomePage(props) {
169
+ const {posts} = props
170
+
171
+ return (
172
+ <ul>
173
+ {posts.map((post) => (
174
+ <li key={post._id}>
175
+ <a href={post?.slug.current}>{post?.title}</a>
176
+ </li>
177
+ ))}
178
+ </ul>
179
+ )
180
+ }
181
+ ```
73
182
 
74
183
  ### Should `useCdn` be `true` or `false`?
75
184
 
185
+ 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.
186
+
76
187
  The general rule is that `useCdn` should be `true` when:
77
188
 
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.
189
+ - 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.
190
+ - 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
191
 
81
192
  And it makes sense to set `useCdn` to `false` when:
82
193
 
83
- - Used in a static site generation context, e.g. `getStaticProps` or `getStaticPaths`.
84
- - Used in a ISR on-demand webhook responder.
194
+ - Used in a static site generation context, for example, `getStaticProps` or `getStaticPaths`.
195
+ - Used in an ISR on-demand webhook responder.
85
196
  - 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.
197
+ - For Preview or Draft modes as part of an editorial workflow, you need to ensure that the latest content is always fetched.
198
+
199
+ ### How does `apiVersion` work?
87
200
 
88
- ### `app-router`, React Server Components and caching
201
+ 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).
202
+
203
+ ## Cache revalidation
204
+
205
+ 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
206
 
90
207
  > **Note**
91
208
  >
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.
209
+ > 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].
210
+
211
+ ### Time-based revalidation
212
+
213
+ Time-based revalidation is best for less complex cases and where content updates don't need to be immediately available.
214
+
215
+ ```tsx
216
+ // ./src/app/home/layout.tsx
217
+ import { client } from '@/src/utils/sanity/client'
218
+ import { PageProps } from '@/src/app/(page)/Page.tsx'
219
+
220
+ type HomePageProps = {
221
+ _id: string
222
+ title?: string
223
+ navItems: PageProps[]
224
+ }
225
+
226
+ export async function HomeLayout({children}) {
227
+ const home = await client.fetch<HomePageProps>(`*[_id == "home"][0]{...,navItems[]->}`,
228
+ next: {
229
+ revalidate: 3600 // look for updates to revalidate cache every hour
230
+ }
231
+ })
232
+
233
+ return (
234
+ <main>
235
+ <nav>
236
+ <span>{home?.title}</span>
237
+ <ul>
238
+ {home?.navItems.map(navItem => ({
239
+ <li key={navItem._id}><a href={navItem?.slug?.current}>{navItem?.title}</a></li>
240
+ }))}
241
+ </ul>
242
+ </nav>
243
+ {children}
244
+ </main>
245
+ )
246
+ }
247
+ ```
248
+
249
+ ### Tag-based revalidation webhook
250
+
251
+ 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.
252
+
253
+ 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).
254
+
255
+ If you're planning to use `revalidateTag`, then remember to set up the webhook (see code below) as well.
256
+
257
+ ```ts
258
+ // ./src/utils/sanity/client.ts
259
+ import 'server-only'
260
+
261
+ import type {QueryParams} from '@sanity/client'
262
+ import {createClient} from 'next-sanity'
263
+
264
+ const projectId = process.env.NEXT_PUBLIC_SANITY_PROJECT_ID // "pv8y60vp"
265
+ const dataset = process.env.NEXT_PUBLIC_SANITY_DATASET // "production"
266
+ const apiVersion = process.env.NEXT_PUBLIC_SANITY_API_VERSION || '2023-05-03'
267
+
268
+ const client = createClient({
269
+ projectId,
270
+ dataset,
271
+ apiVersion, // https://www.sanity.io/docs/api-versioning
272
+ useCdn: false,
273
+ })
274
+
275
+ const DEFAULT_PARAMS = {} as QueryParams
276
+ const DEFAULT_TAGS = [] as string[]
277
+
278
+ export async function sanityFetch<QueryResponse>({
279
+ query,
280
+ params = DEFAULT_PARAMS,
281
+ tags = DEFAULT_TAGS,
282
+ }: {
283
+ query: string
284
+ params?: QueryParams
285
+ tags: string[]
286
+ }): Promise<QueryResponse> {
287
+ return client.fetch<QueryResponse>(query, params, {
288
+ cache: 'force-cache',
289
+ next: {
290
+ //revalidate: 30, // for simple, time-based revalidation
291
+ tags, // for tag-based revalidation
292
+ },
293
+ })
294
+ }
295
+ ```
296
+
297
+ 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:
298
+
299
+ ```tsx
300
+ // ./src/app/home/layout.tsx
301
+ import { sanityFetch } from '@/src/utils/sanity/client'
302
+ import { PageProps } from '@/src/app/(page)/Page.tsx'
303
+
304
+ type HomePageProps = {
305
+ _id: string
306
+ title?: string
307
+ navItems: PageProps[]
308
+ }
309
+
310
+ export async function HomeLayout({children}) {
311
+ // revalidate if there are changes to either the home document or to a page document (since they're referenced to in navItems)
312
+ const home = await sanityFetch<HomePageProps>({
313
+ query: `*[_id == "home"][0]{...,navItems[]->}`,
314
+ tags: ['home', 'page']
315
+ })
316
+
317
+ return (
318
+ <main>
319
+ <nav>
320
+ <span>{home?.title}</span>
321
+ <ul>
322
+ {home?.navItems.map(navItem => ({
323
+ <li key={navItem._id}><a href={navItem?.slug?.current}>{navItem?.title}</a></li>
324
+ }))}
325
+ </ul>
326
+ </nav>
327
+ {children}
328
+ </main>
329
+ )
330
+ }
331
+ ```
332
+
333
+ In order to get `revalidateTag` to work you need to set up an API route in your Next.js app that handles an incoming request, typically made by [a GROQ-Powered Webhook][groq-webhook].
334
+
335
+ You can use this [template][webhook-template] to quickly configure the webhook for your Sanity project.
336
+
337
+ The code example below uses the built-in `parseBody` function to validate that the request comes from your Sanity project (using a shared secret + looking at the request headers). Then it looks at the document type information in the webhook payload and matches that against the revalidation tags in your app:
338
+
339
+ ```ts
340
+ // ./src/app/api/revalidate.ts
341
+ import {revalidateTag} from 'next/cache'
342
+ import {type NextRequest, NextResponse} from 'next/server'
343
+ import {parseBody} from 'next-sanity/webhook'
344
+
345
+ export async function POST(req: NextRequest) {
346
+ try {
347
+ const {isValidSignature, body} = await parseBody<{_type}>(
348
+ req,
349
+ process.env.SANITY_REVALIDATE_SECRET,
350
+ )
351
+
352
+ if (!isValidSignature) {
353
+ const message = 'Invalid signature'
354
+ return new Response(JSON.stringify({message, isValidSignature, body}), {status: 401})
355
+ }
356
+
357
+ if (!body?._type) {
358
+ const message = 'Bad Request'
359
+ return new Response({message, body}, {status: 400})
360
+ }
361
+
362
+ // If the `_type` is `page`, then all `client.fetch` calls with
363
+ // `{next: {tags: ['page']}}` will be revalidated
364
+ await revalidateTag(body._type)
365
+
366
+ return NextResponse.json({body})
367
+ } catch (err) {
368
+ console.error(err)
369
+ return new Response(err.message, {status: 500})
370
+ }
371
+ }
372
+ ```
373
+
374
+ You can choose to match tags based on any field or expression since GROQ-Powered Webhooks allow you to freely define the payload.
375
+
376
+ ### Slug-based revalidation for the Pages Router
377
+
378
+ 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}`.
379
+
380
+ ```ts
381
+ // ./pages/api/revalidate.ts
382
+ import type {NextApiRequest, NextApiResponse} from 'next'
383
+ import {parseBody} from 'next-sanity/webhook'
384
+
385
+ // Export the config from next-sanity to enable validating the request body signature properly
386
+ export {config} from 'next-sanity/webhook'
387
+
388
+ export default async function revalidate(req: NextApiRequest, res: NextApiResponse) {
389
+ try {
390
+ const {isValidSignature, body} = await parseBody(req, process.env.SANITY_REVALIDATE_SECRET)
391
+
392
+ if (!isValidSignature) {
393
+ const message = 'Invalid signature'
394
+ return res.status(401).json({message, isValidSignature, body})
395
+ }
396
+
397
+ const staleRoute = `/${body.slug.current}`
398
+ await res.revalidate(staleRoute)
399
+ const message = `Updated route: ${staleRoute}`
400
+ return res.status(200).json({message, body})
401
+ } catch (err) {
402
+ console.error(err)
403
+ return res.status(500).json({message: err.message})
404
+ }
405
+ }
406
+ ```
407
+
408
+ ### Working example implementation
409
+
410
+ Check out our [Personal website template][personal-website-template] to see a feature-complete example of how `revalidateTag` is used together with Live Previews.
411
+
412
+ ### Debugging caching and revalidation
413
+
414
+ To aid in debugging and understanding what's in the cache, revalidated, skipped, and more, add the following to your Next.js configuration file:
415
+
416
+ ```js
417
+ // ./next.config.js
418
+ module.exports = {
419
+ experimental: {
420
+ logging: 'verbose',
421
+ },
422
+ }
423
+ ```
424
+
425
+ ## Preview
426
+
427
+ There are different ways to set up content previews with Sanity and Next.js.
428
+
429
+ ### Using Perspectives
430
+
431
+ [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.
93
432
 
94
433
  ```ts
434
+ // ./src/utils/sanity/client.ts
435
+ import {createClient} from 'next-sanity'
436
+
437
+ const projectId = process.env.NEXT_PUBLIC_SANITY_PROJECT_ID // "pv8y60vp"
438
+ const dataset = process.env.NEXT_PUBLIC_SANITY_DATASET // "production"
439
+ const apiVersion = process.env.NEXT_PUBLIC_SANITY_API_VERSION || '2023-05-03'
440
+ const token = process.env.SECRET_SANITY_VIEW_TOKEN
441
+
442
+ const client = createClient({
443
+ projectId,
444
+ dataset,
445
+ apiVersion, // https://www.sanity.io/docs/api-versioning
446
+ 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
447
+ token,
448
+ perspective: 'published', // prevent drafts from leaking through even though requests are authenticated
449
+ })
450
+ ```
451
+
452
+ ### Live Preview
453
+
454
+ 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.
455
+
456
+ Router-specific setup guides for Live Preview:
457
+
458
+ - [`app-router`][preivew-app-router]
459
+ - [`pages-router`][preview-pages-router]
460
+
461
+ 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].
462
+ The [same is true][preview-kit-livequery] for `next-sanity/preview/live-query`.
463
+
464
+ ### Using `draftMode()` to de/activate previews
465
+
466
+ 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.
467
+
468
+ ```ts
469
+ // ./src/utils/sanity/client.ts
95
470
  import 'server-only'
96
471
 
472
+ import {draftMode} from 'next/headers'
97
473
  import type {QueryParams} from '@sanity/client'
98
474
  import {createClient, groq} from 'next-sanity'
99
475
  import {draftMode} from 'next/headers'
100
476
 
101
477
  const projectId = process.env.NEXT_PUBLIC_SANITY_PROJECT_ID // "pv8y60vp"
102
478
  const dataset = process.env.NEXT_PUBLIC_SANITY_DATASET // "production"
103
- const apiVersion = process.env.NEXT_PUBLIC_SANITY_API_VERSION // "2023-05-03"
479
+ const apiVersion = process.env.NEXT_PUBLIC_SANITY_API_VERSION || '2023-05-03'
104
480
 
105
481
  const client = createClient({
106
482
  projectId,
@@ -142,37 +518,9 @@ export async function sanityFetch<QueryResponse>({
142
518
  },
143
519
  })
144
520
  }
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
521
  ```
174
522
 
175
- ## `next-sanity` Visual Editing with Content Source Maps
523
+ ## Visual Editing with Content Source Maps
176
524
 
177
525
  > **Note**
178
526
  >
@@ -191,68 +539,69 @@ const client = createClient({
191
539
  projectId,
192
540
  dataset,
193
541
  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
542
+ 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
543
  studioUrl: '/studio', // Or: 'https://my-cool-project.sanity.studio'
196
544
  encodeSourceMap: true, // Optional. Default to: process.env.NEXT_PUBLIC_VERCEL_ENV === 'preview',
197
545
  })
198
546
  ```
199
547
 
200
- [Our setup guide walks you through how to customize the experience.][visual-editing]
548
+ Go to our [setup guide][visual-editing] for a walkthrough on how to customize the experience.
201
549
 
202
- ## `next-sanity/preview` Preview drafts, hot reload on changes
550
+ ## Embedded Sanity Studio
203
551
 
204
- Chose a setup guide for the router you're using:
552
+ 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
553
 
206
- - [`app-router`](./PREVIEW-app-router.md)
207
- - [`pages-router`](./PREVIEW-pages-router.md)
554
+ This opens up many possibilities:
208
555
 
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`.
556
+ - Any service that hosts Next.js apps can now host your Studio.
557
+ - Building previews for your content is easier as your Studio lives in the same environment.
558
+ - Use [Data Fetching][next-data-fetching] to configure your Studio.
559
+ - Easy setup of [Preview Mode][next-preview-mode].
211
560
 
212
- ## `next-sanity/studio`
561
+ > [See it live][embedded-studio-demo]
213
562
 
214
- > [See it live](https://next.sanity.build/studio)
563
+ ### Configuring Sanity Studio on a route
215
564
 
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:
565
+ 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
566
 
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).
567
+ To quickly scaffold the embedded studio and a Sanity project, you can run the following command in your project folder:
222
568
 
223
- ### Usage
569
+ ```bash
570
+ npx sanity@latest init
571
+ ```
224
572
 
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.
573
+ ### Manual installation
226
574
 
227
- Both the Next `/app` and `/pages` examples uses this config file:
228
- `sanity.config.ts`:
575
+ 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
576
 
230
577
  ```ts
578
+ // ./sanity.config.ts
231
579
  import {defineConfig} from 'sanity'
232
580
  import {deskTool} from 'sanity/desk'
233
581
 
234
- import {schemaTypes} from './schemas'
582
+ import {schemaTypes} from './src/schema'
235
583
 
236
584
  const projectId = process.env.NEXT_PUBLIC_SANITY_PROJECT_ID!
237
585
  const dataset = process.env.NEXT_PUBLIC_SANITY_DATASET!
238
586
 
239
587
  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`
588
+ basePath: '/admin', // <-- important that `basePath` matches the route you're mounting your studio from, it applies to both `/pages` and `/app`
241
589
 
242
590
  projectId,
243
591
  dataset,
244
-
245
592
  plugins: [deskTool()],
246
-
247
593
  schema: {
248
594
  types: schemaTypes,
249
595
  },
250
596
  })
251
597
  ```
252
598
 
253
- To use `sanity.cli.ts` with the same `projectId` and `dataset` as your `sanity.config.ts`:
599
+ 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.
600
+
601
+ 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
602
 
255
603
  ```ts
604
+ // ./sanity.cli.ts
256
605
  /* eslint-disable no-process-env */
257
606
  import {loadEnvConfig} from '@next/env'
258
607
  import {defineCliConfig} from 'sanity/cli'
@@ -266,13 +615,12 @@ const dataset = process.env.NEXT_PUBLIC_SANITY_DATASET
266
615
  export default defineCliConfig({api: {projectId, dataset}})
267
616
  ```
268
617
 
269
- Now you can run commands like `npx sanity cors add`. See `npx sanity help` for a full list of what you can do.
618
+ Now you can run commands like `npx sanity cors add`. Run `npx sanity help` for a full list of what you can do.
270
619
 
271
- #### Using `app-router`
272
-
273
- `app/studio/[[...index]]/page.tsx`:
620
+ ### Studio route with App Router
274
621
 
275
622
  ```tsx
623
+ // ./src/app/studio/[[...index]]/page.tsx
276
624
  import {Studio} from './Studio'
277
625
 
278
626
  // Ensures the Studio route is statically generated
@@ -286,9 +634,8 @@ export default function StudioPage() {
286
634
  }
287
635
  ```
288
636
 
289
- `app/studio/[[...index]]/Studio.tsx`:
290
-
291
637
  ```tsx
638
+ // ./src/app/studio/[[...index]]/Studio.tsx
292
639
  'use client'
293
640
 
294
641
  import {NextStudio} from 'next-sanity/studio'
@@ -301,10 +648,10 @@ export function Studio() {
301
648
  }
302
649
  ```
303
650
 
304
- Customize meta tags
305
- `app/studio/[[...index]]/page.tsx`:
651
+ How to customize meta tags:
306
652
 
307
653
  ```tsx
654
+ // ./src/app/studio/[[...index]]/page.tsx
308
655
  import type {Metadata} from 'next'
309
656
  import {metadata as studioMetadata} from 'next-sanity/studio/metadata'
310
657
 
@@ -322,11 +669,10 @@ export default function StudioPage() {
322
669
  }
323
670
  ```
324
671
 
325
- #### Using `pages-router`
326
-
327
- `/pages/studio/[[...index]].tsx`:
672
+ ### Studio Routes with Pages Router
328
673
 
329
674
  ```tsx
675
+ // ./pages/studio/[[...index]].tsx
330
676
  import Head from 'next/head'
331
677
  import {NextStudio} from 'next-sanity/studio'
332
678
  import {metadata} from 'next-sanity/studio/metadata'
@@ -347,9 +693,9 @@ export default function StudioPage() {
347
693
  }
348
694
  ```
349
695
 
350
- ### Opt-in to using `StudioProvider` and `StudioLayout`
696
+ ### Lower level control with `StudioProvider` and `StudioLayout`
351
697
 
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`:
698
+ 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
699
 
354
700
  ```tsx
355
701
  import {NextStudio} from 'next-sanity/studio'
@@ -369,96 +715,50 @@ function StudioPage() {
369
715
  }
370
716
  ```
371
717
 
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
718
  ## Migration guides
446
719
 
447
720
  - 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)
721
+ - [`app-router`][migrate-v4-to-v5-app]
722
+ - [`pages-router`][migrate-v4-to-v5-pages]
723
+ - [From `<0.4` to `v4`][migrate-v1-to-v4]
451
724
 
452
725
  ## License
453
726
 
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
727
+ MIT-licensed. See [LICENSE][LICENSE].
728
+
729
+ [api-versioning]: https://www.sanity.io/docs/api-versioning?utm_source=github&utm_medium=readme&utm_campaign=next-sanity
730
+ [app-router]: https://nextjs.org/docs/app/building-your-application/routing
731
+ [cdn]: https://www.sanity.io/docs/asset-cdn?utm_source=github&utm_medium=readme&utm_campaign=next-sanity
732
+ [ci-workflow]: https://github.com/sanity-io/next-sanity/actions/workflows/ci.yml
733
+ [content-source-maps-intro]: https://www.sanity.io/blog/content-source-maps-announce?utm_source=github&utm_medium=readme&utm_campaign=next-sanity
734
+ [content-source-maps]: https://www.sanity.io/docs/content-source-maps?utm_source=github&utm_medium=readme&utm_campaign=next-sanity
735
+ [draft-mode]: https://nextjs.org/docs/app/building-your-application/configuring/draft-mode
736
+ [embedded-studio-demo]: https://next.sanity.build/studio
737
+ [enterprise-cta]: https://www.sanity.io/enterprise?utm_source=github&utm_medium=readme&utm_campaign=next-sanity
738
+ [groq-syntax-highlighting]: https://marketplace.visualstudio.com/items?itemName=sanity-io.vscode-sanity
739
+ [groq-webhook]: https://www.sanity.io/docs/webhooks?utm_source=github&utm_medium=readme&utm_campaign=next-sanity
740
+ [image-url]: https://www.sanity.io/docs/presenting-images?utm_source=github&utm_medium=readme&utm_campaign=next-sanity
741
+ [LICENSE]: LICENSE
742
+ [migrate-v1-to-v4]: ./MIGRATE-v1-to-v4.md
743
+ [migrate-v4-to-v5-app]: ./MIGRATE-v4-to-v5-pages-app-router.md
744
+ [migrate-v4-to-v5-pages]: ./MIGRATE-v4-to-v5-pages-pages-router.md
745
+ [next-cache]: https://nextjs.org/docs/app/building-your-application/caching
746
+ [next-data-fetching]: https://nextjs.org/docs/basic-features/data-fetching/overview
747
+ [next-preview-mode]: https://nextjs.org/docs/advanced-features/preview-mode
748
+ [pages-router]: https://nextjs.org/docs/pages/building-your-application/routing
749
+ [personal-website-template]: https://github.com/sanity-io/sanity-template-nextjs-app-router-personal-website
750
+ [perspectives-docs]: https://www.sanity.io/docs/perspectives?utm_source=github&utm_medium=readme&utm_campaign=next-sanity
751
+ [portable-text]: https://portabletext.org
752
+ [preivew-app-router]: ./PREVIEW-app-router.md
460
753
  [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
754
+ [preview-kit-documentation]: https://github.com/sanity-io/preview-kit#sanitypreview-kit-1
755
+ [preview-kit-livequery]: https://github.com/sanity-io/preview-kit#using-the-livequery-wrapper-component-instead-of-the-uselivequery-hook
756
+ [preview-kit]: https://github.com/sanity-io/preview-kit
757
+ [preview-pages-router]: ./PREVIEW-pages-router.md
758
+ [revalidate-tag]: https://nextjs.org/docs/app/api-reference/functions/revalidateTag
759
+ [sales-cta]: https://www.sanity.io/contact/sales?utm_source=github&utm_medium=readme&utm_campaign=next-sanity
760
+ [sanity-client]: https://www.sanity.io/docs/js-client?utm_source=github&utm_medium=readme&utm_campaign=next-sanity
761
+ [sanity]: https://www.sanity.io?utm_source=github&utm_medium=readme&utm_campaign=next-sanity
762
+ [visual-editing-intro]: https://www.sanity.io/blog/visual-editing-sanity-vercel?utm_source=github&utm_medium=readme&utm_campaign=next-sanity
763
+ [visual-editing]: https://www.sanity.io/docs/vercel-visual-editing?utm_source=github&utm_medium=readme&utm_campaign=next-sanity
764
+ [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.2",
4
4
  "description": "Sanity.io toolkit for Next.js",
5
5
  "keywords": [
6
6
  "sanity",