hydrogen-sanity 3.3.1 → 4.0.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,14 +2,33 @@
2
2
 
3
3
  [Sanity.io](https://www.sanity.io) toolkit for [Hydrogen](https://hydrogen.shopify.dev/). Requires `@shopify/hydrogen >= 2023.7.0`.
4
4
 
5
+ - [hydrogen-sanity](#hydrogen-sanity)
6
+ - [Installation](#installation)
7
+ - [Usage](#usage)
8
+ - [Satisfy TypeScript](#satisfy-typescript)
9
+ - [Interacting with Sanity data](#interacting-with-sanity-data)
10
+ - [Preferred: Cached fetches using `loadQuery`](#preferred-cached-fetches-using-loadquery)
11
+ - [`loadQuery` Request Options](#loadquery-request-options)
12
+ - [Alternatively: Using `client` directly](#alternatively-using-client-directly)
13
+ - [Visual Editing](#visual-editing)
14
+ - [Enabling preview mode](#enabling-preview-mode)
15
+ - [Setup CORS for front-end domains](#setup-cors-for-front-end-domains)
16
+ - [Modify Content Security Policy for Studio domains](#modify-content-security-policy-for-studio-domains)
17
+ - [Setup Presentation Tool](#setup-presentation-tool)
18
+ - [Using `@sanity/client` instead of hydrogen-sanity](#using-sanityclient-instead-of-hydrogen-sanity)
19
+ - [Migrate to v4 from v3](#migrate-to-v4-from-v3)
20
+ - [License](#license)
21
+ - [Develop \& test](#develop--test)
22
+ - [Release new version](#release-new-version)
23
+
5
24
  **Features:**
6
25
 
7
- - Cacheable queries to Sanity APICDN
8
- - Client-side live real-time preview using an API token
26
+ - Cacheable queries to [Sanity API CDN](https://www.sanity.io/docs/api-cdn)
27
+ - Interactive live preview with [Visual Editing](https://www.sanity.io/docs/loaders-and-overlays)
9
28
 
10
29
  > **Note**
11
30
  >
12
- > Using this package isn't strictly required for working with Sanity in a Hydrogen storefront. If you'd like to use `@sanity/client` directly, see [Using `@sanity/client` directly](#using-sanityclient-directly) below.
31
+ > Using this package isn't strictly required for working with Sanity in a Hydrogen storefront. If you'd like to use `@sanity/react-loader` and/or `@sanity/client` directly, see [Using `@sanity/client` directly](#using-sanityclient-directly) below.
13
32
 
14
33
  ## Installation
15
34
 
@@ -27,78 +46,57 @@ pnpm install hydrogen-sanity
27
46
 
28
47
  ## Usage
29
48
 
30
- Update the server file to include the Sanity client:
49
+ Update the server file to include the Sanity Loader, and optionally, configure the preview mode if you plan to setup Visual Editing
31
50
 
32
51
  ```ts
33
52
  // ./server.ts
34
53
 
35
54
  // ...all other imports
36
- import {createSanityClient} from 'hydrogen-sanity';
55
+ // Add imports for Sanity Loader and Preview Session
56
+ import {createSanityLoader} from 'hydrogen-sanity'
37
57
 
38
58
  // Inside the default export
39
59
  export default () => {
60
+ // ... Leave all other functions like the storefront client as-is
40
61
 
41
- // 1. Add check for Preview Session
42
- const secrets = [env.SESSION_SECRET];
43
- const [cache, session, previewSession] = await Promise.all([
44
- caches.open('hydrogen'),
45
- HydrogenSession.init(request, secrets),
46
- // 👇 Add preview session
47
- (async function createPreviewSession() {
48
- const storage = createCookieSessionStorage({
49
- cookie: {
50
- name: '__preview',
51
- httpOnly: true,
52
- sameSite: true,
53
- secrets,
54
- },
55
- });
56
-
57
- const session = await storage.getSession(request.headers.get("Cookie"));
58
-
59
- return new HydrogenSession(storage, session);
60
- })(),
61
- ]);
62
-
63
- // Leave all other functions like the storefront client as-is
64
- const {storefront} = createStorefrontClient({ ... })
65
-
66
- // 2. Add the Sanity client
67
- const sanity = createSanityClient({
62
+ // 1. Configure the Sanity Loader and preview mode
63
+ const sanity = createSanityLoader({
64
+ // Required:
68
65
  cache,
69
66
  waitUntil,
70
- // Optionally, pass session and token to enable live-preview
71
- preview:
72
- env.SANITY_PREVIEW_SECRET && env.SANITY_API_TOKEN
73
- ? {
74
- session: previewSession,
75
- token: env.SANITY_API_TOKEN,
76
- // Optionally, provide an alternative to the default `previewDrafts` perspective when in preview mode
77
- // See https://www.sanity.io/docs/perspectives
78
- // perspective: "raw"
79
- }
80
- : undefined,
67
+ // Required:
81
68
  // Pass configuration options for Sanity client
82
69
  config: {
83
70
  projectId: env.SANITY_PROJECT_ID,
84
71
  dataset: env.SANITY_DATASET,
85
72
  apiVersion: env.SANITY_API_VERSION ?? '2023-03-30',
86
73
  useCdn: process.env.NODE_ENV === 'production',
87
- }
88
- });
74
+ },
75
+ // Optionally, set a global default cache strategy, defaults to CacheLong
76
+ // strategy: CacheShort() | null,
77
+ // Optionally, enable Visual Editing
78
+ // See "Visual Editing" section below to setup the preview route
79
+ preview:
80
+ session.get('projectId') === env.SANITY_PROJECT_ID
81
+ ? {token: env.SANITY_API_TOKEN, studioUrl: 'http://localhost:3333'}
82
+ : undefined,
83
+ })
89
84
 
90
- // 3. Add Sanity client to the request handler inside getLoadContext
85
+ // 2. Make Sanity available to all action and loader contexts
91
86
  const handleRequest = createRequestHandler({
92
87
  // ...other settings
93
88
  getLoadContext: () => ({
94
89
  // ...other providers
95
90
  sanity,
96
91
  }),
97
- });
92
+ })
98
93
  }
99
94
  ```
100
95
 
101
- Update your environment variables with settings from your Sanity project. Copy these from https://www.sanity.io/manage or run `npx sanity@latest init --env` to fill the minimum required values from a new or existing project.
96
+ Update your environment variables with settings from your Sanity project.
97
+
98
+ - Copy these from [sanity.io/manage](https://sanity.io/manage)
99
+ - or run `npx sanity@latest init --env` to fill the minimum required values from a new or existing project
102
100
 
103
101
  ```sh
104
102
  # Project ID
@@ -107,17 +105,15 @@ SANITY_PROJECT_ID=""
107
105
  SANITY_DATASET=""
108
106
  # (Optional) Sanity API version
109
107
  SANITY_API_VERSION=""
110
- # Sanity token to authenticate requests in "preview" mode,
111
- # with `viewer` role or higher access
112
- # https://www.sanity.io/docs/http-auth
108
+ # Sanity token to authenticate requests in "preview" mode
109
+ # must have `viewer` role or higher access
110
+ # Create in sanity.io/manage
113
111
  SANITY_API_TOKEN=""
114
- # Secret for authenticating preview mode
115
- SANITY_PREVIEW_SECRET=""
116
112
  ```
117
113
 
118
114
  ### Satisfy TypeScript
119
115
 
120
- Update the environment variables in `Env`
116
+ Update the environment variables in `Env` and `AppLoadContext` to include the Sanity configuration:
121
117
 
122
118
  ```ts
123
119
  // ./remix.env.d.ts
@@ -128,11 +124,10 @@ declare global {
128
124
 
129
125
  interface Env {
130
126
  // ...other variables
131
- SANITY_PREVIEW_SECRET: string
132
- SANITY_API_TOKEN: string
133
127
  SANITY_PROJECT_ID: string
134
128
  SANITY_DATASET: string
135
129
  SANITY_API_VERSION: string
130
+ SANITY_API_TOKEN: string
136
131
  }
137
132
  }
138
133
 
@@ -144,59 +139,89 @@ declare module '@shopify/remix-oxygen' {
144
139
  }
145
140
  ```
146
141
 
147
- ### Fetching Sanity data with `query`
142
+ ## Interacting with Sanity data
143
+
144
+ ### Preferred: Cached fetches using `loadQuery`
148
145
 
149
- Query Sanity API and cache the response (defaults to `CacheLong` caching strategy):
146
+ Query Sanity's API and use Hydrogen's cache to store the response (defaults to `CacheLong` caching strategy).
147
+
148
+ `loadQuery` will not implement Hydrogen's caching when:
149
+
150
+ - in preview mode or
151
+ - when the Sanity Client config for `useCdn` is false or
152
+ - when the `strategy` option is set to `null`.
153
+
154
+ Learn more about configuring [caching in Hydrogen on the Shopify documentation](https://shopify.dev/docs/custom-storefronts/hydrogen/caching).
150
155
 
151
156
  ```ts
152
- export async function loader({context, params}: LoaderArgs) {
153
- const homepage = await context.sanity.query({
154
- query: `*[_type == "page" && _id == $id][0]`,
155
- params: {
156
- id: 'home',
157
- },
158
- // optionally pass a caching strategy
159
- // cache: CacheShort()
160
- })
157
+ export async function loader({context, params}: LoaderFunctionArgs) {
158
+ const query = `*[_type == "page" && _id == $id][0]`
159
+ const params = {id: 'home'}
160
+ const initial = await context.sanity.loadQuery(query, params)
161
161
 
162
- return json({
163
- homepage,
164
- })
162
+ return json({initial})
165
163
  }
166
164
  ```
167
165
 
168
- To use other client methods, or to use `fetch` without caching, the Sanity client is also available:
166
+ ### `loadQuery` Request Options
167
+
168
+ If you need to pass any additional options to the request provide `queryOptions` like so:
169
169
 
170
170
  ```ts
171
- export async function loader({context, params}: LoaderArgs) {
172
- const homepage = await context.sanity.client.fetch(
173
- `*[_type == "page" && _id == $id][0]`,
174
- {id: 'home'}
175
- );
171
+ const page = await context.sanity.loadQuery<HomePage>(query, params, {
172
+ // These additional options will be passed to sanity.loadQuery
173
+ queryOptions: {
174
+ tag: 'home',
175
+ headers: {
176
+ 'Accept-Encoding': 'br, gzip, *',
177
+ },
178
+ },
179
+ // Optionally customize the cache strategy for this request
180
+ // strategy: CacheShort(),
181
+ // Or disable caching for this request
182
+ // strategy: null,
183
+ })
184
+ ```
176
185
 
177
- return json({
178
- homepage,
179
- });
186
+ ### Alternatively: Using `client` directly
187
+
188
+ The Sanity Client is also configured in context, but will not return data in the same shape as `loadQuery`. It is recommended to use `loadQuery` for data fetching.
189
+
190
+ Sanity Client can be used for mutations within actions, for example:
191
+
192
+ ```ts
193
+ export async function action({context, request}: ActionFunctionArgs) {
194
+ if (!isAuthenticated(request)) {
195
+ return redirect('/login')
196
+ }
197
+
198
+ return context.sanity
199
+ .withConfig({
200
+ token: context.env.SANITY_WRITE_TOKEN,
201
+ })
202
+ .client.create({
203
+ _type: 'comment',
204
+ text: request.body.get('text'),
205
+ })
206
+ }
180
207
  ```
181
208
 
182
- ### Live preview
209
+ ## Visual Editing
183
210
 
184
- Enable real-time, live preview by streaming dataset updates to the browser.
211
+ Enable real-time, interactive live preview inside the Presentation Tool of your Sanity Studio.
185
212
 
186
- First setup your root route to enable preview mode across the entire application, if the preview session is found:
213
+ First set up your root route to enable preview mode across the entire application, if the preview session is active:
187
214
 
188
215
  ```tsx
189
216
  // ./app/root.tsx
190
217
 
191
218
  // ...other imports
192
- import {PreviewProvider, getPreview} from 'hydrogen-sanity'
219
+ import {VisualEditing} from '@sanity/visual-editing/remix'
193
220
 
194
221
  export async function loader({context}: LoaderArgs) {
195
- const preview = getPreview(context)
196
-
197
222
  return json({
198
223
  // ... other loader data
199
- preview,
224
+ preview: context.sanity.preview,
200
225
  })
201
226
  }
202
227
 
@@ -212,10 +237,8 @@ export default function App() {
212
237
  <Links />
213
238
  </head>
214
239
  <body>
215
- {/* 👇 Wrap <Outlet /> in PreviewProvider component */}
216
- <PreviewProvider {...preview}>
217
- <Outlet />
218
- </Preview>
240
+ <Outlet />
241
+ {preview ? <VisualEditing /> : null}
219
242
  <ScrollRestoration />
220
243
  <Scripts />
221
244
  </body>
@@ -224,113 +247,107 @@ export default function App() {
224
247
  }
225
248
  ```
226
249
 
227
- `PreviewProvider` wraps the `LiveQueryProvider` component of `@sanity/preview-kit` - props passed to `PreviewProvider` will be passed to `LiveQueryProvider`. For more information, see the [`@sanity/preview-kit` documentation](https://github.com/sanity-io/preview-kit).
250
+ This Visual Editing component will trigger incremental updates to draft documents from the server for users with a valid preview session. [Duplicate its source](https://github.com/sanity-io/visual-editing/blob/main/packages/visual-editing/src/remix/VisualEditing.tsx) into your own project if you wish to customize its behavior.
228
251
 
229
- By default, `PreviewProvider` will passthrough rendering to its children if you don't provide a fallback; however you can also pass a `ReactNode` to render a loading indicator or message:
230
-
231
- ```tsx
232
- import {PreviewLoading} from '~/components/PreviewLoading';
233
-
234
- // (Optional) pass a string or your own React component to show while data is loading
235
- <PreviewProvider {...preview} fallback={<PreviewLoading />}>
236
- ```
237
-
238
- Next, for any route that needs to render a preview, wrap it in a `SanityPreview` component which re-runs the same query client-side but will render draft content in place of published content, if it exists. Updating in real-time as changes are streamed in.
239
-
240
- The component will be rendered with live preview if the preview session is found, otherwise, it renders the component with static content.
252
+ These updates are faster when your initial server-side content is passed through an optional `useQuery` hook.
241
253
 
242
254
  ```tsx
243
255
  // Any route file, such as ./app/routes/index.tsx
244
256
 
245
257
  // ...all other imports
246
- import {SanityPreview} from 'hydrogen-sanity'
258
+ import {useQuery} from '@sanity/react-loader'
259
+
260
+ export async function loader({context, params}: LoaderArgs) {
261
+ const query = `*[_type == "page" && _id == $id][0]`
262
+ const params = {id: 'home'}
263
+ const initial = await context.sanity.loadQuery(query, params)
247
264
 
248
- // ...all other exports like `loader` and `meta`
249
- // Tip: In preview mode, pass "query" and "params" from the loader to the component
265
+ return json({initial, query, params})
266
+ }
250
267
 
251
268
  // Default export where content is rendered
252
269
  export default function Index() {
253
270
  // Get initial data, passing it as snapshot to render preview...
254
- const {homepage} = useLoaderData<typeof loader>()
271
+ const {initial, query, params} = useLoaderData<typeof loader>()
272
+ // Optional, pass query, params and initial data to useQuery for faster updates
273
+ const {loading, data} = useQuery(query, params, initial)
255
274
 
256
- // Render preview-enabled component, fetches
257
- // content client-side and renders live updates
258
- // of draft content
259
- return (
260
- <SanityPreview
261
- data={homepage}
262
- query={`*[_type == "page" && _id == $id][0]`}
263
- params={{id: 'home'}}
264
- >
265
- {(homepage) => <>{/* ...render homepage using data */}</>}
266
- </SanityPreview>
267
- )
275
+ return loading ? <div>Loading</div> : <Page page={data} />
268
276
  }
269
277
  ```
270
278
 
271
- ### Entering preview mode
279
+ ### Enabling preview mode
280
+
281
+ For users to enter preview mode, they will need to visit a route that performs some authentication and then writes to the session.
272
282
 
273
- For users to enter preview mode, they will need to visit a route that sets a preview cookie. The logic of what routes should set the preview cookie is up to you, in this example, it checks if a parameter in the URL matches one of your environment variables on the server.
283
+ `hydrogen-sanity` comes with a preconfigured route for this purpose. It checks the value of a secret in the URL used by Presentation Tool - and if valid - writes the `projectId` to the Hydrogen session.
284
+
285
+ Add this route to your project like below, or view the source to copy and modify it in your project.
274
286
 
275
287
  ```tsx
276
- // ./app/routes/api.preview.tsx
277
- import {LoaderFunction, redirect} from '@shopify/remix-oxygen'
278
-
279
- export const loader: LoaderFunction = async function ({request, context}) {
280
- const {env, sanity} = context
281
- const {searchParams} = new URL(request.url)
282
-
283
- if (
284
- !sanity.preview?.session ||
285
- !searchParams.has('secret') ||
286
- searchParams.get('secret') !== env.SANITY_PREVIEW_SECRET
287
- ) {
288
- throw new Response('Invalid secret', {
289
- status: 401,
290
- statusText: 'Unauthorized',
291
- })
292
- }
288
+ // ./app/routes/resource.preview.ts
293
289
 
294
- sanity.preview.session.set('projectId', env.SANITY_PROJECT_ID)
290
+ import {previewRoute} from 'hydrogen-sanity'
295
291
 
296
- return redirect(`/`, {
297
- status: 307,
298
- headers: {
299
- 'Set-Cookie': await sanity.preview.session.commit(),
300
- },
301
- })
302
- }
292
+ export const {loader} = previewRoute
293
+
294
+ // Optionally, export the supplied action which will disable preview mode when POSTed to
295
+ // export const {action, loader} = previewRoute
303
296
  ```
304
297
 
305
- ## Request Options
298
+ ### Setup CORS for front-end domains
299
+
300
+ If your Sanity Studio is not embedded in your Hydrogen App, you will need to add a CORS origin to your project for every URL where your app is hosted or running in development.
301
+
302
+ Add `http://localhost:3000` to the CORS origins in your Sanity project settings at [sanity.io/manage](https://sanity.io/manage).
306
303
 
307
- If you need to pass any additional options to the request, provide `queryOptions` like so:
304
+ ### Modify Content Security Policy for Studio domains
305
+
306
+ You may receive errors in the console due to Content Security Policy (CSP) restrictions due to the default `frame-ancestors` configuration. Modify `entry.server.tsx` to allow any URL that the Studio runs on to display the app in an Iframe.
308
307
 
309
308
  ```ts
310
- const page = await context.sanity.query<HomePage>({
311
- query: HOME_PAGE_QUERY,
312
- cache,
313
- // These additional options will be passed to `sanity.fetch`
314
- queryOptions: {
315
- tag: 'home',
316
- headers: {
317
- 'Accept-Encoding': 'br, gzip, *',
318
- },
319
- },
320
- })
309
+ // ./app/entry.server.tsx
310
+
311
+ // Replace this line
312
+ // responseHeaders.set('Content-Security-Policy', header);
313
+
314
+ // With this
315
+ const safeFrameHeader = header.replace(
316
+ 'frame-ancestors none',
317
+ 'frame-ancestors http://localhost:3333'
318
+ )
319
+ responseHeaders.set('Content-Security-Policy', safeFrameHeader)
321
320
  ```
322
321
 
323
- ## Limits
322
+ ### Setup Presentation Tool
323
+
324
+ Now in your Sanity Studio config, import the Presentation tool with the Preview URL set to the preview route you created.
325
+
326
+ ```ts
327
+ // ./sanity.config.ts
328
+
329
+ // Add this import
330
+ import {presentationTool} from 'sanity/presentation'
331
+
332
+ export default defineConfig({
333
+ // ...all other settings
324
334
 
325
- The real-time preview comes with a configured limit of 3000 documents. You can experiment with larger datasets by configuring `cache.maxDocuments: <Integer>` in your `PreviewProvider`. Be aware that this might affect the preview performance.
335
+ plugins: [
336
+ presentationTool({
337
+ previewUrl: {previewMode: {enable: 'http://localhost:3000/resource/preview'}},
338
+ }),
339
+ // ..all other plugins
340
+ ],
341
+ })
342
+ ```
326
343
 
327
- You can also use the `cache.includeTypes` option to reduce the amount of documents and reduce the risk of hitting the document limit.
344
+ You should now be able to view your Hydrogen app in the Presentation Tool, click to edit any Sanity content and see live updates as you make changes.
328
345
 
329
- If you're a Sanity Enterprise user with Content Source Maps enabled, you can optimize further by enabling `turboSourceMap` which opts-in to a faster and smarter cache. It'll only listen for mutations on the documents that you are using in your queries, and apply the mutations to the cache in real-time.
346
+ ## Using `@sanity/client` instead of hydrogen-sanity
330
347
 
331
- ## Using `@sanity/client` directly
348
+ For whatever reason, if you choose not to use `hydrogen-sanity` you could still configure `@sanity/react-loader` or `@sanity/client` to get Sanity content into your Hydrogen storefront.
332
349
 
333
- For whatever reason, if you choose not to use `hydrogen-sanity` you can still use `@sanity/client` to get Sanity content into your Hydrogen storefront:
350
+ The following example configures Sanity Client.
334
351
 
335
352
  ```ts
336
353
  // ./server.ts
@@ -361,18 +378,119 @@ export default {
361
378
  }
362
379
  ```
363
380
 
364
- Then, in your loaders you'll have access to the client in the request context:
381
+ Then, in your loaders and actions you'll have access to Sanity Client in context:
365
382
 
366
383
  ```ts
367
384
  export async function loader({context, params}: LoaderArgs) {
368
- const homepage = await context.sanity.fetch(
369
- `*[_type == "page" && _id == $id][0]`,
370
- {id: 'home'}
371
- );
385
+ const homepage = await context.sanity.fetch(`*[_type == "page" && _id == $id][0]`, {id: 'home'})
386
+
387
+ return json({homepage})
388
+ }
389
+ ```
390
+
391
+ # Migrate to v4 from v3
392
+
393
+ 1. Swap `createSanityClient` for `createSanityLoader`
394
+
395
+ The new function will still return a client – useful for mutations when supplied with a write token. But primarily it will now return a configured [Sanity React Loader](https://www.sanity.io/docs/react-loader) which is the new recommendation for performing queries that will take advantage of Visual Editing
396
+
397
+ ```diff
398
+ // ./server.ts
399
+
400
+ - import {createSanityClient} from 'hydrogen-sanity';
401
+ + import {createSanityLoader} from 'hydrogen-sanity';
402
+ ```
403
+
404
+ 1. Update `query` data fetches to `loadQuery`.
405
+
406
+ The return type of `loadQuery` is different from Sanity Client's `fetch`, with the returned content is inside a `data` attribute. The recommendation for Hydrogen/Remix applications is to name this response `initial` and return it in its entirety in the loader.
407
+
408
+ ```diff
409
+ ./app/routes/products.$handle.tsx
410
+
411
+ Replace any usage of `query` with `loadQuery`
412
+ Note the different shape for arguments and return value
413
+ - const page = await sanity.query<SanityDocument>({query, params, cache, queryOptions})
414
+ + const initial = await sanity.loadQuery<SanityDocument>(query, params, {strategy, queryOptions})
415
+
416
+ Replace any Sanity Client fetches
417
+ - const page = await sanity.client.fetch<SanityDocument>(query, params)
418
+ + const initial = await sanity.loadQuery<SanityDocument>(query, params)
419
+
420
+ - return json({page})
421
+ + return json({query, params, initial})
422
+ ```
423
+
424
+ 1. Then in the default export, pass this initial object to the `useQuery` hook imported from React Loader.
425
+
426
+ `useQuery` alone will not rerender the component with preview content. For this, you'll need to add a new component to the root.
427
+
428
+ ```tsx
429
+ // ./app/routes/products.$handle.tsx
372
430
 
431
+ import {useQuery} from '@sanity/react-loader'
432
+
433
+ export default function Route() {
434
+ const {query, params, initial} = useLoaderData()
435
+ const {data, loading} = useQuery(query, params, initial)
436
+
437
+ return loading ? <div>Loading</div> : <Product product={data} />
438
+ }
439
+ ```
440
+
441
+ 4. Change imports in `root.tsx`
442
+
443
+ The Sanity Visual Editing package exports a ready-made function for Remix to provide live updates to all `useQuery` hooks throughout the application. It is designed to only run when the app is displayed inside an iframe.
444
+
445
+ Update your imports:
446
+
447
+ ```diff
448
+ // ./root.tsx
449
+
450
+ - import {PreviewProvider, getPreview} from 'hydrogen-sanity'
451
+ + import {VisualEditing} from '@sanity/visual-editing/remix'
452
+ ```
453
+
454
+ Update root loader and default export to remove the old `PreviewProvider` and replace it with the conditionally imported `VisualEditing` component:
455
+
456
+ ```tsx
457
+ // ./root.tsx
458
+
459
+ // Preview config no longer needs to be returned from the Loader
460
+ export async function loader({context}: LoaderArgs) {
373
461
  return json({
374
- homepage,
375
- });
462
+ // ... other loader data
463
+ // Return a boolean for if the app is in preview mode
464
+ preview: !!context.sanity.preview,
465
+ })
466
+ }
467
+
468
+ // Remove the Preview Provider
469
+ export default function App() {
470
+ const {preview, ...data} = useLoaderData<typeof loader>()
471
+
472
+ return (
473
+ <html>
474
+ <head>
475
+ <meta charSet="utf-8" />
476
+ <meta name="viewport" content="width=device-width,initial-scale=1" />
477
+ <Meta />
478
+ <Links />
479
+ </head>
480
+ <body>
481
+ {/* Remove the PreviewProvider wrapper */}
482
+ <PreviewProvider {...preview}>
483
+ <Outlet />
484
+ </Preview>
485
+ {/* Replace with VisualEditing */}
486
+ <Outlet />
487
+ {preview ? <VisualEditing /> : null}
488
+ <ScrollRestoration />
489
+ <Scripts />
490
+ </body>
491
+ </html>
492
+ )
493
+ }
376
494
  ```
377
495
 
378
496
  ## License