next-sanity 4.1.0 → 4.1.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 +97 -60
  2. package/package.json +7 -7
package/README.md CHANGED
@@ -13,20 +13,21 @@
13
13
  - [Table of contents](#table-of-contents)
14
14
  - [Installation](#installation)
15
15
  - [`next-sanity` Running groq queries](#next-sanity-running-groq-queries)
16
+ - [`appDir`, React Server Components and caching](#appdir-react-server-components-and-caching)
16
17
  - [`next-sanity/preview` Live real-time preview](#next-sanitypreview-live-real-time-preview)
17
18
  - [Examples](#examples)
18
19
  - [Built-in Sanity auth](#built-in-sanity-auth)
19
- - [Next 12](#next-12)
20
- - [Next 13 `appDir`](#next-13-appdir)
20
+ - [Using the `/pages` directory](#using-the-pages-directory)
21
+ - [Using the `/app` directory (experimental)](#using-the-app-directory-experimental)
21
22
  - [Custom token auth](#custom-token-auth)
22
- - [Next 12](#next-12-1)
23
- - [Next 13 `appDir`](#next-13-appdir-1)
23
+ - [Using the `/pages` directory](#using-the-pages-directory-1)
24
+ - [Using the `/app` directory (experimental)](#using-the-app-directory-experimental-1)
24
25
  - [Starters](#starters)
25
26
  - [Limits](#limits)
26
27
  - [`next-sanity/studio`](#next-sanitystudio)
27
28
  - [Usage](#usage)
28
- - [Next 13 `app/studio`](#next-13-appstudio)
29
- - [Next 12 or `pages/studio`](#next-12-or-pagesstudio)
29
+ - [Using the `/app` directory (experimental)](#using-the-app-directory-experimental-2)
30
+ - [Using the `/pages` directory](#using-the-pages-directory-2)
30
31
  - [Opt-in to using `StudioProvider` and `StudioLayout`](#opt-in-to-using-studioprovider-and-studiolayout)
31
32
  - [`next-sanity/webhook`](#next-sanitywebhook)
32
33
  - [Migrate](#migrate)
@@ -74,6 +75,34 @@ const client = createClient({
74
75
  const data = await client.fetch(groq`*[]`)
75
76
  ```
76
77
 
78
+ ### `appDir`, React Server Components and caching
79
+
80
+ As `@sanity/client` will only sometimes use `fetch` under the hood, it depends on the environment, [it's best to implement the cache function to ensure reliable caching.](https://beta.nextjs.org/docs/data-fetching/caching#per-request-cachingmd)
81
+
82
+ ```ts
83
+ import {createClient, groq} from 'next-sanity'
84
+ import {cache} from 'react'
85
+
86
+ const projectId = process.env.NEXT_PUBLIC_SANITY_PROJECT_ID // "pv8y60vp"
87
+ const dataset = process.env.NEXT_PUBLIC_SANITY_DATASET // "production"
88
+ const apiVersion = process.env.NEXT_PUBLIC_SANITY_API_VERSION // "2022-11-16"
89
+
90
+ const client = createClient({
91
+ projectId,
92
+ dataset,
93
+ apiVersion, // https://www.sanity.io/docs/api-versioning
94
+ useCdn: typeof document !== 'undefined', // server-side is statically generated, the CDN is only necessary beneficial if queries are called on-demand
95
+ })
96
+
97
+ // Wrap the cache function in a way that reuses the TypeScript definitions
98
+ const clientFetch = cache(client.fetch.bind(client))
99
+
100
+ // Now use it just like before, fully deduped, cached and optimized by react
101
+ const data = await clientFetch(groq`*[]`)
102
+ // You can use the same generics as ebfore
103
+ const total = await clientFetch<number>(groq`count*()`)
104
+ ```
105
+
77
106
  ## `next-sanity/preview` Live real-time preview
78
107
 
79
108
  You can implement real-time client side preview using `definePreview`. It works by streaming the whole dataset to the browser, which it keeps updated using [listeners](https://www.sanity.io/docs/realtime-updates) and Mendoza patches. When it receives updates, then the query is run against the client-side datastore using [groq-js](https://github.com/sanity-io/groq-js).
@@ -99,7 +128,7 @@ Cons:
99
128
  - Safari based browsers (Desktop Safari on Macs, and all browsers on iOS) doesn't work.
100
129
  - Doesn't support incognito browser modes.
101
130
 
102
- `pages/api/preview.js`:
131
+ `pages/api/preview.ts`:
103
132
 
104
133
  ```js
105
134
  export default function preview(req, res) {
@@ -109,7 +138,7 @@ export default function preview(req, res) {
109
138
  }
110
139
  ```
111
140
 
112
- `pages/api/exit-preview.js`:
141
+ `pages/api/exit-preview.ts`:
113
142
 
114
143
  ```js
115
144
  export default function exit(req, res) {
@@ -119,9 +148,9 @@ export default function exit(req, res) {
119
148
  }
120
149
  ```
121
150
 
122
- `components/DocumentsCount.js`:
151
+ `components/DocumentsCount.tsx`:
123
152
 
124
- ```jsx
153
+ ```tsx
125
154
  import groq from 'groq'
126
155
 
127
156
  export const query = groq`count(*[])`
@@ -135,7 +164,7 @@ export function DocumentsCount({data}) {
135
164
  }
136
165
  ```
137
166
 
138
- `lib/sanity.client.js`
167
+ `lib/sanity.client.ts`
139
168
 
140
169
  ```js
141
170
  import {createClient} from 'next-sanity'
@@ -147,11 +176,9 @@ const apiVersion = process.env.NEXT_PUBLIC_SANITY_API_VERSION // "2022-11-16"
147
176
  export const client = createClient({projectId, dataset, apiVersion, useCdn: false})
148
177
  ```
149
178
 
150
- `lib/sanity.preview.js`
179
+ `lib/sanity.preview.ts`
151
180
 
152
181
  ```js
153
- 'use client'
154
-
155
182
  import {definePreview} from 'next-sanity/preview'
156
183
  import {projectId, dataset} from 'lib/sanity.client'
157
184
 
@@ -161,9 +188,9 @@ function onPublicAccessOnly() {
161
188
  export const usePreview = definePreview({projectId, dataset, onPublicAccessOnly})
162
189
  ```
163
190
 
164
- `components/PreviewDocumentsCount.js`:
191
+ `components/PreviewDocumentsCount.ts`:
165
192
 
166
- ```jsx
193
+ ```tsx
167
194
  'use client'
168
195
 
169
196
  import {usePreview} from 'lib/sanity.preview'
@@ -175,11 +202,11 @@ export default function PreviewDocumentsCount() {
175
202
  }
176
203
  ```
177
204
 
178
- ##### Next 12
205
+ ##### Using the `/pages` directory
179
206
 
180
- `pages/index.js`:
207
+ `pages/index.tsx`:
181
208
 
182
- ```jsx
209
+ ```tsx
183
210
  import {PreviewSuspense} from 'next-sanity/preview'
184
211
  import {lazy} from 'react'
185
212
  import {DocumentsCount, query} from 'components/DocumentsCount'
@@ -210,25 +237,31 @@ export default function IndexPage({preview, data}) {
210
237
  }
211
238
  ```
212
239
 
213
- ##### Next 13 `appDir`
240
+ ##### Using the `/app` directory (experimental)
241
+
242
+ We support the new `appDir` mode in Next, [but please note that `appDir` shouldn't be used in production before Vercel says it's stable](https://beta.nextjs.org/docs/getting-started).
214
243
 
215
- `components/PreviewSuspense.js`:
244
+ `components/PreviewSuspense.tsx`:
216
245
 
217
- ```jsx
246
+ ```tsx
218
247
  'use client'
219
248
 
220
249
  // Once rollup supports 'use client' module directives then 'next-sanity' will include them and this re-export will no longer be necessary
221
250
  export {PreviewSuspense as default} from 'next-sanity/preview'
222
251
  ```
223
252
 
224
- `app/page.js`:
253
+ `app/page.tsx`:
225
254
 
226
- ```jsx
255
+ ```tsx
227
256
  import {previewData} from 'next/headers'
228
257
  import PreviewSuspense from 'components/PreviewSuspense'
229
258
  import {DocumentsCount, query} from 'components/DocumentsCount'
230
259
  import PreviewDocumentsCount from 'components/PreviewDocumentsCount'
231
260
  import {client} from 'lib/sanity.client'
261
+ import {cache} from 'react'
262
+
263
+ // Enable NextJS to cache and dedupe queries
264
+ const clientFetch = cache(client.fetch.bind(client))
232
265
 
233
266
  export default async function IndexPage() {
234
267
  if (previewData()) {
@@ -239,7 +272,7 @@ export default async function IndexPage() {
239
272
  )
240
273
  }
241
274
 
242
- const data = await client.fetch(query)
275
+ const data = await clientFetch(query)
243
276
  return <DocumentsCount data={data} />
244
277
  }
245
278
  ```
@@ -261,7 +294,7 @@ Cons:
261
294
  - Like all things with great power comes great responsibility. You're responsible for implementing adequate protection against leaking the `token` in your js bundle, or preventing the `/api/preview?secret=${secret}` from being easily guessable.
262
295
  - It results in a larger JS bundle as `@sanity/groq-store` currently requires `event-source-polyfill` since native `window.EventSource` does not support setting `Authorization` headers needed for the token auth.
263
296
 
264
- `pages/api/preview.js`:
297
+ `pages/api/preview.ts`:
265
298
 
266
299
  ```js
267
300
  import getSecret from 'lib/getSecret'
@@ -282,7 +315,7 @@ export default async function preview(req, res) {
282
315
  }
283
316
  ```
284
317
 
285
- `pages/api/exit-preview.js`:
318
+ `pages/api/exit-preview.ts`:
286
319
 
287
320
  ```js
288
321
  export default function exit(req, res) {
@@ -292,9 +325,9 @@ export default function exit(req, res) {
292
325
  }
293
326
  ```
294
327
 
295
- `components/DocumentsCount.js`:
328
+ `components/DocumentsCount.tsx`:
296
329
 
297
- ```jsx
330
+ ```tsx
298
331
  import groq from 'groq'
299
332
 
300
333
  export const query = groq`count(*[])`
@@ -308,7 +341,7 @@ export function DocumentsCount({data}) {
308
341
  }
309
342
  ```
310
343
 
311
- `lib/sanity.client.js`
344
+ `lib/sanity.client.ts`
312
345
 
313
346
  ```js
314
347
  import {createClient} from 'next-sanity'
@@ -320,20 +353,18 @@ const apiVersion = process.env.NEXT_PUBLIC_SANITY_API_VERSION // "2022-11-16"
320
353
  export const client = createClient({projectId, dataset, apiVersion, useCdn: false})
321
354
  ```
322
355
 
323
- `lib/sanity.preview.js`
356
+ `lib/sanity.preview.ts`
324
357
 
325
358
  ```js
326
- 'use client'
327
-
328
359
  import {definePreview} from 'next-sanity/preview'
329
360
  import {projectId, dataset} from 'lib/sanity.client'
330
361
 
331
362
  export const usePreview = definePreview({projectId, dataset})
332
363
  ```
333
364
 
334
- `components/PreviewDocumentsCount.js`:
365
+ `components/PreviewDocumentsCount.tsx`:
335
366
 
336
- ```jsx
367
+ ```tsx
337
368
  'use client'
338
369
 
339
370
  import {usePreview} from 'lib/sanity.preview'
@@ -345,16 +376,17 @@ export default function PreviewDocumentsCount({token}) {
345
376
  }
346
377
  ```
347
378
 
348
- ##### Next 12
379
+ ##### Using the `/pages` directory
349
380
 
350
- `pages/index.js`:
381
+ `pages/index.tsx`:
351
382
 
352
- ```jsx
383
+ ```tsx
353
384
  import {PreviewSuspense} from 'next-sanity/preview'
354
385
  import {lazy} from 'react'
355
386
  import {DocumentsCount, query} from 'components/DocumentsCount'
356
387
  import {client} from 'lib/sanity.client'
357
388
 
389
+ // Wrapping preview components in React.lazy ensures visitors not on preview mode doesn't load any JS related to it
358
390
  const PreviewDocumentsCount = lazy(() => import('components/PreviewDocumentsCount'))
359
391
 
360
392
  export const getStaticProps = async ({preview = false, previewData = {}}) => {
@@ -380,31 +412,34 @@ export default function IndexPage({preview, token, data}) {
380
412
  }
381
413
  ```
382
414
 
383
- ##### Next 13 `appDir`
415
+ ##### Using the `/app` directory (experimental)
416
+
417
+ We support the new `appDir` mode in Next, [but please note that `appDir` shouldn't be used in production before Vercel says it's stable](https://beta.nextjs.org/docs/getting-started).
384
418
 
385
- `components/PreviewSuspense.js`:
419
+ `components/PreviewSuspense.tsx`:
386
420
 
387
- ```jsx
421
+ ```tsx
388
422
  'use client'
389
423
 
390
424
  // Once rollup supports 'use client' module directives then 'next-sanity' will include them and this re-export will no longer be necessary
391
425
  export {PreviewSuspense as default} from 'next-sanity/preview'
392
426
  ```
393
427
 
394
- `app/page.js`:
428
+ `app/page.tsx`:
395
429
 
396
- ```jsx
430
+ ```tsx
397
431
  import {previewData} from 'next/headers'
398
432
  import PreviewSuspense from 'components/PreviewSuspense'
399
433
  import {DocumentsCount, query} from 'components/DocumentsCount'
400
434
  import PreviewDocumentsCount from 'components/PreviewDocumentsCount'
401
435
  import {client} from 'lib/sanity.client'
402
436
 
437
+ type AppPreviewData = {token: string} | undefined
403
438
  export default async function IndexPage() {
404
- if (previewData()?.token) {
439
+ if ((previewData() as AppPreviewData)?.token) {
405
440
  return (
406
441
  <PreviewSuspense fallback="Loading...">
407
- <PreviewDocumentsCount token={previewData().token} />
442
+ <PreviewDocumentsCount token={(previewData() as AppPreviewData).token} />
408
443
  </PreviewSuspense>
409
444
  )
410
445
  }
@@ -456,7 +491,7 @@ The basic setup is 2 components, `NextStudio` and `NextStudioHead`.
456
491
  `NextStudio` loads up the `import {Studio} from 'sanity'` component for you and wraps it in a Next-friendly layout.
457
492
  While `NextStudioHead` sets necessary `<head>` meta tags such as `<meta name="viewport">` to ensure the responsive CSS in the Studio works as expected.
458
493
 
459
- Both the Next 13 and 12 examples uses this config file:
494
+ Both the Next `/app` and `/pages` examples uses this config file:
460
495
  `sanity.config.ts`:
461
496
 
462
497
  ```ts
@@ -501,9 +536,11 @@ export default defineCliConfig({api: {projectId, dataset}})
501
536
 
502
537
  Now you can run commands like `npx sanity cors add`. See `npx sanity help` for a full list of what you can do.
503
538
 
504
- #### Next 13 `app/studio`
539
+ #### Using the `/app` directory (experimental)
540
+
541
+ We support the new `appDir` mode in Next, [but please note that `appDir` shouldn't be used in production before Vercel says it's stable](https://beta.nextjs.org/docs/getting-started).
505
542
 
506
- In Next 13's `appDir` mode you use `page.tsx` to load `NextStudio`, and optionally (recommended, especially if you want great support for iPhones and other devices with display cutouts like "The Notch" or "Dynamic Island") export `NextStudioHead` in a `head.tsx`.
543
+ In Next 13's new `appDir` mode you use `page.tsx` to load `NextStudio`, and optionally (recommended, especially if you want great support for iPhones and other devices with display cutouts like "The Notch" or "Dynamic Island") export `NextStudioHead` in a `head.tsx`.
507
544
  In routes that load `NextStudio` ensure you have `'use client'` at the top of your file.
508
545
 
509
546
  `app/studio/[[...index]]/page.tsx`:
@@ -560,7 +597,7 @@ export default function Loading() {
560
597
  }
561
598
  ```
562
599
 
563
- #### Next 12 or `pages/studio`
600
+ #### Using the `/pages` directory
564
601
 
565
602
  Using just `NextStudio` gives you a fully working Sanity Studio v3. However we recommend also using `NextStudioHead` as it ensures CSS Media Queries that target mobile devices with display cutouts (for example iPhone's "The Notch" and "Dynamic Island") and other details.
566
603
 
@@ -651,7 +688,7 @@ The `v3` release only contains breaking changes on the `next-sanity/studio` impo
651
688
 
652
689
  #### `NextStudioGlobalStyle` is removed
653
690
 
654
- The layout is no longer using global CSS to set the Studio height. The switch to local CSS helps interop between Next 12 and 13 layouts.
691
+ The layout is no longer using global CSS to set the Studio height. The switch to local CSS helps interop between Next `/pages` and `/app` layouts.
655
692
 
656
693
  #### `ServerStyleSheetDocument` is removed
657
694
 
@@ -684,7 +721,7 @@ export default function MyComponent(props: Pick<StudioProps, 'config'>) {
684
721
  }
685
722
  ```
686
723
 
687
- The reason why `useBasePath` and `useConfigWithBasePath` got removed is because Next 12 and 13 diverge too much in how they declare dynamic segments. Thus you'll need to specify `basePath` in your `sanity.config.ts` manually to match the route you're loading the studio, for the time being.
724
+ The reason why `useBasePath` and `useConfigWithBasePath` got removed is because Next `/pages` and `/app` diverge too much in how they declare dynamic segments. Thus you'll need to specify `basePath` in your `sanity.config.ts` manually to match the route you're loading the studio, for the time being.
688
725
 
689
726
  #### The `NextStudioHead` component has moved from `next-sanity/studio` to `next-sanity/studio/head`
690
727
 
@@ -698,11 +735,11 @@ There are several differences between the hooks. First of all, `definePreview` r
698
735
 
699
736
  ##### Before
700
737
 
701
- The files that are imported here are the same as the [Next 12 example](#next-12).
738
+ The files that are imported here are the same as the [Next `/pages` example](#using-the-pages-director).
702
739
 
703
- `pages/index.js`
740
+ `pages/index.tsx`
704
741
 
705
- ```jsx
742
+ ```tsx
706
743
  import {createPreviewSubscriptionHook} from 'next-sanity'
707
744
  import {DocumentsCount, query} from 'components/DocumentsCount'
708
745
  import {client, projectId, dataset} from 'lib/sanity.client'
@@ -722,9 +759,9 @@ export default function IndexPage({preview, data: initialData}) {
722
759
 
723
760
  ##### After
724
761
 
725
- `components/PreviewDocumentsCount.js`
762
+ `components/PreviewDocumentsCount.tsx`
726
763
 
727
- ```jsx
764
+ ```tsx
728
765
  import {definePreview} from 'next-sanity/preview'
729
766
  import {projectId, dataset} from 'lib/sanity.client'
730
767
 
@@ -735,9 +772,9 @@ export default function PreviewDocumentsCount() {
735
772
  }
736
773
  ```
737
774
 
738
- `pages/index.js`
775
+ `pages/index.tsx`
739
776
 
740
- ```jsx
777
+ ```tsx
741
778
  import {lazy} from 'react'
742
779
  import {PreviewSuspense} from 'next-sanity/preview'
743
780
  import {DocumentsCount, query} from 'components/DocumentsCount'
@@ -767,7 +804,7 @@ export default function IndexPage({preview, data}) {
767
804
 
768
805
  If you used this hook to check if the user is cookie authenticated:
769
806
 
770
- ```jsx
807
+ ```tsx
771
808
  import {createCurrentUserHook} from 'next-sanity'
772
809
 
773
810
  const projectId = process.env.NEXT_PUBLIC_SANITY_PROJECT_ID
@@ -784,7 +821,7 @@ export default function Page() {
784
821
 
785
822
  Then you can achieve the same functionality using `@sanity/preview-kit` and `suspend-react`:
786
823
 
787
- ```jsx
824
+ ```tsx
788
825
  import {suspend} from 'suspend-react'
789
826
  import {_checkAuth} from '@sanity/preview-kit'
790
827
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "next-sanity",
3
- "version": "4.1.0",
3
+ "version": "4.1.1",
4
4
  "description": "Sanity.io toolkit for Next.js",
5
5
  "keywords": [
6
6
  "sanity",
@@ -147,27 +147,27 @@
147
147
  },
148
148
  "dependencies": {
149
149
  "@sanity/client": "^4",
150
- "@sanity/preview-kit": "^1.2",
150
+ "@sanity/preview-kit": "^1.3",
151
151
  "@sanity/webhook": "^2",
152
152
  "groq": "^3"
153
153
  },
154
154
  "devDependencies": {
155
155
  "@rollup/plugin-url": "^8.0.1",
156
156
  "@sanity/eslint-config-studio": "^2.0.1",
157
- "@sanity/image-url": "^1.0.1",
157
+ "@sanity/image-url": "^1.0.2",
158
158
  "@sanity/pkg-utils": "^2.2.1",
159
159
  "@sanity/semantic-release-preset": "^3.0.2",
160
160
  "@sanity/ui": "^1.0.14",
161
161
  "@sanity/vision": "^3.2.4",
162
162
  "@types/eventsource": "^1.1.10",
163
- "@types/jest": "^29.2.5",
164
- "@types/react": "^18.0.26",
163
+ "@types/jest": "^29.2.6",
164
+ "@types/react": "^18.0.27",
165
165
  "@types/react-dom": "^18.0.10",
166
166
  "@types/styled-components": "^5.1.26",
167
167
  "@typescript-eslint/eslint-plugin": "^5.48.2",
168
168
  "autoprefixer": "^10.4.13",
169
169
  "eslint": "^8.32.0",
170
- "eslint-config-next": "13.1.3-canary.2",
170
+ "eslint-config-next": "13.1.3-canary.5",
171
171
  "eslint-config-prettier": "^8.6.0",
172
172
  "eslint-config-sanity": "^6.0.0",
173
173
  "eslint-gitignore": "^0.1.0",
@@ -177,7 +177,7 @@
177
177
  "jest": "^29.3.1",
178
178
  "jest-environment-jsdom": "^29.3.1",
179
179
  "ls-engines": "^0.8.0",
180
- "next": "13.1.3-canary.2",
180
+ "next": "13.1.3-canary.5",
181
181
  "postcss": "^8.4.21",
182
182
  "prettier": "^2.8.3",
183
183
  "prettier-plugin-packagejson": "^2.4.0",