### Install @sanity/core-loader and @sanity/client Source: https://github.com/sanity-io/visual-editing/blob/main/packages/core-loader/README.md Install the core loader and client packages using npm. This is the initial setup step for using the loader. ```sh npm install @sanity/core-loader @sanity/client ``` -------------------------------- ### Install @sanity/preview-url-secret and @sanity/client Source: https://github.com/sanity-io/visual-editing/blob/main/packages/preview-url-secret/README.md Install the necessary packages for preview URL secrets and Sanity client integration. ```sh npm install @sanity/preview-url-secret @sanity/client ``` -------------------------------- ### Install @sanity/react-loader Source: https://github.com/sanity-io/visual-editing/blob/main/packages/react-loader/README.md Install the necessary packages for @sanity/react-loader, @sanity/client, and React. ```sh npm install @sanity/react-loader @sanity/client react@^18.2 ``` -------------------------------- ### Install @sanity/svelte-loader and Dependencies Source: https://github.com/sanity-io/visual-editing/blob/main/packages/svelte-loader/README.md Install the core package and necessary dependencies for data fetching and visual editing. ```sh npm install @sanity/svelte-loader # We will also need the following dependencies for fetching data and enabling visual editing npm install @sanity/client @sanity/visual-editing ``` -------------------------------- ### Setup Shared Loader for SSR and Live Mode Source: https://github.com/sanity-io/visual-editing/blob/main/packages/react-loader/README.md Configure a shared file for server and client to enable SSR and defer client setup. This ensures data is fetched server-side by default. ```ts // ./src/app/sanity.loader.ts import {createQueryStore} from '@sanity/react-loader' export const { // Used only server side loadQuery, setServerClient, // Used only client side useQuery, useLiveMode, } = createQueryStore({client: false, ssr: true}) ``` -------------------------------- ### Install @sanity/debug-preview-url-secret-plugin Source: https://github.com/sanity-io/visual-editing/blob/main/packages/preview-url-secret/README.md Command to install the debug plugin for viewing generated URL secrets in the Sanity Studio. ```bash npm install @sanity/debug-preview-url-secret-plugin ``` -------------------------------- ### Uninstall and Install Dependencies Source: https://github.com/sanity-io/visual-editing/blob/main/packages/presentation/README.md Replace the @sanity/presentation package with the latest sanity package. ```sh npm uninstall @sanity/presentation npm install sanity@latest ``` -------------------------------- ### Setup Sanity Client Instance (Server-side) Source: https://github.com/sanity-io/visual-editing/blob/main/packages/svelte-loader/README.md Configure a server-specific Sanity client instance with a read token for fetching preview content. This instance extends the base client configuration. ```ts // src/lib/server/sanity.ts import {SANITY_API_READ_TOKEN} from '$env/static/private' import {client} from '$lib/sanity' export const serverClient = client.withConfig({ token: SANITY_API_READ_TOKEN, // Optionally enable stega // stega: true }) ``` -------------------------------- ### Alternative Shared Loader Setup Source: https://github.com/sanity-io/visual-editing/blob/main/packages/react-loader/README.md An alternative way to set up the shared loader file by directly exporting from the main package. ```ts // ./src/app/sanity.loader.ts export { // Used only server side loadQuery, setServerClient, // Used only client side useQuery, useLiveMode, } from '@sanity/react-loader' ``` -------------------------------- ### Migrate from @sanity/nuxt-loader to @nuxtjs/sanity Source: https://github.com/sanity-io/visual-editing/blob/main/packages/nuxt-loader/README.md Use these commands to uninstall the old package and install the new one. ```sh npm uninstall @sanity/nuxt-loader npm install @nuxtjs/sanity ``` -------------------------------- ### Setup Sanity Client Instance (Client-side) Source: https://github.com/sanity-io/visual-editing/blob/main/packages/svelte-loader/README.md Create and export a Sanity client instance using environment variables for use in the SvelteKit application. This client is configured for general use, including stega. ```ts // src/lib/sanity.ts import { createClient, } from '@sanity/client' import { PUBLIC_SANITY_API_VERSION, PUBLIC_SANITY_DATASET, PUBLIC_SANITY_PROJECT_ID, PUBLIC_SANITY_STUDIO_URL, } from '$env/static/public' export const client = createClient({ projectId: PUBLIC_SANITY_PROJECT_ID, dataset: PUBLIC_SANITY_DATASET, apiVersion: PUBLIC_SANITY_API_VERSION, useCdn: true, stega: { studioUrl: PUBLIC_SANITY_STUDIO_URL, }, }) ``` -------------------------------- ### Product Page with React Loader Source: https://github.com/sanity-io/visual-editing/blob/main/packages/react-loader/README.md This example demonstrates setting up a product page using `@sanity/react-loader`. It fetches product data and provides `encodeDataAttribute` for linking elements to the Sanity Studio. ```tsx // ./src/app/routes/products.$slug.tsx import {json, type LoaderFunction} from '@remix-run/node' import {Link, useLoaderData, useParams} from '@remix-run/react' import {useQuery} from '@sanity/react-loader' import {loadQuery} from '~/sanity.loader.server' interface Product {} const query = `*[_type == "product" && slug.current == $slug][0]` export const loader: LoaderFunction = async ({params}) => { return json({ params, initial: await loadQuery(query, params), }) } export default function ProductPage() { const {params, initial} = useLoaderData() if (!params.slug || !initial.data?.slug?.current) { throw new Error('No slug, 404?') } const {data, encodeDataAttribute} = useQuery(query, params, { initial, }) // Use `data` in your view, it'll mirror what the loader returns in production mode, // while Live Mode it becomes reactive and respons in real-time to your edits in the Presentation tool. // And `encodeDataAttribute` is a helpful utility for adding custom `data-sanity` attributes. return } ``` -------------------------------- ### Install next-sanity for Next.js App Router Source: https://github.com/sanity-io/visual-editing/blob/main/packages/visual-editing/README.md Installs the necessary package for integrating visual editing with the Next.js App Router. This is a prerequisite for using the VisualEditing component. ```sh npm i next-sanity ``` -------------------------------- ### Uninstall @sanity/next-loader and Install next-sanity Source: https://github.com/sanity-io/visual-editing/blob/main/packages/next-loader/README.md Use npm to remove the old package and add the new one. ```sh npm uninstall @sanity/next-loader npm install next-sanity ``` -------------------------------- ### Uninstall @sanity/overlays and Install @sanity/visual-editing Source: https://github.com/sanity-io/visual-editing/blob/main/packages/overlays/README.md Use npm to remove the old dependency and add the new one. ```sh npm uninstall @sanity/overlays npm install @sanity/visual-editing ``` -------------------------------- ### Enable Live Mode with Visual Editing Source: https://github.com/sanity-io/visual-editing/blob/main/packages/react-loader/README.md Activate Live Mode by integrating `useLiveMode` with `@sanity/visual-editing` in a component. This setup requires a browser client and is typically lazy-loaded. ```tsx // ./src/app/VisualEditing.tsx import {enableVisualEditing, type HistoryUpdate} from '@sanity/visual-editing' import {useLiveMode} from '~/sanity.loader' import {useEffect} from 'react' // A browser client for Live Mode, it's only part of the browser bundle when the `VisualEditing` component is lazy loaded with `React.lazy` const client = createClient({ projectId: window.ENV.SANITY_PROJECT_ID, dataset: window.ENV.SANITY_DATASET, useCdn: true, apiVersion: window.ENV.SANITY_API_VERSION, stega: { enabled: true, studioUrl: 'https://my.sanity.studio', }, }) export default function VisualEditing() { useEffect( () => enableVisualEditing({ history: { // setup Remix router integration }, }), [], ) useLiveMode({client}) return null } ``` -------------------------------- ### Server-Side Client Setup in Remix Source: https://github.com/sanity-io/visual-editing/blob/main/packages/react-loader/README.md Set up the Sanity client on the server-side in a Remix application. The `.server.ts` suffix prevents this code from being included in the browser bundle. ```ts // ./src/app/sanity.loader.server.ts import {createClient} from '@sanity/client' import {loadQuery, setServerClient} from './sanity.loader' const client = createClient({ projectId: process.env.SANITY_PROJECT_ID, dataset: process.env.SANITY_DATASET, useCdn: true, apiVersion: process.env.SANITY_API_VERSION, stega: { enabled: true, studioUrl: 'https://my.sanity.studio', }, }) setServerClient(client) // Re-export for convenience export {loadQuery} ``` -------------------------------- ### Change Overlay z-index Source: https://github.com/sanity-io/visual-editing/blob/main/packages/visual-editing/README.md TypeScript example showing how to configure the `zIndex` option for visual editing overlay elements. ```typescript enableVisualEditing({ zIndex: 1000, }) ``` -------------------------------- ### Update Import Statements for next-sanity Source: https://github.com/sanity-io/visual-editing/blob/main/packages/next-loader/README.md Modify your import statements to reflect the new package structure. Lines starting with '-' are removed, and lines starting with '+' are added. ```diff -import { defineLive, isCorsOriginError } from '@sanity/next-loader' -import { usePresentationQuery } from '@sanity/next-loader/hooks' +import { isCorsOriginError } from 'next-sanity' +import { defineLive } from 'next-sanity/live' +import { usePresentationQuery } from 'next-sanity/hooks' ``` -------------------------------- ### Configure @sanity/debug-preview-url-secret-plugin in sanity.config.ts Source: https://github.com/sanity-io/visual-editing/blob/main/packages/preview-url-secret/README.md Example of how to add the debugSecrets plugin to your Sanity Studio configuration. ```typescript import {debugSecrets} from '@sanity/debug-preview-url-secret-plugin' import {defineConfig} from 'sanity' export default defineConfig({ // ... other options plugins: [ // Makes the url secrets visible in the Sanity Studio like any other documents defined in your schema debugSecrets(), ], }) ``` -------------------------------- ### Enable Visual Editing with History API Integration Source: https://github.com/sanity-io/visual-editing/blob/main/packages/visual-editing/README.md Enables visual editing overlays and integrates with a router using the History API for seamless navigation. Requires manual event listener setup and cleanup. ```typescript import {enableVisualEditing} from '@sanity/visual-editing' // Enables visual editing overlays enableVisualEditing() // Integrate with a router that uses the History API enableVisualEditing({ history: { subscribe: (navigate) => { const handler = (event: PopStateEvent) => { navigate({ type: 'push', url: `${location.pathname}${location.search}`, }) } window.addEventListener('popstate', handler) return () => window.removeEventListener('popstate', handler) }, update: (update) => { switch (update.type) { case 'push': return window.history.pushState(null, '', update.url) case 'pop': return window.history.back() case 'replace': return window.history.replaceState(null, '', update.url) default: throw new Error(`Unknown update type: ${update.type}`) } }, }, }) ``` -------------------------------- ### Implement VisualEditing in RootLayout Source: https://github.com/sanity-io/visual-editing/blob/main/packages/visual-editing/README.md Integrate the `VisualEditing` component into your `RootLayout` for App Router. This setup includes a default refresh logic using Server Actions to revalidate paths on manual or mutation sources. ```tsx import {VisualEditing} from 'next-sanity/visual-editing' import {revalidatePath, revalidateTag} from 'next/cache' import {draftMode} from 'next/headers' export default function RootLayout({children}: {children: React.ReactNode}) { return ( {children} {draftMode().isEnabled && ( { 'use server' // Guard against a bad actor attempting to revalidate the page if (!draftMode().isEnabled) { return } if (payload.source === 'manual') { await revalidatePath('/', 'layout') } // Only revalidate on mutations if the route doesn't have loaders or preview-kit if (payload.source === 'mutation' && !payload.livePreviewEnabled) { await revalidatePath('/', 'layout') } }} /> )} ) } ``` -------------------------------- ### Set Edit Target Element Source: https://github.com/sanity-io/visual-editing/blob/main/packages/visual-editing/README.md HTML example demonstrating how to use the `data-sanity-edit-target` attribute to specify which element should host the 'Edit in Sanity Studio' buttons. ```html

