Visual Editing

Visual Editing with Nuxt

Get started with Sanity Visual Editing in a new or existing Nuxt application.

Following this guide will enable you to:

  • Render overlays in your application, allowing content editors to jump directly from Sanity content to its source in Sanity Studio.
  • Edit your content and see changes reflected in an embedded preview of your application in Sanity’s Presentation tool.
  • Provide instant updates and seamless switching between draft and published content.

Prerequisites

Nuxt application setup

The following steps should be performed in your Nuxt application.

Install dependencies

Install the Sanity module, which provides your application with data fetching and Visual Editing capabilities.

Environment variables

Create a .env file in your application’s root directory to provide Sanity-specific configuration.

You can use Manage to find your project ID and dataset, and to create a token with Viewer permissions which will be used to fetch preview content.

The URL of your Sanity Studio will depend on where it is hosted or embedded.

# .env
# Public
SANITY_PROJECT_ID="YOUR_PROJECT_ID"
SANITY_DATASET="YOUR_DATASET"
SANITY_STUDIO_URL="YOUR_STUDIO_URL"
# Private
SANITY_VIEWER_TOKEN="YOUR_VIEWER_TOKEN"

Application setup

Sanity module

Configure the Sanity module to handle fetching data from Content Lake.

Configuring the stega option enables automatic overlays for basic data types when preview mode is enabled. Read more about how stega works.

// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@nuxtjs/sanity'],
  sanity: {
    projectId: process.env.SANITY_PROJECT_ID,
    dataset: process.env.SANITY_DATASET,
    apiVersion: '2026-07-01',
    visualEditing: {
      token: process.env.SANITY_VIEWER_TOKEN,
      studioUrl: process.env.SANITY_STUDIO_URL,
      stega: true
    }
  }
})

VisualEditing component props

The <VisualEditing /> component rendered by the @nuxtjs/sanity module accepts additional props introduced in @sanity/visual-editing 5.5.0. Make sure your project uses version 5.5.0 or later.

For example, use onSuspiciousStega to log reports of stega found in unsafe DOM placements. The module serializes the callback into the build, so it must be self-contained. Don't reference imports or variables defined outside the function body.

// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@nuxtjs/sanity'],
  sanity: {
    projectId: process.env.SANITY_PROJECT_ID,
    dataset: process.env.SANITY_DATASET,
    apiVersion: '2026-07-01',
    visualEditing: {
      token: process.env.SANITY_VIEWER_TOKEN,
      studioUrl: process.env.SANITY_STUDIO_URL,
      stega: true,
      // Report stega found in unsafe DOM placements
      onSuspiciousStega: (reports) => {
        for (const report of reports) {
          console.warn(`Stega found in ${report.kind}`, report)
        }
      },
    }
  }
})

Performance note

  • Optional. Default: false. By default, <VisualEditing /> intercepts copy events and removes stega encoding from both text/plain and text/html clipboard payloads so users do not get invisible characters when copying text from the preview page. Copies without stega are left untouched. Set keepStegaOnCopy to true to disable this behavior.

  • onSuspiciousStega

    (reports: SuspiciousStegaReport[]) => void

    Optional. Opt-in callback that reports stega found in unsafe DOM placements: element attributes (class, id, href, src, style, data-*, and others), inside <head> (title, meta[content]), in <script> or <style> text content (including JSON-LD), in textarea form values, or in the page URL. Each report includes kind, element (if applicable), attribute (if applicable), value, cleaned, and, when the encoded value resolves to a Sanity node, sanity. Performs an initial full DOM audit (TreeWalker and MutationObserver), then incremental idle-time checks. Intended for debugging rather than production use.

Rendering pages

First, set up the queries you will use to fetch data from Content Lake.

// queries.ts
export type PageResult = { title: string }

export const pageQuery = /* groq */`*[_type == "page"][0]{title}`
// pages/index.vue
<script setup lang="ts">
import {pageQuery, type PageResult} from '../queries'

const {data, pending} = await useSanityQuery<PageResult>(pageQuery)
</script>

<template>
  <div v-if="pending">Loading...</div>
  <h1 v-else>{{ data?.title }}</h1>
</template>

Studio setup

To set up the Presentation Tool in your Studio, import the tool from sanity/presentation, add it to your plugins array, and configure previewUrl with an initial preview URL and the endpoint used to enable preview mode.

We similarly recommend using environment variables loaded via a .env file to support development and production environments.

// sanity.config.ts
import {defineConfig} from 'sanity'
import {presentationTool} from 'sanity/presentation'

export default defineConfig({
  // ... project configuration
  plugins: [
    presentationTool({
      previewUrl: {
        initial: process.env.SANITY_STUDIO_PREVIEW_ORIGIN,
        previewMode: {
          enable: '/preview/enable',
        },
      }
    }),
    // ... other plugins
  ],
})

Optional extras

Adding data attributes

useSanityQuery also returns an encodeDataAttribute helper method for generating data-sanity attributes. These attributes give you direct control over rendering overlays in your application, and are especially useful if not using stega encoding.

// pages/index.vue
<script setup lang="ts">
import {pageQuery, type PageResult} from '../queries'

const {data, pending, encodeDataAttribute} = await useSanityQuery<PageResult>(pageQuery)
</script>

<template>
  <div v-if="pending">Loading...</div>
  <h1 v-else :data-sanity="encodeDataAttribute(['title'])">{{ data?.title }}</h1>
</template>

Next steps

You now have a Nuxt application that renders overlays, updates content instantly, and previews drafts in the Presentation Tool. To keep going, learn more about configuring the Presentation Tool, customizing overlays, and how stega encoding works.

Was this page helpful?