Localess
Next.js

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 modeExportLive editing
App Router with React Server Components@localess/react/rscYes, driven by a Server Action
Static export (output: 'export')@localess/react/ssrYes, 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 wherever localessInit() 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@latest

Setup

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 _schema exactly. 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

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

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/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.

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

Playground: playgrounds/next (App Router + RSC) and playgrounds/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

License

MIT

On this page