Localess
SDK

React

Package: @localess/react — v3.0.0

Package: @localess/react — v3.0.0

The @localess/react package is the official React integration for the Localess headless CMS platform. It provides component mapping, rich text rendering, and Visual Editor synchronization support for React applications.

⚠️ Security Notice: This package uses @localess/client internally, which requires an API token for server-side data fetching. Always fetch Localess content server-side (e.g., Next.js Server Components, API routes, or getServerSideProps) and never expose your token in client-side code.

Requirements

  • Node.js >= 24.0.0
  • React 17, 18, or 19

Installation

# npm
npm install @localess/react

# yarn
yarn add @localess/react

# pnpm
pnpm add @localess/react

Choosing the Right Export

@localess/react provides three different exports to suit different rendering strategies:

ExportUse CaseLive EditingStatic Export
@localess/reactSingle Page Applications (SPA), client-side renderingYesYes
@localess/react/ssrSSR without live editing, Next.js static exportsNoYes
@localess/react/rscReact Server Components with live editingYesNo

When to Use Each Export

Use @localess/react (default) for:

  • Single Page Applications (SPA) or fully client-rendered React apps
  • Apps where localessInit and components run entirely in the browser

Use @localess/react/ssr for:

  • Next.js projects with output: 'export' (static site generation)
  • Server-side rendering where live editing is not required
  • Scenarios where bundle size matters and you want to exclude all browser-only sync code

Use @localess/react/rsc for:

  • Next.js App Router with React Server Components
  • Apps that need live Visual Editor editing alongside server rendering
  • Modern Next.js apps with a server/client component split

Quick Comparison

// SPA — everything runs client-side
import { localessInit, LocalessComponent, useLocaless } from "@localess/react";

// SSR — server-safe, no live editing, no hooks
import { localessInit, LocalessServerComponent } from "@localess/react/ssr";

// RSC — server components + client components for live editing
import { localessInit, LocalessServerComponent } from "@localess/react/rsc";          // server
import { LocalessDocument, useLocaless, localessEditable } from "@localess/react/rsc"; // client

Note: When using Next.js with output: 'export', always use @localess/react/ssr. The RSC export is not compatible with static exports.

Playgrounds: Full working Next.js projects are available for both modes — playgrounds/next (App Router + RSC, live editing) and playgrounds/next-static (output: 'export', static).

Getting Started

1. Initialize the SDK

Call localessInit once at application startup (e.g., in your root layout or _app.tsx) to configure the client, register your components, and optionally enable the Visual Editor.

import { localessInit } from "@localess/react";
import { Page, Header, Teaser, Footer } from "@/components";

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,
    'header': Header,
    'teaser': Teaser,
    'footer': Footer,
  },
});

Initialization Options

OptionTypeRequiredDefaultDescription
originstringYesFully qualified domain with protocol
spaceIdstringYesLocaless Space ID, found in Space settings
tokenstringYesLocaless API token (keep secret — server-side only)
version'draft' | stringNo'published'Default content version
debugbooleanNofalseEnable debug logging
cacheTTLnumber | falseNo300Cache TTL in seconds. Set false to disable caching entirely
componentsRecord<string, React.ElementType>No{}Map of schema keys to React components
fallbackComponentReact.ElementTypeNoComponent rendered when a schema key has no registered component
enableSyncbooleanNofalseLoad the Visual Editor sync script for live-editing support

LocalessComponent

LocalessComponent is a dynamic renderer that maps Localess content data to your registered React components by schema key. It automatically applies Visual Editor attributes when sync is enabled.

import { LocalessComponent } from "@localess/react";

// Render a single content block
<LocalessComponent data={content.data} />

// Render a list of nested blocks
{data.body.map(item => (
  <LocalessComponent
    key={item._id}
    data={item}
    links={content.links}
    references={content.references}
  />
))}

Props

PropTypeRequiredDescription
dataContentDataYesContent data object from Localess. The component looks up data._schema in the component registry
linksLinksNoResolved content links map, forwarded to the rendered component
referencesReferencesNoResolved references map, forwarded to the rendered component
assetsAssetsNoResolved content assets map (keyed by asset ID), forwarded to the rendered component
refReact.Ref<HTMLElement>NoRef forwarded to the rendered component's root element
...restanyNoAny additional props are forwarded to the rendered component