{dynamicTitle}

Hardcoded Tagline
``` -------------------------------- ### Utility to Get Draft Mode Status (Remix.js) Source: https://github.com/sanity-io/visual-editing/blob/main/packages/preview-url-secret/README.md A server-side utility function to check if draft mode is active by inspecting the session cookie. ```typescript // ./app/sanity/get-draft-mode.server.ts import {client} from '~/sanity/client' import {getSession} from '~/sessions' export async function getDraftMode(request: Request) { const draftSession = await getSession(request.headers.get('Cookie')) const draft = draftSession.get('projectId') === client.config().projectId if (draft && !process.env.SANITY_API_READ_TOKEN) { throw new Error( `Cannot activate draft mode without a 'SANITY_API_READ_TOKEN' token in your environment variables.`, ) } return draft } ``` -------------------------------- ### Custom Refresh Logic for Manual Source Source: https://github.com/sanity-io/visual-editing/blob/main/packages/visual-editing/README.md Customize the `VisualEditing` component's refresh behavior for manual triggers. This example uses `revalidateTag('all', {expire: 0})` assuming a pattern where 'all' is added to data fetches. ```tsx { 'use server' // Guard against a bad actor attempting to revalidate the page if (!draftMode().isEnabled) { return } if (payload.source === 'manual') { await revalidateTag('all', {expire: 0}) } }} /> ``` -------------------------------- ### Enable Live Mode with Studio URL Source: https://github.com/sanity-io/visual-editing/blob/main/packages/react-loader/README.md If not using stega, define the `studioUrl` in the `useLiveMode` hook to enable live mode. This connects your application to the Sanity Studio for real-time preview. ```diff -useLiveMode({ client }) +useLiveMode({ client, studioUrl: 'https://my.sanity.studio' }) ``` -------------------------------- ### Using loadQuery and useQuery in a Remix Route Source: https://github.com/sanity-io/visual-editing/blob/main/packages/react-loader/README.md Integrate `loadQuery` for server-side data fetching and `useQuery` for client-side reactivity in a Remix route. `useQuery` defers fetching until Live Mode is active. ```tsx // ./src/app/routes/products.$slug.tsx import {json, type LoaderFunction} from '@remix-run/node' import {Link, useLoaderData, useParams} from '@remix-run/react' import {useQuery} from '~/sanity.loader' import {loadQuery} from '~/sanity.loader.server' interface Product {} const query = `*[_type == "product" && slug.current == $slug][0]` export const loader: LoaderFunction = async ({params}) => { return json({ params, initial: await loadQuery(query, params), }) } export default function ProductPage() { const {params, initial} = useLoaderData() if (!params.slug || !initial.data?.slug?.current) { throw new Error('No slug, 404?') } const {data} = useQuery(query, params, {initial}) // Use `data` in your view, it'll mirror what the loader returns in production mode, // while Live Mode it becomes reactive and respons in real-time to your edits in the Presentation tool. return } ``` -------------------------------- ### Configure Presentation Tool with Preview URL Source: https://github.com/sanity-io/visual-editing/blob/main/packages/preview-url-secret/README.md Configure the presentation tool in your Sanity project to enable preview URLs. Specify the application origin and the preview mode endpoint. ```typescript // ./sanity.config.ts import {defineConfig} from 'sanity' import {presentationTool} from 'sanity/presentation' export default defineConfig({ // ... other options plugins: [ // ... other plugins presentationTool({ previewUrl: { // @TODO change to the URL of the application, or `location.origin` if it's an embedded Studio origin: 'http://localhost:3000', previewMode: { enable: '/api/draft', }, }, }), ], }) ``` -------------------------------- ### Fetch Data on Client using useQuery Source: https://github.com/sanity-io/visual-editing/blob/main/packages/svelte-loader/README.md Utilizes the `useQuery` hook in a Svelte page component to fetch and manage data on the client. It supports live updates, draft/published content switching, and generates `data-sanity` attributes for overlays. ```svelte {#if loading}
Loading...
{:else}

