Rendering pages
SysthemaPage, RootLayout, the catch-all route and convertLexicalNodesToJSX.
On this page
Systhema's frontend rendering converts serialised Lexical content into Systhema React components.
Rendering Lexical contentLink to this section
// Inside a page component or app router route.
import { Lexical } from '@systhemaui/payload/next'
const { content } = page.layout
// `Lexical` (the RichText renderer) maps the serialised Lexical doc → JSX,
// resolving Systhema components + custom blocks.
return <Lexical data={content} />Each block converter (built-in or custom) receives the serialised node and returns JSX. Custom blocks plug into this pipeline via customBlocks in plugin options.
App routesLink to this section
For full app routing — including /sys/[route] server routes, sitemaps, and the catch-all [...path] route — use the file generator:
pnpm systhema-core payload create-app-filesThis creates the Next.js App Router scaffolding Systhema's plugin needs.
PrerenderingLink to this section
The catch-all's generateStaticParams calls generateSysthemaStaticParams({ payloadInstance }), which lists every published page and post for next build to prerender. payloadInstance takes a getter, () => getPayload({ config }), or a getPayload() promise. With SYSTHEMA_PRERENDER=off it returns [] without calling the getter, so the build needs no database. See Building without database access.
Accessibility defaultsLink to this section
Systhema's built-in templates and converters render with accessible defaults:
- Page shell —
<RootLayout>sets<html lang>(from the active locale, overridable viahtmlAttributes) and renders a "Skip to content" link as the first focusable element, targeting the<main id="main-content">that every built-in page template renders. - Hero titles render as a real
<h1>on every built-in template (page, post, archive) and every hero type — not styled<div>s. The feature hero keeps thetext-h2size because it sits beside its media rather than spanning the page; the element is still the page'sh1. - Decorative images — a block image with no authored
altrendersalt=""(decorative) rather than borrowing the block's internal name. - Rich-text upload images take their
altfrom the media document's authoredaltfield, falling back to the filename. - Links that open a new tab append a visually hidden "(opens in new tab)" hint for screen readers.
- Icon-only inline blocks keep an accessible name. The button and chip blocks have a Hide title visually checkbox (Advanced Settings) that keeps the title in the DOM for screen readers while hiding it visually (
sr-only) — so an icon-only button/chip still has an accessible name. The icon block (which never shows text) has an Accessible label field, applied asaria-labelwhen the icon is a link; if left empty, a name is derived from the link's domain (e.g. afacebook.comlink → "Facebook"). - Google Maps embeds carry a
title. ThegoogleMapsblock and map heroes render an accessible titled iframe (viaGoogleMapEmbed) rather than the title-less@next/third-partiesembed. - Form block — submission and captcha errors use
role="alert", the success confirmation is a focusablerole="status"region, and email / country / state fields set the matchingautocompletevalue.
Frontend localesLink to this section
When frontend locales are enabled, SysthemaPage resolves locale and frontendMessages internally (no locale-aware wrapper is generated — the catch-all page.tsx is the plain, byte-identical baseline whether locales are on or off) and threads them through RootLayout, RootHeader, RootFooter, the built-in page/post/archive templates, custom template props, live-preview client renderers, Lexical converters, forms, post controls, embeds, and the frontend admin bar. The owned site shell only has to pass locale down to those three components — RootLayout derives <html lang> and dir and the resolved message catalog from systhema.config.ts (messages.catalogs + messages.fallback) internally whenever the frontendMessages prop is absent, and it renders SysthemaProvider, which supplies the client-side message context that useSysthemaFrontendTranslator() reads from. Consumer templates can use createSysthemaFrontendTranslator() from @systhemaui/next/messages on the server or useSysthemaFrontendTranslator() from @systhemaui/next/messages/client in a client child. Payload sites keep app/(site)/(systhema)/[[...segments]]; enabling locales never requires a literal [locale] directory — the only file that relocates is the owned site shell, from app/(site)/template.tsx to app/(site)/(systhema)/[[...segments]]/layout.tsx. @systhemaui/payload/next also re-exports getFrontendTranslator(), frontendMessage(), and their Payload-facing types for custom server templates.
The Payload starter also forwards the same canonical systhema.config.ts locales value into SysthemaPayloadPluginOptions. New starters keep this bridge in src/payload.config.ts even when locales are omitted; the value is then undefined, so Payload retains its existing single-language schema and no locale runtime is loaded. Existing starters receive the bridge during the confirmed one-time sync migration — which, along with wrapping next.config.ts in withSysthema(nextConfig) and relocating the site shell, is the entire migration; it generates no other files. A separate locales object in payload.config.ts is rejected because it could make frontend routes and Payload content localization disagree.
RootHeader and RootFooter also resolve their logo link from that locale: the logo points at the locale's own home path (/hu under prefix routing, that locale's domain root under domain routing) instead of a bare /, which would drop the visitor back into the default locale. Pass an explicit logoHref to override it — that always wins, and locale-off projects still get /.
Payload Admin translations remain an independent option. Enabling frontend and content locales does not add Admin languages, while enabling Admin translations does not add public routes or localized fields. For example, locales: { supported: ['en', 'fr', 'nl'], ... } can publish three localized sites while translations: { supported: ['en'] } keeps Payload Admin English-only. Conversely, a Hungarian-only website can offer both English and Hungarian Admin UI. Locale domains are also frontend-only: see the separate-domain, subdomain, and localhost recipes.