Getting Started
Use Localess in a Next.js app — App Router with React Server Components, or a static export.
Package: @localess/react
The @localess/react package is the official React integration for the Localess headless CMS platform. In a Next.js app you pick the export that matches your rendering mode:
| Rendering mode | Export | Live editing |
|---|---|---|
| App Router with React Server Components | @localess/react/rsc | Yes, driven by a Server Action |
Static export (output: 'export') | @localess/react/ssr | Yes, via the client-side fallback |
Both are covered below. The rest of the API — components, hooks, rich text — is identical across them.
⚠️ Security Notice: Use a secret token when
localessInit()runs exclusively in server-only code that is never bundled to the browser, which is the case for both walkthroughs on this page. Use a public, read-only token whereverlocalessInit()runs in code that ships to the browser — that is the static-export fallback's second, client-side call (see Visual Editor).
Requirements
- Node.js >= 24.0.0
- React 17, 18, or 19
Installation
# npm
npm install @localess/react@latest
# yarn
yarn add @localess/react@latest
# pnpm
pnpm add @localess/react@latestSetup
Call localessInit once, in a module of its own, to configure the client, register your components, and optionally enable the Visual Editor. It returns the client, so export it from that module and import it wherever you fetch:
// src/shared/localess.ts
import { localessInit } from "@localess/react";
import { Page } from "@/components/localess/page";
import { Button } from "@/components/localess/button";
export const localessClient = localessInit({
origin: "https://my-localess.web.app",
spaceId: "YOUR_SPACE_ID",
token: "YOUR_API_TOKEN",
enableSync: true, // Enable Visual Editor sync script
components: {
'Page': Page,
'Button': Button,
},
});// anywhere that fetches
import { localessClient } from "@/shared/localess";
const content = await localessClient.getContentBySlug<Page>('home', { locale });Putting the call in a module that your pages import is what guarantees it has run before anything reaches the registry. A bare localessInit() side effect in a root layout is not enough on its own under the App Router, where a page module can be evaluated without the layout's module body having run first.
If you'd rather not thread the client through imports, getLocalessClient() returns the same instance from anywhere after initialization — see Fetching content.
Schema keys must match
_schemaexactly. The examples here use'Page'/'Button'because that's how the schemas are named in the space they came from. Use whatever casing your own CMS uses — there is no normalization on a hand-written map.
Initialization Options
| Option | Type | Required | Default | Description |
|---|---|---|---|---|
origin | string | Yes | — | Fully qualified domain with protocol |
spaceId | string | Yes | — | Localess Space ID, found in Space settings |
token | string | Yes | — | Localess API token (keep secret — server-side only) |
version | 'draft' | string | No | 'published' | Default content version |
debug | boolean | No | false | Enable debug logging |
cacheTTL | number | false | No | 300 | Cache TTL in seconds. Set false to disable caching entirely |
components | Record<string, React.ElementType> | No | {} | Map of schema keys to React components |
fallbackComponent | React.ElementType | No | — | Component rendered when a schema key has no registered component |
enableSync | boolean | No | false | Load the Visual Editor sync script for live-editing support |
Writing components
Components receive data, links, and references as props — type them with LocalessSchemaProps<T>, the contract every registered component accepts (there's no base class to extend, unlike @localess/angular's SchemaComponent<T>). Always spread localessEditable/localessEditableField so the Visual Editor can highlight and select the block and its fields:
import { localessEditable, localessEditableField } from "@localess/react";
import type { LocalessSchemaProps } from "@localess/react";
import type { HeroSection } from "./.localess/localess";
const Hero = ({ data, links, references }: LocalessSchemaProps<HeroSection>) => (
<section {...localessEditable(data)}>
<h1 {...localessEditableField<HeroSection>('title')}>{data.title}</h1>
<p {...localessEditableField<HeroSection>('subtitle')}>{data.subtitle}</p>
</section>
);Pass links and references through the entire tree — child LocalessComponents need them.
localessEditable(content)
Marks a content block root element as editable.
import { localessEditable } from "@localess/react";
const Header = ({ data }) => (
<nav {...localessEditable(data)}>
{/* ... */}
</nav>
);localessEditableField<T>(fieldName)
Marks a specific field within a content block as editable, with type-safe field name inference when combined with generated types.
import { localessEditableField } from "@localess/react";
const Hero = ({ data }: { data: HeroBlock }) => (
<section {...localessEditable(data)}>
<h1 {...localessEditableField<HeroBlock>('title')}>{data.title}</h1>
<p {...localessEditableField<HeroBlock>('subtitle')}>{data.subtitle}</p>
</section>
);App Router with React Server Components
Use @localess/react/rsc when you want React Server Components and Visual Editor live editing together.
Setup — app/layout.tsx
// Server Component — safe to use API token here
import { localessInit } from "@localess/react/rsc";
import { Page, Header, Teaser, Footer } from "@/components";
localessInit({
origin: process.env.LOCALESS_ORIGIN!,
spaceId: process.env.LOCALESS_SPACE_ID!,
token: process.env.LOCALESS_TOKEN!,
enableSync: process.env.NODE_ENV !== 'production',
components: { Page, Header, Teaser, Footer },
});
export default function RootLayout({ children }: { children: React.ReactNode }) {
return <html><body>{children}</body></html>;
}Rendering — app/[locale]/page.tsx
Use LocalessDocument for a zero-boilerplate live-editing integration, or useLocaless for client-side re-fetching with more control.
Option A — LocalessDocument (recommended): it's a Server Component internally (only its sync subscription runs client-side), so it renders directly in the Server Component — no separate Client Component file needed.
import { getLocalessClient, LocalessDocument } from "@localess/react/rsc";
export default async function Home({ params }: { params: { locale: string } }) {
const { locale } = await params;
const content = await getLocalessClient().getContentBySlug("home", { locale });
return <LocalessDocument document={content} />;
}Option B — useLocaless hook: re-fetches on the client, so it needs an actual 'use client' file.
// app/[locale]/page.tsx (Server Component)
import { getLocalessClient } from "@localess/react/rsc";
import PageClient from "./page-client";
export default async function Home({ params }: { params: { locale: string } }) {
const { locale } = await params;
const content = await getLocalessClient().getContentBySlug("home", { locale });
return <PageClient initialContent={content} locale={locale} />;
}// app/[locale]/page-client.tsx (Client Component)
'use client';
import { useLocaless, LocalessComponent, localessEditable } from "@localess/react/rsc";
export default function PageClient({ initialContent, locale }) {
const content = useLocaless("home", { locale }) ?? initialContent;
return (
<main {...localessEditable(content.data)}>
{content.data?.body?.map(item => (
<LocalessComponent key={item._id} data={item} links={content.links} references={content.references} />
))}
</main>
);
}Playground:
playgrounds/nextis a full working Next.js App Router project built on Option A —localessInit()inpage.tsxand<LocalessDocument document={document} />rendered directly from the Server Component.
Static Export (output: 'export')
Use @localess/react/ssr when your Next.js project uses output: 'export' for static site generation. Live editing is not available in this mode.
next.config.js
/** @type {import('next').NextConfig} */
module.exports = { output: 'export' };Setup — lib/localess.ts
import { localessInit } from "@localess/react/ssr";
import { Page, Header, Teaser } from "@/components";
export const getClient = localessInit({
origin: process.env.LOCALESS_ORIGIN!,
spaceId: process.env.LOCALESS_SPACE_ID!,
token: process.env.LOCALESS_TOKEN!,
// enableSync is not applicable in static export — omit or set to false
components: { Page, Header, Teaser },
});Page — app/page.tsx
import { LocalessServerComponent } from "@localess/react/ssr";
import { getLocalessClient } from "@localess/react/ssr";
import "@/lib/localess"; // ensure init runs
export default async function Home() {
const client = getLocalessClient();
const content = await client.getContentBySlug("home", { locale: "en" });
return (
<main>
<LocalessServerComponent data={content.data} links={content.links} references={content.references} />
</main>
);
}@localess/react/ssr also exports LocalessServerDocument, which takes the full Content<T> response as a single document prop (like LocalessDocument, but with no sync attributes since live editing has no meaning once the HTML is pre-baked):
import { LocalessServerDocument } from "@localess/react/ssr";
<LocalessServerDocument document={content} />Playground:
playgrounds/next-staticmirrors the RSC playground but targetsoutput: 'export'and uses@localess/react/ssrend to end.
Playground:
playgrounds/next(App Router + RSC) andplaygrounds/next-static(output: 'export') are the reference implementations for this guide.
AI Coding Agents
This package ships a SKILL.md file that provides AI coding agents (GitHub Copilot, Claude Code, Cursor, and others) with accurate, up-to-date APIs, patterns, and best practices.
Reference it from your project's AGENTS.md:
## Localess
@node_modules/@localess/react/SKILL.md