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