|
44 | 44 |
|
45 | 45 | ```ts |
46 | 46 | // sanity.config.ts |
47 | | - import { |
48 | | - defineArrayMember, |
49 | | - defineConfig, |
50 | | - defineField, |
51 | | - defineType, |
52 | | - } from "sanity"; |
| 47 | + import {defineArrayMember, defineConfig, defineField, defineType} from 'sanity' |
53 | 48 |
|
54 | 49 | export default defineConfig({ |
55 | | - name: "default", |
56 | | - projectId: "abc123", |
57 | | - dataset: "production", |
| 50 | + name: 'default', |
| 51 | + projectId: 'abc123', |
| 52 | + dataset: 'production', |
58 | 53 | schema: { |
59 | 54 | types: [ |
60 | 55 | defineType({ |
61 | | - name: "post", |
62 | | - type: "document", |
| 56 | + name: 'post', |
| 57 | + type: 'document', |
63 | 58 | fields: [ |
64 | | - defineField({ name: "title", type: "string" }), |
| 59 | + defineField({name: 'title', type: 'string'}), |
65 | 60 | defineField({ |
66 | | - name: "content", |
67 | | - type: "array", |
| 61 | + name: 'content', |
| 62 | + type: 'array', |
68 | 63 | of: [ |
69 | | - defineArrayMember({ type: "block" }), |
| 64 | + defineArrayMember({type: 'block'}), |
70 | 65 | defineArrayMember({ |
71 | | - type: "image", |
72 | | - options: { hotspot: true }, |
73 | | - fields: [defineField({ name: "alt", type: "string" })], |
| 66 | + type: 'image', |
| 67 | + options: {hotspot: true}, |
| 68 | + fields: [defineField({name: 'alt', type: 'string'})], |
74 | 69 | }), |
75 | 70 | ], |
76 | 71 | }), |
77 | 72 | ], |
78 | 73 | }), |
79 | 74 | ], |
80 | 75 | }, |
81 | | - }); |
| 76 | + }) |
82 | 77 | ``` |
83 | 78 |
|
84 | 79 | #### Before: hand-typing handlers |
|
87 | 82 |
|
88 | 83 | ```tsx |
89 | 84 | // app/[slug]/page.tsx |
90 | | - import { createClient } from "@sanity/client"; |
91 | | - import { createImageUrlBuilder } from "@sanity/image-url"; |
92 | | - import { PortableText } from "@portabletext/react"; |
93 | | - import { defineQuery } from "groq"; |
| 85 | + import {createClient} from '@sanity/client' |
| 86 | + import {createImageUrlBuilder} from '@sanity/image-url' |
| 87 | + import {PortableText} from '@portabletext/react' |
| 88 | + import {defineQuery} from 'groq' |
94 | 89 |
|
95 | 90 | const client = createClient({ |
96 | | - projectId: "abc123", |
97 | | - dataset: "production", |
| 91 | + projectId: 'abc123', |
| 92 | + dataset: 'production', |
98 | 93 | useCdn: true, |
99 | | - apiVersion: "2026-05-04", |
100 | | - }); |
101 | | - const builder = createImageUrlBuilder(client); |
| 94 | + apiVersion: '2026-05-04', |
| 95 | + }) |
| 96 | + const builder = createImageUrlBuilder(client) |
102 | 97 |
|
103 | | - export default async function Page({ slug }: { slug: string }) { |
104 | | - const query = defineQuery( |
105 | | - `*[_type == "post" && slug.current == $slug][0]{title,content}` |
106 | | - ); |
107 | | - const data = await client.fetch(query, { slug }); |
| 98 | + export default async function Page({slug}: {slug: string}) { |
| 99 | + const query = defineQuery(`*[_type == "post" && slug.current == $slug][0]{title,content}`) |
| 100 | + const data = await client.fetch(query, {slug}) |
108 | 101 |
|
109 | | - if (!data) return notFound(); |
| 102 | + if (!data) return notFound() |
110 | 103 |
|
111 | 104 | return ( |
112 | 105 | <article> |
|
119 | 112 | }: { |
120 | 113 | value: { |
121 | 114 | asset?: { |
122 | | - _ref: string; |
123 | | - _type: "reference"; |
124 | | - _weak?: boolean; |
125 | | - }; |
| 115 | + _ref: string |
| 116 | + _type: 'reference' |
| 117 | + _weak?: boolean |
| 118 | + } |
126 | 119 | hotspot?: { |
127 | | - _type: "sanity.imageHotspot"; |
128 | | - x?: number; |
129 | | - y?: number; |
130 | | - height?: number; |
131 | | - width?: number; |
132 | | - }; |
| 120 | + _type: 'sanity.imageHotspot' |
| 121 | + x?: number |
| 122 | + y?: number |
| 123 | + height?: number |
| 124 | + width?: number |
| 125 | + } |
133 | 126 | crop?: { |
134 | | - _type: "sanity.imageCrop"; |
135 | | - top?: number; |
136 | | - bottom?: number; |
137 | | - left?: number; |
138 | | - right?: number; |
139 | | - }; |
140 | | - alt?: string; |
141 | | - _type: "image"; |
142 | | - _key: string; |
143 | | - }; |
144 | | - }) => ( |
145 | | - <img src={builder.image(value).url()} alt={value.alt || ""} /> |
146 | | - ), |
| 127 | + _type: 'sanity.imageCrop' |
| 128 | + top?: number |
| 129 | + bottom?: number |
| 130 | + left?: number |
| 131 | + right?: number |
| 132 | + } |
| 133 | + alt?: string |
| 134 | + _type: 'image' |
| 135 | + _key: string |
| 136 | + } |
| 137 | + }) => <img src={builder.image(value).url()} alt={value.alt || ''} />, |
147 | 138 | }, |
148 | 139 | }} |
149 | 140 | value={data.content} |
150 | 141 | /> |
151 | 142 | </article> |
152 | | - ); |
| 143 | + ) |
153 | 144 | } |
154 | 145 | ``` |
155 | 146 |
|
|
159 | 150 |
|
160 | 151 | ```tsx |
161 | 152 | // app/[slug]/page.tsx |
162 | | - import { createClient } from "@sanity/client"; |
163 | | - import { createImageUrlBuilder } from "@sanity/image-url"; |
164 | | - import { PortableText } from "@portabletext/react"; |
165 | | - import { defineQuery } from "groq"; |
| 153 | + import {createClient} from '@sanity/client' |
| 154 | + import {createImageUrlBuilder} from '@sanity/image-url' |
| 155 | + import {PortableText} from '@portabletext/react' |
| 156 | + import {defineQuery} from 'groq' |
166 | 157 |
|
167 | 158 | const client = createClient({ |
168 | | - projectId: "abc123", |
169 | | - dataset: "production", |
| 159 | + projectId: 'abc123', |
| 160 | + dataset: 'production', |
170 | 161 | useCdn: true, |
171 | | - apiVersion: "2026-05-04", |
172 | | - }); |
173 | | - const builder = createImageUrlBuilder(client); |
| 162 | + apiVersion: '2026-05-04', |
| 163 | + }) |
| 164 | + const builder = createImageUrlBuilder(client) |
174 | 165 |
|
175 | | - export default async function Page({ slug }: { slug: string }) { |
176 | | - const query = defineQuery( |
177 | | - `*[_type == "post" && slug.current == $slug][0]{title,content}` |
178 | | - ); |
179 | | - const data = await client.fetch(query, { slug }); |
| 166 | + export default async function Page({slug}: {slug: string}) { |
| 167 | + const query = defineQuery(`*[_type == "post" && slug.current == $slug][0]{title,content}`) |
| 168 | + const data = await client.fetch(query, {slug}) |
180 | 169 |
|
181 | | - if (!data) return notFound(); |
| 170 | + if (!data) return notFound() |
182 | 171 |
|
183 | 172 | return ( |
184 | 173 | <article> |
|
187 | 176 | components={{ |
188 | 177 | types: { |
189 | 178 | // value is fully typed from the query result, no annotation needed |
190 | | - image: ({ value }) => ( |
191 | | - <img src={builder.image(value).url()} alt={value.alt || ""} /> |
192 | | - ), |
| 179 | + image: ({value}) => <img src={builder.image(value).url()} alt={value.alt || ''} />, |
193 | 180 | }, |
194 | 181 | }} |
195 | 182 | value={data.content} |
196 | 183 | /> |
197 | 184 | </article> |
198 | | - ); |
| 185 | + ) |
199 | 186 | } |
200 | 187 | ``` |
201 | 188 |
|
|
205 | 192 |
|
206 | 193 | ```tsx |
207 | 194 | // app/[slug]/page.tsx |
208 | | - import { createClient } from "@sanity/client"; |
209 | | - import { createImageUrlBuilder } from "@sanity/image-url"; |
210 | | - import { PortableText, type InferComponents } from "@portabletext/react"; |
211 | | - import { defineQuery } from "groq"; |
| 195 | + import {createClient} from '@sanity/client' |
| 196 | + import {createImageUrlBuilder} from '@sanity/image-url' |
| 197 | + import {PortableText, type InferComponents} from '@portabletext/react' |
| 198 | + import {defineQuery} from 'groq' |
212 | 199 |
|
213 | 200 | const client = createClient({ |
214 | | - projectId: "abc123", |
215 | | - dataset: "production", |
| 201 | + projectId: 'abc123', |
| 202 | + dataset: 'production', |
216 | 203 | useCdn: true, |
217 | | - apiVersion: "2026-05-04", |
218 | | - }); |
219 | | - const builder = createImageUrlBuilder(client); |
| 204 | + apiVersion: '2026-05-04', |
| 205 | + }) |
| 206 | + const builder = createImageUrlBuilder(client) |
220 | 207 |
|
221 | | - export default async function Page({ slug }: { slug: string }) { |
222 | | - const query = defineQuery( |
223 | | - `*[_type == "post" && slug.current == $slug][0]{title,content}` |
224 | | - ); |
225 | | - const data = await client.fetch(query, { slug }); |
| 208 | + export default async function Page({slug}: {slug: string}) { |
| 209 | + const query = defineQuery(`*[_type == "post" && slug.current == $slug][0]{title,content}`) |
| 210 | + const data = await client.fetch(query, {slug}) |
226 | 211 |
|
227 | | - if (!data) return notFound(); |
| 212 | + if (!data) return notFound() |
228 | 213 |
|
229 | 214 | const components = { |
230 | 215 | types: { |
231 | | - image: ({ value }) => ( |
232 | | - <img src={builder.image(value).url()} alt={value.alt || ""} /> |
233 | | - ), |
| 216 | + image: ({value}) => <img src={builder.image(value).url()} alt={value.alt || ''} />, |
234 | 217 | }, |
235 | | - } satisfies InferComponents<typeof data.content>; |
| 218 | + } satisfies InferComponents<typeof data.content> |
236 | 219 |
|
237 | 220 | return ( |
238 | 221 | <article> |
239 | 222 | <h1>{data.title}</h1> |
240 | 223 | <PortableText components={components} value={data.content} /> |
241 | 224 | </article> |
242 | | - ); |
| 225 | + ) |
243 | 226 | } |
244 | 227 | ``` |
245 | 228 |
|
|
249 | 232 |
|
250 | 233 | ```tsx |
251 | 234 | // app/[slug]/page.tsx |
252 | | - import { createClient, type SanityQueries } from "@sanity/client"; |
253 | | - import { createImageUrlBuilder } from "@sanity/image-url"; |
254 | | - import { |
255 | | - PortableText, |
256 | | - type InferStrictComponents, |
257 | | - type InferValue, |
258 | | - } from "@portabletext/react"; |
259 | | - import { defineQuery } from "groq"; |
| 235 | + import {createClient, type SanityQueries} from '@sanity/client' |
| 236 | + import {createImageUrlBuilder} from '@sanity/image-url' |
| 237 | + import {PortableText, type InferStrictComponents, type InferValue} from '@portabletext/react' |
| 238 | + import {defineQuery} from 'groq' |
260 | 239 |
|
261 | 240 | const client = createClient({ |
262 | | - projectId: "abc123", |
263 | | - dataset: "production", |
| 241 | + projectId: 'abc123', |
| 242 | + dataset: 'production', |
264 | 243 | useCdn: true, |
265 | | - apiVersion: "2026-05-04", |
266 | | - }); |
267 | | - const builder = createImageUrlBuilder(client); |
| 244 | + apiVersion: '2026-05-04', |
| 245 | + }) |
| 246 | + const builder = createImageUrlBuilder(client) |
268 | 247 |
|
269 | 248 | // Array value type for every Portable Text item shape across all registered queries. |
270 | | - type PortableTextValue = InferValue<SanityQueries[keyof SanityQueries]>; |
| 249 | + type PortableTextValue = InferValue<SanityQueries[keyof SanityQueries]> |
271 | 250 |
|
272 | | - function CustomPortableText({ value }: { value: PortableTextValue }) { |
| 251 | + function CustomPortableText({value}: {value: PortableTextValue}) { |
273 | 252 | const components = { |
274 | 253 | types: { |
275 | | - image: ({ value }) => ( |
276 | | - <img src={builder.image(value).url()} alt={value.alt || ""} /> |
277 | | - ), |
| 254 | + image: ({value}) => <img src={builder.image(value).url()} alt={value.alt || ''} />, |
278 | 255 | }, |
279 | | - } satisfies InferStrictComponents<PortableTextValue>; |
| 256 | + } satisfies InferStrictComponents<PortableTextValue> |
280 | 257 | // ^ TypeScript errors when the schema gains a custom type, mark, or list |
281 | 258 | // style without a matching handler defined here |
282 | 259 |
|
283 | | - return <PortableText components={components} value={value} />; |
| 260 | + return <PortableText components={components} value={value} /> |
284 | 261 | } |
285 | 262 |
|
286 | | - export default async function Page({ slug }: { slug: string }) { |
287 | | - const query = defineQuery( |
288 | | - `*[_type == "post" && slug.current == $slug][0]{title,content}` |
289 | | - ); |
290 | | - const data = await client.fetch(query, { slug }); |
| 263 | + export default async function Page({slug}: {slug: string}) { |
| 264 | + const query = defineQuery(`*[_type == "post" && slug.current == $slug][0]{title,content}`) |
| 265 | + const data = await client.fetch(query, {slug}) |
291 | 266 |
|
292 | | - if (!data) return notFound(); |
| 267 | + if (!data) return notFound() |
293 | 268 |
|
294 | 269 | return ( |
295 | 270 | <article> |
296 | 271 | <h1>{data.title}</h1> |
297 | | - {Array.isArray(data.content) && ( |
298 | | - <CustomPortableText value={data.content} /> |
299 | | - )} |
| 272 | + {Array.isArray(data.content) && <CustomPortableText value={data.content} />} |
300 | 273 | </article> |
301 | | - ); |
| 274 | + ) |
302 | 275 | } |
303 | 276 | ``` |
304 | 277 |
|
|
0 commit comments