---
title: "Rendering pages"
description: "SysthemaPage, RootLayout, the catch-all route and convertLexicalNodesToJSX."
requested_language: ar
language: en
translation_notice: "This page isn't translated yet"
url: https://docs.systhema.app/ar/payload/frontend/rendering
version: unreleased (main)
docs_index: https://docs.systhema.app/ar/llms.txt
---
> This page isn't translated yet. Showing English.


Systhema's frontend rendering converts serialised Lexical content into Systhema React components.

## Rendering Lexical content

```tsx
// 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 routes

For full app routing — including `/sys/[route]` server routes, sitemaps, and the catch-all `[...path]` route — use the file generator:

```bash
pnpm systhema-core payload create-app-files
```

This creates the Next.js App Router scaffolding Systhema's plugin needs.

## Prerendering

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](https://docs.systhema.app/ar/guides/deploying.md#building-without-database-access).

## Accessibility defaults

Systhema's built-in templates and converters render with accessible defaults:

- **Page shell** — `<RootLayout>` sets `<html lang>` (from the active locale, overridable via `htmlAttributes`) 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 the `text-h2` size because it sits beside its media rather than spanning the page; the element is still the page's `h1`.
- **Decorative images** — a block image with no authored `alt` renders `alt=""` (decorative) rather than borrowing the block's internal name.
- **Rich-text upload images** take their `alt` from the media document's authored `alt` field, 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 as `aria-label` when the icon is a link; if left empty, a name is derived from the link's domain (e.g. a `facebook.com` link → "Facebook").
- **Google Maps embeds carry a `title`.** The `googleMaps` block and map heroes render an accessible titled iframe (via `GoogleMapEmbed`) rather than the title-less `@next/third-parties` embed.
- **Form block** — submission and captcha errors use `role="alert"`, the success confirmation is a focusable `role="status"` region, and email / country / state fields set the matching `autocomplete` value.

## Frontend locales

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](https://docs.systhema.app/ar/nextjs/locales/url-recipes.md).
