Localess
Nuxt

Visual Editor

Live-editing in a Nuxt app — enableSync, LocalessDocument, the useLocalessSync composable, and marking elements editable.

Visual Editor

Set enableSync: true in the localess block of nuxt.config.ts to load the Visual Editor sync script (sync-v1.js):

// nuxt.config.ts
localess: {
  origin: 'https://my-localess.web.app',
  spaceId: 'YOUR_SPACE_ID',
  token: process.env.LOCALESS_PUBLIC_TOKEN, // required — sync runs in the browser
  enableSync: true,
}

Sync needs the public token: the script runs in the browser, so a server-only setup with just serverToken has nothing to sync with. It's a no-op outside the Localess Visual Editor iframe, so it's harmless in a normal browser session — though there's no reason to ship it in a production build that will never be framed by the editor.

LocalessDocument

The whole integration, for most apps. It subscribes to input/change editor events automatically when sync is active and re-renders in place. It applies only the edits for its own document (matched by document.id), so a page rendering several documents updates each one independently, and an instance that mounts late, after a lazy fetch or a client-side navigation, catches up with the latest edit:

<script setup lang="ts">
import { type Content } from '@localess/vue';

import type { Page } from '#shared/models/localess';

const { data: content } = await useAsyncData('content', () => $fetch<Content<Page>>('/api/content'));
</script>

<template>
  <LocalessDocument v-if="content" :document="content" />
</template>

Nothing else is required — the content was fetched server-side with the secret token, and the live updates arrive over the editor bridge with the public one.

useLocalessSync

For an integration that manages its own state instead of handing it to LocalessDocument, subscribe to the bridge directly. It returns a Ref holding the latest matching event:

<script setup lang="ts">
const latest = useLocalessSync(['input', 'change']);
</script>

<template>
  <pre>{{ latest?.data }}</pre>
</template>

The subscription registers in onMounted, so it never runs during SSR, and is removed when the calling component (or effect scope) is disposed. The ref stays undefined when sync isn't active — no enableSync, no public token, or not inside the editor iframe.

useLocalessSync is auto-imported. Its events carry every document's edits; to follow one document, use localessSyncOnDocument instead.

localessSyncOnDocument

localessSyncOnDocument(documentId, callback) calls callback with the edited content for that document only, and returns a function that removes the subscription. Like LocalessDocument, it replays the latest edit to a late subscriber. It is not auto-imported, so import it from @localess/vue, and subscribe in onMounted so it never runs during SSR:

<script setup lang="ts">
import { type Content, localessSyncOnDocument } from '@localess/vue';

import type { Page } from '#shared/models/localess';

const { data: content } = await useAsyncData('content', () => $fetch<Content<Page>>('/api/content'));
const page = ref(content.value?.data);

let unsubscribe: (() => void) | undefined;
onMounted(() => {
  if (content.value) {
    unsubscribe = localessSyncOnDocument<Page>(content.value.id, data => (page.value = data));
  }
});
onUnmounted(() => unsubscribe?.());
</script>

<template>
  <h1 v-if="page">{{ page.title }}</h1>
</template>

Marking elements editable

The editor can only select what it can identify. <LocalessComponent> applies the block-level attributes for you; add field-level ones yourself:

<script setup lang="ts">
import { type LocalessSchemaProps } from '@localess/vue';

import type { Page } from '#shared/models/localess';

const props = defineProps<LocalessSchemaProps<Page>>();
</script>

<template>
  <main v-bind="localessEditable(props.data)">
    <h1 v-bind="localessEditableField<Page>('title')">{{ props.data.title }}</h1>
  </main>
</template>

localessEditable(data) emits data-ll-id and data-ll-schema; localessEditableField(name) emits data-ll-field. Both are auto-imported and return plain attribute objects, so v-bind is all that's needed. Passing the content type as localessEditableField<Page>('title') restricts the argument to that schema's own field names, so a typo is a compile error.

See Visual Editor for what those attributes mean on the editor side.

Available events

EventWhen
inputA field value changed — every keystroke (real-time preview)
changeBlocks added, removed, duplicated or reordered — and once when the preview connects, with the current content
saveContent saved
publishContent published
unpublishContent unpublished
pongEditor heartbeat response
enterSchemaEditor cursor enters a schema block
hoverSchemaEditor cursor hovers over a schema block
leaveSchemaEditor cursor leaves a schema block

Every input, change, save, publish and unpublish event carries a documentId — the id of the edited document, as returned by the API — so a page rendering several documents can apply each edit to the right one. LocalessDocument (<ll-document> in Angular) filters on it for you; check event.documentId yourself only when you subscribe to raw events. Because edits are matched by documentId, update your Localess deployment together with the SDK — see Troubleshooting.

On this page