If a schema key is not registered and no fallbackComponent is configured, LocalessComponent renders an error message in the DOM.

Marking Editable Content

Use these helpers to add Visual Editor attributes to your JSX elements. They enable element highlighting and selection in the Localess Visual Editor.

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>
);

Rich Text Rendering

renderRichTextToReact(content)

Converts a Localess ContentRichText object to a React node tree. Supports the full range of rich text formatting produced by the Localess editor.

import { renderRichTextToReact } from "@localess/react";

const Article = ({ data }) => (
  <article>
    <h1>{data.title}</h1>
    <div>{renderRichTextToReact(data.body)}</div>
  </article>
);

Supported rich text elements:

  • Document structure
  • Headings (h1–h6)
  • Paragraphs
  • Text formatting: bold, italic, strikethrough, underline
  • Ordered and unordered lists
  • Code blocks (with syntax highlighting support)
  • Links (inline)

Accessing the Client

getLocalessClient()

Returns the LocalessClient instance created during localessInit. Use this in server-side data-fetching functions.

import { getLocalessClient } from "@localess/react";

async function fetchPageData(locale?: string) {
  const client = getLocalessClient();
  return client.getContentBySlug<Page>('home', { locale });
}

Throws an error if called before localessInit has been executed.

Component Registry API

These functions allow dynamic management of the component registry after initialization.

import {
  registerComponent,
  unregisterComponent,
  setComponents,
  getComponent,
  setFallbackComponent,
  getFallbackComponent,
  isSyncEnabled,
} from "@localess/react";

// Register a new component
registerComponent('hero-block', HeroBlock);

// Unregister a component
unregisterComponent('hero-block');

// Replace the entire registry
setComponents({ 'page': Page, 'hero': Hero });

// Retrieve a component by schema key
const Component = getComponent('hero');

// Configure the fallback component
setFallbackComponent(UnknownComponent);

// Get the current fallback component
const fallback = getFallbackComponent();

// Check if Visual Editor sync is enabled
const syncEnabled = isSyncEnabled();

Assets

resolveAsset(asset, params?)

Resolves a ContentAsset object to a fully qualified URL using the initialized client's origin. Pass an AssetTransformParams object as the second argument to request a resized image or a different output format — the params are appended as query string parameters that the Localess asset endpoint uses to transform the image on the fly. See Image Transforms for the full parameter reference.

import { resolveAsset } from "@localess/react";

const Image = ({ data }) => (
  <img src={resolveAsset(data.image)} alt={data.imageAlt} />
);
import { resolveAsset } from "@localess/react";

function HeroImage({ image }: { image: ContentAsset }) {
  return (
    <img
      // Smaller, WebP thumbnail for a card
      src={resolveAsset(image, { w: 400, f: 'webp' })}
      alt={image.alt ?? ''}
    />
  );
}

function ProductImage({ image }: { image: ContentAsset }) {
  return (
    <img
      // Fixed box crop + quality control
      src={resolveAsset(image, { w: 800, h: 600, q: 70, f: 'avif' })}
      alt={image.alt ?? ''}
    />
  );
}
ParamTypeDescription
wnumberTarget width in pixels
hnumberTarget height in pixels (combined with w, crops to cover the box)
qnumberOutput quality 1–100 (default 85; ignored for PNG)
f'webp' | 'jpeg' | 'png' | 'avif'Converts the output format
downloadbooleanForces a browser download via Content-Disposition
thumbnailbooleanExtracts the first frame of an animated/video asset before resizing

useLocaless Hook

useLocaless<T> fetches content by slug in a Client Component and automatically subscribes to Visual Editor live updates when enableSync is active.

'use client';

import { useLocaless, LocalessComponent } from "@localess/react";
import type { Page } from "./.localess/localess";

export function PageView({ slug }: { slug: string }) {
  const content = useLocaless<Page>(slug, { locale: 'en' });

  if (!content) return <div>Loading…</div>;

  return (
    <main>
      {content.data.body.map(item => (
        <LocalessComponent key={item._id} data={item} links={content.links} />
      ))}
    </main>
  );
}

Parameters

ParameterTypeRequiredDescription
slugstring | string[]YesContent slug. Arrays are joined with / — e.g. ['blog', 'post']'blog/post'
optionsContentFetchParamsNoSame fetch options as getContentBySlug (locale, version, resolveReference, resolveLink)

