|
121 | 121 |
|
122 | 122 | ```ts |
123 | 123 | // sanity.config.ts |
124 | | - import { |
125 | | - defineArrayMember, |
126 | | - defineConfig, |
127 | | - defineField, |
128 | | - defineType, |
129 | | - } from "sanity"; |
| 124 | + import {defineArrayMember, defineConfig, defineField, defineType} from 'sanity' |
130 | 125 |
|
131 | 126 | export default defineConfig({ |
132 | | - name: "default", |
133 | | - projectId: "abc123", |
134 | | - dataset: "production", |
| 127 | + name: 'default', |
| 128 | + projectId: 'abc123', |
| 129 | + dataset: 'production', |
135 | 130 | schema: { |
136 | 131 | types: [ |
137 | 132 | defineType({ |
138 | | - name: "post", |
139 | | - type: "document", |
| 133 | + name: 'post', |
| 134 | + type: 'document', |
140 | 135 | fields: [ |
141 | | - defineField({ name: "title", type: "string" }), |
| 136 | + defineField({name: 'title', type: 'string'}), |
142 | 137 | defineField({ |
143 | | - name: "content", |
144 | | - type: "array", |
| 138 | + name: 'content', |
| 139 | + type: 'array', |
145 | 140 | of: [ |
146 | | - defineArrayMember({ type: "block" }), |
| 141 | + defineArrayMember({type: 'block'}), |
147 | 142 | defineArrayMember({ |
148 | | - type: "image", |
149 | | - options: { hotspot: true }, |
150 | | - fields: [defineField({ name: "alt", type: "string" })], |
| 143 | + type: 'image', |
| 144 | + options: {hotspot: true}, |
| 145 | + fields: [defineField({name: 'alt', type: 'string'})], |
151 | 146 | }), |
152 | 147 | ], |
153 | 148 | }), |
154 | 149 | ], |
155 | 150 | }), |
156 | 151 | ], |
157 | 152 | }, |
158 | | - }); |
| 153 | + }) |
159 | 154 | ``` |
160 | 155 |
|
161 | 156 | #### Before: hand-typing handlers |
|
164 | 159 |
|
165 | 160 | ```tsx |
166 | 161 | // app/[slug]/page.tsx |
167 | | - import { createClient } from "@sanity/client"; |
168 | | - import { createImageUrlBuilder } from "@sanity/image-url"; |
169 | | - import { PortableText } from "@portabletext/react"; |
170 | | - import { defineQuery } from "groq"; |
| 162 | + import {createClient} from '@sanity/client' |
| 163 | + import {createImageUrlBuilder} from '@sanity/image-url' |
| 164 | + import {PortableText} from '@portabletext/react' |
| 165 | + import {defineQuery} from 'groq' |
171 | 166 |
|
172 | 167 | const client = createClient({ |
173 | | - projectId: "abc123", |
174 | | - dataset: "production", |
| 168 | + projectId: 'abc123', |
| 169 | + dataset: 'production', |
175 | 170 | useCdn: true, |
176 | | - apiVersion: "2026-05-04", |
177 | | - }); |
178 | | - const builder = createImageUrlBuilder(client); |
| 171 | + apiVersion: '2026-05-04', |
| 172 | + }) |
| 173 | + const builder = createImageUrlBuilder(client) |
179 | 174 |
|
180 | | - export default async function Page({ slug }: { slug: string }) { |
181 | | - const query = defineQuery( |
182 | | - `*[_type == "post" && slug.current == $slug][0]{title,content}` |
183 | | - ); |
184 | | - const data = await client.fetch(query, { slug }); |
| 175 | + export default async function Page({slug}: {slug: string}) { |
| 176 | + const query = defineQuery(`*[_type == "post" && slug.current == $slug][0]{title,content}`) |
| 177 | + const data = await client.fetch(query, {slug}) |
185 | 178 |
|
186 | | - if (!data) return notFound(); |
| 179 | + if (!data) return notFound() |
187 | 180 |
|
188 | 181 | return ( |
189 | 182 | <article> |
|
196 | 189 | }: { |
197 | 190 | value: { |
198 | 191 | asset?: { |
199 | | - _ref: string; |
200 | | - _type: "reference"; |
201 | | - _weak?: boolean; |
202 | | - }; |
| 192 | + _ref: string |
| 193 | + _type: 'reference' |
| 194 | + _weak?: boolean |
| 195 | + } |
203 | 196 | hotspot?: { |
204 | | - _type: "sanity.imageHotspot"; |
205 | | - x?: number; |
206 | | - y?: number; |
207 | | - height?: number; |
208 | | - width?: number; |
209 | | - }; |
| 197 | + _type: 'sanity.imageHotspot' |
| 198 | + x?: number |
| 199 | + y?: number |
| 200 | + height?: number |
| 201 | + width?: number |
| 202 | + } |
210 | 203 | crop?: { |
211 | | - _type: "sanity.imageCrop"; |
212 | | - top?: number; |
213 | | - bottom?: number; |
214 | | - left?: number; |
215 | | - right?: number; |
216 | | - }; |
217 | | - alt?: string; |
218 | | - _type: "image"; |
219 | | - _key: string; |
220 | | - }; |
221 | | - }) => ( |
222 | | - <img src={builder.image(value).url()} alt={value.alt || ""} /> |
223 | | - ), |
| 204 | + _type: 'sanity.imageCrop' |
| 205 | + top?: number |
| 206 | + bottom?: number |
| 207 | + left?: number |
| 208 | + right?: number |
| 209 | + } |
| 210 | + alt?: string |
| 211 | + _type: 'image' |
| 212 | + _key: string |
| 213 | + } |
| 214 | + }) => <img src={builder.image(value).url()} alt={value.alt || ''} />, |
224 | 215 | }, |
225 | 216 | }} |
226 | 217 | value={data.content} |
227 | 218 | /> |
228 | 219 | </article> |
229 | | - ); |
| 220 | + ) |
230 | 221 | } |
231 | 222 | ``` |
232 | 223 |
|
|
236 | 227 |
|
237 | 228 | ```tsx |
238 | 229 | // app/[slug]/page.tsx |
239 | | - import { createClient } from "@sanity/client"; |
240 | | - import { createImageUrlBuilder } from "@sanity/image-url"; |
241 | | - import { PortableText } from "@portabletext/react"; |
242 | | - import { defineQuery } from "groq"; |
| 230 | + import {createClient} from '@sanity/client' |
| 231 | + import {createImageUrlBuilder} from '@sanity/image-url' |
| 232 | + import {PortableText} from '@portabletext/react' |
| 233 | + import {defineQuery} from 'groq' |
243 | 234 |
|
244 | 235 | const client = createClient({ |
245 | | - projectId: "abc123", |
246 | | - dataset: "production", |
| 236 | + projectId: 'abc123', |
| 237 | + dataset: 'production', |
247 | 238 | useCdn: true, |
248 | | - apiVersion: "2026-05-04", |
249 | | - }); |
250 | | - const builder = createImageUrlBuilder(client); |
| 239 | + apiVersion: '2026-05-04', |
| 240 | + }) |
| 241 | + const builder = createImageUrlBuilder(client) |
251 | 242 |
|
252 | | - export default async function Page({ slug }: { slug: string }) { |
253 | | - const query = defineQuery( |
254 | | - `*[_type == "post" && slug.current == $slug][0]{title,content}` |
255 | | - ); |
256 | | - const data = await client.fetch(query, { slug }); |
| 243 | + export default async function Page({slug}: {slug: string}) { |
| 244 | + const query = defineQuery(`*[_type == "post" && slug.current == $slug][0]{title,content}`) |
| 245 | + const data = await client.fetch(query, {slug}) |
257 | 246 |
|
258 | | - if (!data) return notFound(); |
| 247 | + if (!data) return notFound() |
259 | 248 |
|
260 | 249 | return ( |
261 | 250 | <article> |
|
264 | 253 | components={{ |
265 | 254 | types: { |
266 | 255 | // value is fully typed from the query result, no annotation needed |
267 | | - image: ({ value }) => ( |
268 | | - <img src={builder.image(value).url()} alt={value.alt || ""} /> |
269 | | - ), |
| 256 | + image: ({value}) => <img src={builder.image(value).url()} alt={value.alt || ''} />, |
270 | 257 | }, |
271 | 258 | }} |
272 | 259 | value={data.content} |
273 | 260 | /> |
274 | 261 | </article> |
275 | | - ); |
| 262 | + ) |
276 | 263 | } |
277 | 264 | ``` |
278 | 265 |
|
|
282 | 269 |
|
283 | 270 | ```tsx |
284 | 271 | // app/[slug]/page.tsx |
285 | | - import { createClient } from "@sanity/client"; |
286 | | - import { createImageUrlBuilder } from "@sanity/image-url"; |
287 | | - import { PortableText, type InferComponents } from "@portabletext/react"; |
288 | | - import { defineQuery } from "groq"; |
| 272 | + import {createClient} from '@sanity/client' |
| 273 | + import {createImageUrlBuilder} from '@sanity/image-url' |
| 274 | + import {PortableText, type InferComponents} from '@portabletext/react' |
| 275 | + import {defineQuery} from 'groq' |
289 | 276 |
|
290 | 277 | const client = createClient({ |
291 | | - projectId: "abc123", |
292 | | - dataset: "production", |
| 278 | + projectId: 'abc123', |
| 279 | + dataset: 'production', |
293 | 280 | useCdn: true, |
294 | | - apiVersion: "2026-05-04", |
295 | | - }); |
296 | | - const builder = createImageUrlBuilder(client); |
| 281 | + apiVersion: '2026-05-04', |
| 282 | + }) |
| 283 | + const builder = createImageUrlBuilder(client) |
297 | 284 |
|
298 | | - export default async function Page({ slug }: { slug: string }) { |
299 | | - const query = defineQuery( |
300 | | - `*[_type == "post" && slug.current == $slug][0]{title,content}` |
301 | | - ); |
302 | | - const data = await client.fetch(query, { slug }); |
| 285 | + export default async function Page({slug}: {slug: string}) { |
| 286 | + const query = defineQuery(`*[_type == "post" && slug.current == $slug][0]{title,content}`) |
| 287 | + const data = await client.fetch(query, {slug}) |
303 | 288 |
|
304 | | - if (!data) return notFound(); |
| 289 | + if (!data) return notFound() |
305 | 290 |
|
306 | 291 | const components = { |
307 | 292 | types: { |
308 | | - image: ({ value }) => ( |
309 | | - <img src={builder.image(value).url()} alt={value.alt || ""} /> |
310 | | - ), |
| 293 | + image: ({value}) => <img src={builder.image(value).url()} alt={value.alt || ''} />, |
311 | 294 | }, |
312 | | - } satisfies InferComponents<typeof data.content>; |
| 295 | + } satisfies InferComponents<typeof data.content> |
313 | 296 |
|
314 | 297 | return ( |
315 | 298 | <article> |
316 | 299 | <h1>{data.title}</h1> |
317 | 300 | <PortableText components={components} value={data.content} /> |
318 | 301 | </article> |
319 | | - ); |
| 302 | + ) |
320 | 303 | } |
321 | 304 | ``` |
322 | 305 |
|
|
326 | 309 |
|
327 | 310 | ```tsx |
328 | 311 | // app/[slug]/page.tsx |
329 | | - import { createClient, type SanityQueries } from "@sanity/client"; |
330 | | - import { createImageUrlBuilder } from "@sanity/image-url"; |
331 | | - import { |
332 | | - PortableText, |
333 | | - type InferStrictComponents, |
334 | | - type InferValue, |
335 | | - } from "@portabletext/react"; |
336 | | - import { defineQuery } from "groq"; |
| 312 | + import {createClient, type SanityQueries} from '@sanity/client' |
| 313 | + import {createImageUrlBuilder} from '@sanity/image-url' |
| 314 | + import {PortableText, type InferStrictComponents, type InferValue} from '@portabletext/react' |
| 315 | + import {defineQuery} from 'groq' |
337 | 316 |
|
338 | 317 | const client = createClient({ |
339 | | - projectId: "abc123", |
340 | | - dataset: "production", |
| 318 | + projectId: 'abc123', |
| 319 | + dataset: 'production', |
341 | 320 | useCdn: true, |
342 | | - apiVersion: "2026-05-04", |
343 | | - }); |
344 | | - const builder = createImageUrlBuilder(client); |
| 321 | + apiVersion: '2026-05-04', |
| 322 | + }) |
| 323 | + const builder = createImageUrlBuilder(client) |
345 | 324 |
|
346 | 325 | // Array value type for every Portable Text item shape across all registered queries. |
347 | | - type PortableTextValue = InferValue<SanityQueries[keyof SanityQueries]>; |
| 326 | + type PortableTextValue = InferValue<SanityQueries[keyof SanityQueries]> |
348 | 327 |
|
349 | | - function CustomPortableText({ value }: { value: PortableTextValue }) { |
| 328 | + function CustomPortableText({value}: {value: PortableTextValue}) { |
350 | 329 | const components = { |
351 | 330 | types: { |
352 | | - image: ({ value }) => ( |
353 | | - <img src={builder.image(value).url()} alt={value.alt || ""} /> |
354 | | - ), |
| 331 | + image: ({value}) => <img src={builder.image(value).url()} alt={value.alt || ''} />, |
355 | 332 | }, |
356 | | - } satisfies InferStrictComponents<PortableTextValue>; |
| 333 | + } satisfies InferStrictComponents<PortableTextValue> |
357 | 334 | // ^ TypeScript errors when the schema gains a custom type, mark, or list |
358 | 335 | // style without a matching handler defined here |
359 | 336 |
|
360 | | - return <PortableText components={components} value={value} />; |
| 337 | + return <PortableText components={components} value={value} /> |
361 | 338 | } |
362 | 339 |
|
363 | | - export default async function Page({ slug }: { slug: string }) { |
364 | | - const query = defineQuery( |
365 | | - `*[_type == "post" && slug.current == $slug][0]{title,content}` |
366 | | - ); |
367 | | - const data = await client.fetch(query, { slug }); |
| 340 | + export default async function Page({slug}: {slug: string}) { |
| 341 | + const query = defineQuery(`*[_type == "post" && slug.current == $slug][0]{title,content}`) |
| 342 | + const data = await client.fetch(query, {slug}) |
368 | 343 |
|
369 | | - if (!data) return notFound(); |
| 344 | + if (!data) return notFound() |
370 | 345 |
|
371 | 346 | return ( |
372 | 347 | <article> |
373 | 348 | <h1>{data.title}</h1> |
374 | | - {Array.isArray(data.content) && ( |
375 | | - <CustomPortableText value={data.content} /> |
376 | | - )} |
| 349 | + {Array.isArray(data.content) && <CustomPortableText value={data.content} />} |
377 | 350 | </article> |
378 | | - ); |
| 351 | + ) |
379 | 352 | } |
380 | 353 | ``` |
381 | 354 |
|
|
0 commit comments