Désormais avec création de pages par IA via le serveur MCP

Layouts

En-tête et pied de page sont des blocs de layout - hérités dans l'arbre des pages et éditables comme n'importe quel bloc.

Un en-tête n'est pas un composant que vous écrivez en dur dans app/layout.tsx. Dans cmssy c'est un bloc de layout : la même chose qu'un hero ou une grille tarifaire - stocké dans le CMS, édité dans le constructeur de pages, et rendu par un composant de votre registre.

C'est un choix de conception avec une conséquence. Si l'en-tête était du balisage, un éditeur ne pourrait pas changer un lien de navigation sans déploiement. Comme c'est un bloc, il le peut.

Positions et héritage

Les blocs de layout occupent des positions nommées - header et footer. Une page possède son layout ou l'hérite de son parent : définir l'en-tête une fois sur la page racine le donne à tout l'arbre.

Ne le redéfinissez que là où une section diffère vraiment : une landing sans navigation, un tunnel de commande au pied allégé. Tout le reste hérite, ce qui veut dire qu'une seule modification met à jour toutes les pages qui ne s'en sont pas exclues.

Deux layouts racine, pas un

La route publique et la route d'édition ont chacune leur propre layout racine. Elles récupèrent les mêmes groupes de layout et les affichent via des composants différents : un en-tête rendu par le serveur ne peut pas être édité, et un en-tête monté côté client ne peut pas être statique.

Vous récupérez les groupes vous-même - une requête de diffusion renvoyant les blocs de layout d'une page :

// services/layout.ts
export async function fetchChromeLayouts(
  pageSlug: string,
  previewSecret?: string,
): Promise<CmssyLayoutGroup[]> { /* requête PublicPageLayouts */ }

Le layout public

CmssyServerLayout de @cmssy/react affiche les groupes côté serveur. Il lui faut le registre de blocs, car il résout les types sur le serveur :

// app/[[...path]]/layout.tsx
import { CmssyServerLayout } from "@cmssy/react";

const { isEnabled: draft } = await draftMode();
const [locales, groups] = await Promise.all([
  resolveSiteLocales(),
  fetchChromeLayouts("/", draft ? cmssy.draftSecret : undefined),
]);

const slot = (position: "header" | "footer") => (
  <CmssyServerLayout
    groups={groups}
    blocks={blocks}
    position={position}
    locale={locale}
    defaultLocale={locales.defaultLocale}
    enabledLocales={locales.locales}
  />
);

Le secret de brouillon n'est transmis que si le mode brouillon est actif. Transmettez-le sans condition et votre site public se met à servir des modifications d'en-tête non publiées.

Le layout d'édition

La route d'édition affiche les mêmes groupes via votre wrapper client :

// app/cmssy-edit/[[...path]]/layout.tsx
const slot = (position: "header" | "footer") => (
  <EditableLayout
    groups={groups}
    position={position}
    locale={locale}
    defaultLocale={locales.defaultLocale}
    enabledLocales={locales.locales}
    edit={{ editorOrigin }}
  />
);

Notez ce qui manque : pas de prop blocks. Le registre est chargé paresseusement côté client par le wrapper lui-même :

// cmssy/editable-layout.tsx
"use client";
import { CmssyLazyLayout, type CmssyLazyLayoutProps } from "@cmssy/react/client";

export function EditableLayout(props: Omit<CmssyLazyLayoutProps, "load">) {
  return <CmssyLazyLayout {...props} load={() => import("./blocks")} />;
}

Le chargement paresseux n'est pas une optimisation ici, c'est une frontière. Les modules de blocs peuvent contenir des loaders serveur qui lisent la configuration et des dépendances purement serveur ; importer le registre de façon énergique depuis un composant client traînerait tout cela dans le bundle navigateur.

Les deux layouts racine doivent définir <html lang> à partir de la locale résolue - y compris celui d'édition, car l'aperçu doit déclarer la langue qu'il affiche réellement.

Pourquoi c'est la partie qui casse en silence

Omettez editable et tout continue de paraître correct. Le site se construit, les visiteurs voient le bon en-tête, vos tests passent.

Mais dans l'éditeur, l'en-tête est désormais du balisage rendu par le serveur. Il est sélectionnable et n'a aucun champ : l'éditeur peut le surligner et ne rien changer. Aucune erreur, aucun avertissement, aucun build rouge.

C'est exactement ce que vérifie checkCmssyEditMode, et pourquoi sa condition de réussite se lit à l'envers : l'absence de <header> dans le HTML serveur d'une requête en mode édition est l'état correct. Voir tests.

Étapes suivantes