Returns Content<T> | undefinedundefined while the initial fetch is in progress. Content<T> includes data, links, references, and assets (a map of resolved content assets keyed by asset ID).

When enableSync is active and the page is rendered inside the Localess Visual Editor iframe, the hook automatically subscribes to input / change events and updates the returned content in place.

Resolves a ContentLink field to a URL string. Use it to build href values from Localess content links.

import { findLink } from "@localess/react";

// type: 'content' → '/' + fullSlug, or '/not-found' if not in map
// type: 'url'     → raw URI unchanged
const href = findLink(content.links, data.ctaLink);

const NavLink = ({ data, links }) => (
  <a href={findLink(links, data.link)}>{data.label}</a>
);

Visual Editor Events

With useLocaless Hook

When enableSync: true is set in localessInit, the useLocaless hook handles the full cycle automatically — initial fetch and live sync updates — with no extra wiring needed.

'use client';

import { useLocaless, LocalessComponent, localessEditable } from "@localess/react";
import type { Page } from "./.localess/localess";

export function PageView({ slug, locale }: { slug: string; locale?: string }) {
  const content = useLocaless<Page>(slug, { locale });

  if (!content) return null;

  return (
    <main {...localessEditable(content.data)}>
      {content.data?.body.map(item => (
        <LocalessComponent key={item._id} data={item} links={content.links} references={content.references} />
      ))}
    </main>
  );
}

With LocalessDocument Component

LocalessDocument is a component alternative to the hook. Pass it the full server-fetched content response and it handles live sync updates internally, delegating rendering to LocalessComponent.

// app/[locale]/page.tsx (Server Component — fetches data)
import { getLocalessClient, LocalessDocument } from "@localess/react/rsc";
import type { Page } from "./.localess/localess";

export default async function HomePage({ params }: { params: Promise<{ locale?: string }> }) {
  const { locale } = await params;
  const client = getLocalessClient();
  const content = await client.getContentBySlug<Page>('home', { locale });

  return <LocalessDocument document={content} />;
}

Props:

PropTypeRequiredDescription
documentContent<T>YesFull content response object (from getContentBySlug/getContentById)
refReact.Ref<HTMLElement>NoForwarded to the rendered root element

LocalessDocument subscribes to input / change editor events automatically when enableSync is active.

Import it from @localess/react/rsc (as above) when calling it directly from a Server Component, as this example does — that variant renders the component registry lookup itself in the Server Component module graph, where localessInit()'s registration is visible. The plain @localess/react export's LocalessDocument is a Client Component internally; calling it directly from a Server Component moves that lookup into a separate client module graph where the registration set by a Server Component's localessInit() call was never applied, and it fails to find any registered component. Use the plain @localess/react export's LocalessDocument only from inside an actual 'use client' file (see the SPA example below).

See the playgrounds/next playground for a full working Next.js App Router + RSC project built on this pattern.

Manual Integration

If you manage content state yourself without useLocaless or LocalessDocument, use localessSyncOn / localessSyncOnChange. They wrap the isSyncEnabled() check and the sync-ready wait internally, so you don't need to guard for browser/iframe context or race the sync script load:

'use client';

import { useEffect, useState } from "react";
import { LocalessComponent, localessEditable, localessSyncOnChange } from "@localess/react";
import type { Content, Page } from "./.localess/localess";

export function PageClient({ initialContent }: { initialContent: Content<Page> }) {
  const [pageData, setPageData] = useState(initialContent.data);

  useEffect(() => {
    // No-op automatically if sync isn't enabled/usable — no manual guards needed.
    localessSyncOnChange((event) => setPageData(event.data));
    // No cleanup needed: window.localess has no .off() method
  }, []);

  return (
    <main {...localessEditable(pageData)}>
      {pageData?.body.map(item => (
        <LocalessComponent key={item._id} data={item} links={initialContent.links} references={initialContent.references} />
      ))}
    </main>
  );
}

localessSyncOnChange(callback) is shorthand for localessSyncOn(['input', 'change'], callback). Use localessSyncOn directly to subscribe to other event types:

import { localessSyncOn } from "@localess/react";

localessSyncOn(['save', 'publish'], (event) => console.info(`Content ${event.type}d`));

Available events:

EventWhen
inputUser is typing in a field (real-time preview)
changeField value confirmed
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

