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 >= 20.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, LocalessComponent } from "@localess/react/ssr";

// RSC — server components + client components for live editing
import { localessInit, LocalessComponent } 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.

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 | falseNo300000Cache TTL in milliseconds. Set false to disable
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
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>
);

Deprecated: llEditable() and llEditableField() are deprecated aliases. Use localessEditable() and localessEditableField() instead.

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)

Resolves a ContentAsset object to a fully qualified URL using the initialized client's origin.

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

const Image = ({ data }) => (
  <img src={resolveAsset(data.image)} alt={data.imageAlt} />
);

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.

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 server-fetched content data 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";
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
      data={content.data}
      links={content.links}
      references={content.references}
    />
  );
}

Props:

PropTypeRequiredDescription
dataContentDataYesInitial content data (typically server-fetched)
linksLinksNoResolved links map, forwarded to the inner LocalessComponent
referencesReferencesNoResolved references map, forwarded to the inner LocalessComponent
refReact.Ref<HTMLElement>NoForwarded to the rendered root element
...restanyNoAny additional props are forwarded

LocalessDocument subscribes to input / change editor events automatically when enableSync is active. It is a Client Component internally — no 'use client' directive needed at the call site in Server Components.

Manual Integration

If you manage content state yourself without useLocaless or LocalessDocument, subscribe to editor events directly via window.localess:

'use client';

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

export function PageClient({ 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);
        }
      });
    }
    // 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>
  );
}

Available events via window.localess.on():

EventWhen
inputUser is typing in a field (real-time preview)
changeField value confirmed
saveContent saved
publishContent published
pongEditor heartbeat response
enterSchemaEditor cursor enters a schema block
hoverSchemaEditor cursor hovers over a schema block

window.localess only exposes .on() and .onChange() — there is no .off() method.

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

// app/[locale]/page.tsx (Server Component — no separate client file needed)
import { getLocalessClient, LocalessDocument } from "@localess/react";
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 });

  return (
    <LocalessDocument
      data={content.data}
      links={content.links}
      references={content.references}
    />
  );
}

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 { LocalessComponent } 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>
      <LocalessComponent data={content.data} links={content.links} references={content.references} />
    </main>
  );
}

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

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

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} />;
}

Client Component — app/[locale]/page-client.tsx

Option A — LocalessDocument (recommended):

'use client';
import { LocalessDocument } from "@localess/react/rsc";

export default function PageClient({ initialContent }) {
  return (
    <LocalessDocument
      data={initialContent.data}
      links={initialContent.links}
      references={initialContent.references}
    />
  );
}

Option B — useLocaless hook:

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

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
LocalessComponentYesYesYes
renderRichTextToReactYesYesYes
findLinkYesYesYes
isServerYesYesYes
All content typesYesYesYes
LocalessDocumentYesNoYes
useLocalessYesNoYes
localessEditable / localessEditableFieldYesNoYes
isBrowser / isIframeYesNoYes
isSyncEnabledYesNoYes
Sync event types (LocalessSync, EventToApp, …)YesNoYes

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