{page.title}

{/if} ``` -------------------------------- ### Fetch Data on Server using loadQuery Source: https://github.com/sanity-io/visual-editing/blob/main/packages/svelte-loader/README.md Uses `locals.loadQuery` in a server load function to fetch initial data for a page. It takes the query and parameters, returning the fetched data and route parameters. ```typescript // src/routes/[slug]/+page.server.ts import {pageQuery, type PageResult} from '$lib/queries' import type {PageServerLoad} from './$types' export const load: PageServerLoad = async ({params, locals: {loadQuery}}) => { const {slug} = params const initial = await loadQuery(pageQuery, {slug}) return {initial, params: {slug}} } ``` -------------------------------- ### Enable Visual Editing in Root Layout Source: https://github.com/sanity-io/visual-editing/blob/main/packages/svelte-loader/README.md Enables visual editing features, including live mode and overlays, by calling `enableVisualEditing` and `useLiveMode` in the root layout component. Requires the Sanity Studio URL. ```svelte
``` -------------------------------- ### Configure SvelteKit Server Hooks with @sanity/svelte-loader Source: https://github.com/sanity-io/visual-editing/blob/main/packages/svelte-loader/README.md Integrate the @sanity/svelte-loader into SvelteKit's server hooks by setting the server client and creating a request handler. This enables preview routes and cookie verification. ```ts // src/hooks.server.ts import {createRequestHandler, setServerClient} from '@sanity/svelte-loader' import {serverClient} from '$lib/server/sanity' setServerClient(serverClient) export const handle = createRequestHandler() ``` -------------------------------- ### Set Client-Side Preview State (Server Load) Source: https://github.com/sanity-io/visual-editing/blob/main/packages/svelte-loader/README.md Passes the preview state from the server to the client via a load function. Typically implemented in the root layout server file. ```typescript // src/routes/+layout.server.ts import type {LayoutServerLoad} from './$types' export const load: LayoutServerLoad = ({locals: {preview}}) => { return {preview} } ``` -------------------------------- ### Create Session Cookie Storage for Draft Mode (Remix.js) Source: https://github.com/sanity-io/visual-editing/blob/main/packages/preview-url-secret/README.md Sets up session storage for draft mode using Remix.js. Requires a SANITY_SESSION_SECRET environment variable. ```typescript // ./app/sessions.ts import {createCookieSessionStorage} from '@remix-run/node' export const DRAFT_SESSION_NAME = '__draft' if (!process.env.SANITY_SESSION_SECRET) { throw new Error(`Missing SANITY_SESSION_SECRET in .env`) } const {getSession, commitSession, destroySession} = createCookieSessionStorage({ cookie: { name: DRAFT_SESSION_NAME, secrets: [process.env.SANITY_SESSION_SECRET], sameSite: 'lax', }, }) export {commitSession, destroySession, getSession} ``` -------------------------------- ### Set Client-Side Preview State (Client Load) Source: https://github.com/sanity-io/visual-editing/blob/main/packages/svelte-loader/README.md Accesses the preview state passed from the server and sets it using the `setPreviewing` function. This should be done in the client-side layout load function. ```typescript // src/routes/+layout.ts import {setPreviewing} from '@sanity/svelte-loader' import type {LayoutLoad} from './$types' export const load: LayoutLoad = ({data: {preview}}) => { setPreviewing(preview) } ``` -------------------------------- ### Inspect Studio Origin with validatePreviewUrl Source: https://github.com/sanity-io/visual-editing/blob/main/packages/preview-url-secret/README.md Demonstrates how to access the studioOrigin property returned by validatePreviewUrl to identify the source of the preview request. ```typescript const {isValid, redirectTo = '/', studioOrigin} = await validatePreviewUrl(clientWithToken, req.url) if (studioOrigin === 'http://localhost:3333') { console.log('This preview was initiated from the local development Studio') } ``` -------------------------------- ### Update Import Statements Source: https://github.com/sanity-io/visual-editing/blob/main/packages/presentation/README.md Change the import path for the presentationTool from '@sanity/presentation' to 'sanity/presentation'. ```diff -import { presentationTool } from '@sanity/presentation' +import { presentationTool } from 'sanity/presentation' ``` -------------------------------- ### API Route for Enabling Draft Mode (Remix.js) Source: https://github.com/sanity-io/visual-editing/blob/main/packages/preview-url-secret/README.md A Remix.js API route to validate preview URLs and enable draft mode. Requires SANITY_API_READ_TOKEN and uses validatePreviewUrl. ```typescript // ./app/routes/api.draft.ts import {redirect, type LoaderFunctionArgs} from '@remix-run/node' import {validatePreviewUrl} from '@sanity/preview-url-secret' import {client} from '~/sanity/client' import {commitSession, getSession} from '~/sessions' export const loader = async ({request}: LoaderFunctionArgs) => { if (!process.env.SANITY_API_READ_TOKEN) { throw new Response('Draft mode missing token!', {status: 401}) } const clientWithToken = client.withConfig({ // Required, otherwise the URL preview secret can't be validated token: process.env.SANITY_API_READ_TOKEN, }) const {isValid, redirectTo = '/'} = await validatePreviewUrl(clientWithToken, request.url) if (!isValid) { throw new Response('Invalid secret!', {status: 401}) } const session = await getSession(request.headers.get('Cookie')) await session.set('projectId', client.config().projectId) return redirect(redirectTo, { headers: { 'Set-Cookie': await commitSession(session), }, }) } ``` -------------------------------- ### Define Environment Variables for Sanity Source: https://github.com/sanity-io/visual-editing/blob/main/packages/svelte-loader/README.md Configure essential Sanity environment variables in a .env file for project configuration. Ensure sensitive tokens are not exposed publicly. ```bash # .env SANITY_API_READ_TOKEN="..." PUBLIC_SANITY_PROJECT_ID="..." PUBLIC_SANITY_DATASET="..." PUBLIC_SANITY_API_VERSION="..." PUBLIC_SANITY_STUDIO_URL="..." ``` -------------------------------- ### Enable Draft Mode in Next.js App Router Source: https://github.com/sanity-io/visual-editing/blob/main/packages/preview-url-secret/README.md Use this API handler to enable Sanity.io's draft mode. Ensure the SANITY_API_READ_TOKEN environment variable is set. ```typescript // ./app/api/draft/route.ts import {client} from '@/sanity/lib/client' import {validatePreviewUrl} from '@sanity/preview-url-secret' import {draftMode} from 'next/headers' import {redirect} from 'next/navigation' const clientWithToken = client.withConfig({ // Required, otherwise the URL preview secret can't be validated token: process.env.SANITY_API_READ_TOKEN, }) export async function GET(req: Request) { const {isValid, redirectTo = '/'} = await validatePreviewUrl(clientWithToken, req.url) if (!isValid) { return new Response('Invalid secret', {status: 401}) } draftMode().enable() redirect(redirectTo) } ``` -------------------------------- ### Enable Visual Editing in React.js Source: https://github.com/sanity-io/visual-editing/blob/main/packages/visual-editing/README.md For React applications without framework integration, use the `enableVisualEditing` function within a `useEffect` hook. Remember to integrate your router with the `history` option for URL bar compatibility in Presentation mode. ```tsx import { enableVisualEditing } from '@sanity/visual-editing' import { useEffect } from 'react' export default function VisualEditing() { useEffect(() => { const disable = enableVisualEditing({ history: {} // recommended, integrate your router here so it works with the URL bar in Presentation zIndex: 1000, // optional }) return () => disable() }, []) return null } ``` -------------------------------- ### Visual Editing Options Interface Source: https://github.com/sanity-io/visual-editing/blob/main/packages/visual-editing/README.md Defines the options for visual editing, including a custom refresh function. The `refresh` function can return `false` for default behavior or a `Promise` to show a loading UI. ```typescript interface VisualEditingOptions { refresh?: (payload: HistoryRefresh) => false | Promise } type HistoryRefresh = | { source: 'manual' livePreviewEnabled: boolean } | { source: 'mutation' livePreviewEnabled: boolean document: { _id: string _type: string _rev: string slug?: { current?: string | null } } } ``` -------------------------------- ### Update Import Statements for Visual Editing Source: https://github.com/sanity-io/visual-editing/blob/main/packages/overlays/README.md Replace import statements from '@sanity/overlays' with those from '@sanity/visual-editing'. This ensures that the new visual editing functionalities are correctly imported. ```diff -import { enableOverlays, type DisableOverlays } from '@sanity/overlays' +import { enableVisualEditing, type DisableVisualEditing } from '@sanity/visual-editing' ``` -------------------------------- ### Display Preview State in Svelte Component Source: https://github.com/sanity-io/visual-editing/blob/main/packages/svelte-loader/README.md Conditionally renders content based on the current preview state using the `isPreviewing` store. This component demonstrates how to check if previews are enabled. ```svelte {#if $isPreviewing}
Previews Enabled
{:else}
Previews Disabled
{/if} ``` -------------------------------- ### Integrate Visual Editing with React Router Source: https://github.com/sanity-io/visual-editing/blob/main/packages/visual-editing/README.md Use the `VisualEditing` component from `@sanity/visual-editing/react-router` in your `app/root.tsx` for React Router applications. It conditionally renders based on environment variables. ```tsx import {json} from '@react-router/node' import {Links, Meta, Outlet, Scripts, ScrollRestoration, useLoaderData} from '@react-router' import {VisualEditing} from '@sanity/visual-editing/react-router' export const loader = () => { return json({ ENV: { SANITY_VISUAL_EDITING_ENABLED: process.env.SANITY_VISUAL_EDITING_ENABLED === 'true', }, }) } export default function App() { const {ENV} = useLoaderData() return (
{ENV.SANITY_VISUAL_EDITING_ENABLED && ( )} ) } ``` -------------------------------- ### Integrate VisualEditing with Next.js App Router Layout Source: https://github.com/sanity-io/visual-editing/blob/main/packages/visual-editing/README.md Adds the VisualEditing component to the root layout for Next.js App Router. It's typically conditionally rendered based on Draft Mode. ```tsx import {VisualEditing} from 'next-sanity/visual-editing' import {draftMode} from 'next/headers' export default function RootLayout({children}: {children: React.ReactNode}) { return ( {children} {draftMode().isEnabled && ( )} ) } ``` -------------------------------- ### Enable Draft Mode in Next.js Pages Router Source: https://github.com/sanity-io/visual-editing/blob/main/packages/preview-url-secret/README.md This API handler enables Sanity.io's draft mode for the Pages Router. It requires the SANITY_API_READ_TOKEN environment variable. ```typescript // ./pages/api/draft.ts import {client} from '@/sanity/lib/client' import {validatePreviewUrl} from '@sanity/preview-url-secret' import type {NextApiRequest, NextApiResponse} from 'next' const clientWithToken = client.withConfig({ // Required, otherwise the URL preview secret can't be validated token: process.env.SANITY_API_READ_TOKEN, }) export default async function handler(req: NextApiRequest, res: NextApiResponse) { if (!req.url) { throw new Error('Missing url') } const {isValid, redirectTo = '/'} = await validatePreviewUrl(clientWithToken, req.url) if (!isValid) { return res.status(401).send('Invalid secret') } // Enable Draft Mode by setting the cookies res.setDraftMode({enable: true}) res.writeHead(307, {Location: redirectTo}) res.end() } ``` -------------------------------- ### Define GROQ Query and Result Type Source: https://github.com/sanity-io/visual-editing/blob/main/packages/svelte-loader/README.md Defines a GROQ query for fetching a single page based on its slug and an interface for the expected result structure. This is typically placed in a shared queries file. ```typescript // src/lib/queries.ts export const pageQuery = `*[_type == "page" && slug.current == $slug][0]` export interface PageResult { title: string // ...etc } ``` -------------------------------- ### Remix Visual Editing Integration Source: https://github.com/sanity-io/visual-editing/blob/main/packages/visual-editing/README.md Default internal implementation for Remix apps using `useRevalidator` hook for visual editing refresh logic. Handles 'manual' and 'mutation' sources. ```tsx import {useRevalidator} from 'react-router' import {VisualEditing} from '@sanity/visual-editing/react-router' export default function App() { const {ENV} = useLoaderData() const revalidator = useRevalidator() return ( {ENV.SANITY_VISUAL_EDITING_ENABLED && ( { if (payload.source === 'manual') { revalidator.revalidate() } if (payload.source === 'mutation' && !payload.livePreviewEnabled) { revalidator.revalidate() } }} /> )} ) } ``` -------------------------------- ### Integrate VisualEditing with Next.js Pages Router App Source: https://github.com/sanity-io/visual-editing/blob/main/packages/visual-editing/README.md Adds the VisualEditing component to the root of the Next.js Pages Router application. It's typically conditionally rendered based on Preview Mode or Draft Mode. ```tsx import {VisualEditing} from '@sanity/visual-editing/next-pages-router' import type {AppProps} from 'next/app' import {useRouter} from 'next/router' export default function App({Component, pageProps}: AppProps) { const {isPreview} = useRouter() // A common alternative pattern to `isPreview` and `useRouter` is to pass down the draftMode/preview from getStaticProps/getServerSideProps/getInitialProps // const { draftMode } = pageProps return ( <> {isPreview && ( )} ) } ``` -------------------------------- ### API Route for Disabling Draft Mode (Remix.js) Source: https://github.com/sanity-io/visual-editing/blob/main/packages/preview-url-secret/README.md A Remix.js API route to disable draft mode by destroying the session cookie. ```typescript // ./app/routes/api.disable-draft.ts import {redirect, type LoaderFunctionArgs} from '@remix-run/node' import {destroySession, getSession} from '~/sessions' export const loader = async ({request}: LoaderFunctionArgs) => { const session = await getSession(request.headers.get('Cookie')) return redirect('/', { headers: { 'Set-Cookie': await destroySession(session), }, }) } ``` -------------------------------- ### Update App Locals Interface for TypeScript Source: https://github.com/sanity-io/visual-editing/blob/main/packages/svelte-loader/README.md Extend the App.Locals interface in your TypeScript configuration to include properties added by createRequestHandler from @sanity/svelte-loader. ```ts // app.d.ts import type {LoaderLocals} from '@sanity/svelte-loader' declare global { namespace App { interface Locals extends LoaderLocals {} } } export {} ``` -------------------------------- ### Custom Visual Editing Refresh Logic Source: https://github.com/sanity-io/visual-editing/blob/main/packages/visual-editing/README.md Customizing the `refresh` prop for `VisualEditing` to control revalidation behavior, including using `refreshDefault` and specific conditions for document types. ```tsx { if (payload.source === 'manual') { return refreshDefault() } // Always revalidate on mutations for document types that are used for MetaFunctions that render in if (payload.source === 'mutation' && payload.document._type === 'settings') { return refreshDefault() } }} /> ``` -------------------------------- ### Product Template with Data Attributes Source: https://github.com/sanity-io/visual-editing/blob/main/packages/react-loader/README.md This template component utilizes `encodeDataAttribute` to add `data-sanity` attributes to elements, enabling them to be clickable in the Sanity Presentation tool. The `image` attribute specifically links to the image field. ```tsx // ./src/app/templates/product.tsx import {EncodeDataAttributeCallback} from '@sanity/react-loader' interface Product {} interface Props { data: Product encodeDataAttribute: EncodeDataAttributeCallback } export default function ProductTemplate(props: Props) { const {data, encodeDataAttribute} = props return ( <> ) } ``` -------------------------------- ### Disable Draft Mode in Next.js App Router Source: https://github.com/sanity-io/visual-editing/blob/main/packages/preview-url-secret/README.md Create this API route to easily disable Sanity.io's draft mode when exiting Presentation Mode. ```typescript // ./app/api/disable-draft/route.ts import {draftMode} from 'next/headers' import {NextRequest, NextResponse} from 'next/server' export function GET(request: NextRequest) { draftMode().disable() const url = new URL(request.nextUrl) return NextResponse.redirect(new URL('/', url.origin)) } ``` -------------------------------- ### Apply Data Attributes for Array Items Source: https://github.com/sanity-io/visual-editing/blob/main/packages/visual-editing/src/util/drag-and-drop.md Use the createDataAttribute function to generate 'data-sanity' attributes for array items. This is crucial for enabling drag and drop by mapping elements to their Sanity paths. The attribute requires the parent document's ID, type, and the array path including the child's _key. ```jsx import {createDataAttribute} from '@sanity/visual-editing' { arrayItems.map((arrayItem, i) => ( )) } ``` -------------------------------- ### Custom Refresh Logic for Mutation Source Source: https://github.com/sanity-io/visual-editing/blob/main/packages/visual-editing/README.md Implement custom refresh logic for mutation sources when using `revalidateTag`. This snippet revalidates a tag based on the document's type, aligning with GROQ webhook patterns. ```tsx { 'use server' // Guard against a bad actor attempting to revalidate the page if (!draftMode().isEnabled) { return } if (payload.source === 'manual') { await revalidateTag('all', {expire: 0}) } if (payload.source === 'mutation') { // Call `revalidateTag` in the same way as ./app/api/revalidate/route.ts await revalidateTag(payload.document._type, {expire: 0}) } }} /> ``` -------------------------------- ### Define Array Field for Drag and Drop Source: https://github.com/sanity-io/visual-editing/blob/main/packages/visual-editing/src/util/drag-and-drop.md Configure an array field in your Sanity schema to enable drag and drop functionality for its items. Ensure the 'of' property defines the structure of the array items. ```jsx defineField({ type: 'array', name: 'ctas', title: 'CTAs', of: [ { type: 'object', name: 'cta', title: 'CTA', fields: [ ... ], }, ], }), ``` -------------------------------- ### Disable Draft Mode in Next.js Pages Router Source: https://github.com/sanity-io/visual-editing/blob/main/packages/preview-url-secret/README.md Use this API handler to disable Sanity.io's draft mode when leaving Presentation Mode in a Pages Router application. ```typescript // ./pages/api/disable-draft.ts import type {NextApiRequest, NextApiResponse} from 'next' export default function handler(_req: NextApiRequest, res: NextApiResponse): void { // Exit the current user from "Draft Mode". res.setDraftMode({enable: false}) // Redirect the user back to the index page. res.writeHead(307, {Location: '/'}) res.end() } ``` -------------------------------- ### Prevent Stega Children Overriding Array Paths Source: https://github.com/sanity-io/visual-editing/blob/main/packages/visual-editing/src/util/drag-and-drop.md Use `stegaClean` to prevent Stega-encoded strings from interfering with drag and drop on parent array items. This is useful when a string's overlay prevents interaction with the parent. ```jsx ``` ```jsx import {stegaClean} from '@sanity/client/stega' ``` ```jsx ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.