Content
Fetch content server-side with the secret token or client-side with the public one, and render it by schema, in a Nuxt app.
Two clients, two tokens
The module's token split gives you two ways to reach the API, and which one you use decides what you can read:
useLocalessServerClient() | useLocaless() | |
|---|---|---|
| Token | serverToken (secret) | token (public) |
| Runs in | Server only — Nitro routes, server/ utilities | Anywhere the Vue plugin is installed |
| Can read draft content | Yes | No |
| Throws if | Called in the browser, or serverToken unset | token unset |
Prefer the server client. It keeps the read off the browser entirely, and it's the only way to reach draft or unpublished content — a public token is scoped to published content and translations.
Server-side fetching
useLocalessServerClient() is exposed through the #localess/server alias, which the module registers in both the app and Nitro build graphs:
// server/api/content.ts
import { useLocalessServerClient } from '#localess/server';
export default defineEventHandler(async event => {
const client = useLocalessServerClient();
return client.getContentBySlug((getQuery(event).slug as string) || 'home');
});Then read it from a page with Nuxt's own data layer:
<script setup lang="ts">
const { data: content } = await useAsyncData('content', () =>
$fetch('/api/content', { query: { slug: 'home' } })
);
</script>
<template>
<LocalessDocument v-if="content" :document="content" />
</template>Nuxt's payload transfer hydrates the server-fetched result to the client, so the browser doesn't re-fetch — the module needs no hydration mechanism of its own.
The client is memoised for the lifetime of the server process, so its in-memory cache is shared across requests. That's safe because every caller uses the same token and therefore has identical permissions. Tune it with the cacheTTL module option, or set cacheTTL: false to disable caching.
Draft content
Reading unpublished content needs the secret token, so it only works through the server client:
const client = useLocalessServerClient();
const draft = await client.getContentBySlug('home', { version: 'draft' });Setting version: 'draft' on the module's public config would send the flag from the browser with a public token, which the API rejects. Gate drafts behind a server route instead — see Draft & Publish.
Client-side fetching
useLocaless() returns the client the module's plugin installed, authenticated with the public token. Use it for reads that genuinely have to happen in the browser — an event handler, a search box, a paginated list:
<script setup lang="ts">
const client = useLocaless();
async function loadMore(slug: string) {
return client.getContentBySlug(slug);
}
</script>useLocaless is auto-imported. It throws if no public token was configured, which is the expected outcome of a deliberately server-only setup.
Rendering content
LocalessComponent
Renders one content block by looking up its _schema in the component registry. Auto-imported, so no import line is needed:
<template>
<LocalessComponent
v-for="block in props.data.blocks ?? []"
:key="block._id"
:data="block"
:links="props.links"
:references="props.references"
/>
</template>| Prop | Type | Required | Description |
|---|---|---|---|
data | ContentData | Yes | Content data object. The component looks up data._schema in the registry |
links | Links | No | Resolved content links map, forwarded to the rendered component |
references | References | No | Resolved references map, forwarded to the rendered component |
assets | Assets | No | Resolved content assets map, forwarded to the rendered component |
It always applies localessEditable(data)'s attributes to the rendered component's root, so the Visual Editor can target it.
If a schema key has no registered component, the
fallbackComponentrenders if configured, and otherwise an inline error naming the missing key. The DevTools Localess tab shows every discovered key, which is usually the fastest way to spot the mismatch.
LocalessDocument
Renders a whole Content response, unpacking data/links/references/assets for you, and re-renders live on Visual Editor edits. This is what a page should render:
<template>
<LocalessDocument :document="content" />
</template>| Prop | Type | Required | Description |
|---|---|---|---|
document | Content<T> | Yes | The full content response from getContentBySlug/getContentById |
Reassigning document re-syncs the rendered tree, so a client-side navigation to a different slug swaps the content correctly even though Nuxt reuses the same component instance.
Use LocalessDocument for the top-level content of a page, and LocalessComponent for nested blocks inside an already-rendered tree. See Visual Editor for the live-sync half.
Getting Started
Nuxt module for Localess — one config block for content delivery, component auto-registration, a server-only client for draft content, and Visual Editor integration.
Visual Editor
Live-editing in a Nuxt app — enableSync, LocalessDocument, the useLocalessSync composable, and marking elements editable.