---
title: "Routing helpers"
description: "Locale-aware links, preview and live preview helpers."
requested_language: fr
language: en
translation_notice: "This page isn't translated yet"
url: https://docs.systhema.app/fr/nextjs/locales/routing-helpers
version: unreleased (main)
docs_index: https://docs.systhema.app/fr/llms.txt
---
> This page isn't translated yet. Showing English.


The gateway routes custom Admin hosts and locales, and `@systhemaui/next/locale` provides the routing and navigation helpers your own code uses. This page also covers how Preview and Live Preview keep their locale.

## Custom Admin host matching

Custom admin domain matching uses `Host`, falling back to `request.nextUrl.hostname` when the header is empty or invalid. It ignores `x-forwarded-host` by default. This restores public-host routing when Next.js constructs its request URL from the internal bind address. Matching ignores letter case and ports on both sides, including `localhost` on any port and bracketed IPv6 such as `[::1]:3000`. With `customAdminURL` configured, `/admin` on other hosts still rewrites to `/not-found`; without it, `/admin` remains available on the site host. Host routing selects a virtual host; Payload authentication still controls access to the CMS.

### `trustForwardedHost`

Enable `trustForwardedHost: true` only if the proxy replaces `Host` with an internal upstream host and supplies the public host in `x-forwarded-host`:

```ts title="src/proxy.ts"
export const proxy = systhemaGateway({
  customAdminURL: process.env.ADMIN_URL,
  trustForwardedHost: true,
})
```

With this option, the first comma-separated forwarded value takes precedence over `Host`. Each candidate is trimmed and parsed as a hostname; an empty or invalid first forwarded value falls back to `Host`, then the request URL. The proxy must overwrite client-supplied `x-forwarded-host`, including any client-supplied list, and the origin must only accept traffic through that proxy. Enabling the option on a directly reachable server lets clients select admin versus public routing. This option applies to custom-admin routing only; the locale middleware retains next-intl's header policy.

Choose the option from the proxy's actual header configuration:

- Direct `next start`, localhost, and proxies preserving the public `Host` need no opt-in. This includes a standard Railway deployment. [Vercel documents `x-forwarded-host` as identical to `Host`](https://vercel.com/docs/headers/request-headers#x-forwarded-host), so that setup needs no opt-in either.
- [nginx defaults to the upstream `Host`](https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_set_header). Prefer preserving the public host with `proxy_set_header Host $host;`. If the upstream requires its internal host, set `proxy_set_header X-Forwarded-Host $host;` and enable the option.
- [Caddy overwrites forwarded headers by default](https://caddyserver.com/docs/caddyfile/directives/reverse_proxy#defaults). When Caddy preserves `Host`, leave the option off. An HTTPS upstream configuration that replaces `Host` needs the option if the public host is only available in `x-forwarded-host`.
- For Cloudflare Tunnel or a custom proxy chain, inspect the headers reaching Node. The platform name alone does not establish whether an opt-in is needed or safe.

## Routing and navigation helpers

Use `createSysthemaRouting()` and `createSysthemaNavigation()` from `@systhemaui/next/locale` for routing and navigation helpers, and the advanced `createSysthemaLocaleProxy()` middleware factory from `@systhemaui/next/locale/proxy` if you need to compose the next-intl middleware yourself. `src/proxy.ts` calls `systhemaGateway({ customAdminURL })` from `@systhemaui/next/gateway` — no `locales`/`localeMiddleware` argument; the gateway resolves them itself from the `withSysthema`-injected env (falling back to a locale-off no-op when it's absent, and still accepting an explicit `options.locales`/`options.localeMiddleware` override for tests or advanced setups). It composes custom-admin routing before locale negotiation and preserves Preview and Live Preview handling.

## Preview and Live Preview

The explicit preview locale of Preview and Live Preview overrides a stale `NEXT_LOCALE` cookie, is validated against `locales.supported`, and keeps all stable-document and project query parameters through next-intl redirects. With prefix routing, a relative Live Preview can remain on a configured custom Admin origin. With domain routing, the explicit Preview locale selects that locale's canonical origin and preserves the Preview query. If the Admin and selected frontend are on different registrable domains, use Payload client Live Preview unless authenticated draft-cookie scope has been deliberately solved; never weaken Preview secret or user authentication. See [Cross-domain preview](https://docs.systhema.app/fr/payload/frontend/live-preview/cross-domain.md).

## Routing modes

The Next-only starter sets `adminRouting: false`, so ordinary routes such as `/admin` are not reserved when Payload is absent. `always` is the multi-locale default and prefixes every locale; `as-needed` keeps the default locale unprefixed. A domain map instead gives every locale an unprefixed path on its own canonical origin. An exact-one-locale contract stays unprefixed with detection disabled. The non-unique `never` mode is not part of Systhema's path-routing contract; Systhema uses it only in the generated one-locale-per-domain projection where each origin is already unique.

## The Payload locale boundary

For Payload sites, the managed boundary layout at `app/(site)/(systhema)/[[...segments]]/layout.tsx` is the real locale boundary — no literal `[locale]` folder is needed. A managed `not-found.tsx` sits beside it, re-exporting the consumer's `app/(site)/not-found.tsx`: a thrown `notFound()` renders the **nearest** not-found boundary, and only one below the boundary layout renders inside the site shell — with `RootLayout`'s CSS, event listeners, and the requested locale's strings and `dir` (keep customizing the 404 in `app/(site)/not-found.tsx`; the managed file just repositions it). The layout is Systhema-owned (do-not-modify, regenerated by sync) and contains the only locale-aware line: `const route = await resolveSysthemaLocaleRoute((await params).segments)` (from `@systhemaui/payload/next`; a no-op returning the default locale when `locales` is off), injected as `locale` into the consumer-owned `app/(site)/shell.tsx`, which passes it to `RootLayout`, `RootHeader`, and `RootFooter`. Those components derive everything else from config internally — `RootLayout` sets both `<html lang>` and `dir` (via `getSysthemaLocaleDirection`) and resolves the frontend message catalog from `systhema.config.ts` (`messages.catalogs` + `messages.fallback`) whenever the `frontendMessages` prop is absent, and renders `SysthemaProvider` so client children can read strings via `useSysthemaFrontendTranslator`. The catch-all `page.tsx` itself stays the plain, locale-unaware baseline — `SysthemaPage`, `generateSysthemaStaticParams`, and `generateSysthemaMetadata` resolve locale internally: with locales on, static params fan out across every supported locale so locale-specific slugs remain statically renderable and revalidate with the existing ISR setting, and metadata resolves/strips the locale segment before lookup. The shell is converted once and then fully project-owned: sync preserves your customizations (while freely regenerating everything inside `(systhema)`), and disabling locales refuses to discard a customized shell until you move those changes back to `template.tsx` yourself.

## Related

- [Gateway and status codes](https://docs.systhema.app/fr/nextjs/gateway.md)
- [Generated files and migration](https://docs.systhema.app/fr/nextjs/locales/generated-files.md)