window.localess only exposes .on() and .onChange() — there is no .off() method. Prefer localessSyncOn/localessSyncOnChange over calling window.localess directly — they handle the enabled/ready checks for you.

Full Example — SPA / Default (@localess/react)

For SPAs or fully client-rendered React apps. All imports use the default @localess/react export.

Setup — app/layout.tsx

// Server Component — safe to use API token here
import { localessInit } from "@localess/react";
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>;
}

Server Component — app/[locale]/page.tsx

import { getLocalessClient } from "@localess/react";
import type { Content, Page } from "./.localess/localess";
import { PageClientHook } from "./page-client-hook";

export default async function HomePage({
  params,
}: {
  params: Promise<{ locale?: string }>;
}) {
  const { locale } = await params;
  const content = await getLocalessClient().getContentBySlug<Page>('home', { locale });

  return <PageClientHook initialContent={content} locale={locale} />;
}

Client Component — Option A: useLocaless Hook

// app/[locale]/page-client-hook.tsx
'use client';

import { useLocaless, LocalessComponent, localessEditable } from "@localess/react";
import type { Content, Page } from "./.localess/localess";

export function PageClientHook({
  initialContent,
  locale,
}: {
  initialContent: Content<Page>;
  locale?: string;
}) {
  const content = useLocaless<Page>('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>
  );
}

Client Component — Option B: LocalessDocument

This specific pattern — calling LocalessDocument directly from a Server Component with no separate 'use client' file — needs the @localess/react/rsc import, not the plain @localess/react export used elsewhere in this SPA example. See With LocalessDocument Component above for why.

// app/[locale]/page.tsx (Server Component — no separate client file needed)
import { getLocalessClient, LocalessDocument } from "@localess/react/rsc";
import type { Page } from "./.localess/localess";

export default async function HomePage({
  params,
}: {
  params: Promise<{ locale?: string }>;
}) {
  const { locale } = await params;
  const content = await getLocalessClient().getContentBySlug<Page>('home', { locale });

  // LocalessDocument handles sync internally — no 'use client' wrapper needed here
  return <LocalessDocument document={content} />;
}

Client Component — Option C: Manual

// app/[locale]/page-client-manual.tsx
'use client';

import { useEffect, useState } from "react";
import { LocalessComponent, localessEditable, isSyncEnabled, isBrowser } from "@localess/react";
import type { Content, Page } from "./.localess/localess";

export function PageClientManual({
  initialContent,
}: {
  initialContent: Content<Page>;
}) {
  const [pageData, setPageData] = useState(initialContent.data);

  useEffect(() => {
    if (isSyncEnabled() && isBrowser() && window.localess) {
      window.localess.on(['input', 'change'], (event) => {
        if (event.type === 'input' || event.type === 'change') {
          setPageData(event.data);
        }
      });
    }
  }, []);

  return (
    <main {...localessEditable(pageData)}>
      {pageData?.body.map(item => (
        <LocalessComponent key={item._id} data={item} links={initialContent.links} references={initialContent.references} />
      ))}
    </main>
  );
}

Full Example — Next.js Static Export (@localess/react/ssr)

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-static mirrors the RSC playground but targets output: 'export' and uses @localess/react/ssr end to end.

Full Example — Next.js App Router with RSC (@localess/react/rsc)

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/next is a full working Next.js App Router project built on Option A — localessInit() in page.tsx and <LocalessDocument document={document} /> rendered directly from the Server Component.

Export Reference

The table below shows which symbols are available in each export.

Symbol@localess/react@localess/react/ssr@localess/react/rsc
localessInitYesYesYes
getLocalessClientYesYesYes
registerComponent / setComponents / getComponentYesYesYes
setFallbackComponent / getFallbackComponentYesYesYes
resolveAssetYesYesYes
LocalessComponentYesNoYes
LocalessServerComponent / LocalessServerDocumentNoYesYes
renderRichTextToReactYesYesYes
findLinkYesYesYes
isServerYesYesYes
All content typesYesYesYes
LocalessDocumentYesNoYes
useLocalessYesNoYes
localessEditable / localessEditableFieldYesYesYes
isBrowser / isIframeYesYesYes
isSyncEnabled / localessSyncOn / localessSyncOnChange / localessSyncReadyYesNoYes
Sync event types (LocalessSync, EventToApp, EventToAppOf, …)YesYesYes

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

License

MIT

On this page