Layouty
Header i footer to bloki layoutu - dziedziczone w dół drzewa stron i edytowalne jak każdy inny blok.
Header to nie komponent, który wklepujesz na sztywno w app/layout.tsx. W cmssy to blok layoutu: to samo co hero albo tabela cennika - przechowywany w CMS-ie, edytowany w edytorze stron i renderowany przez komponent z Twojego rejestru.
To decyzja projektowa z konsekwencją. Gdyby header był markupem, redaktor nie zmieniłby linku w nawigacji bez deployu. Ponieważ jest blokiem - może.
Pozycje i dziedziczenie
Bloki layoutu żyją na nazwanych pozycjach - header i footer. Strona albo posiada własny layout, albo dziedziczy go po rodzicu, więc ustawienie headera raz na stronie głównej daje go całemu drzewu.
Nadpisuj tylko tam, gdzie sekcja naprawdę się różni: landing bez nawigacji, checkout z okrojonym footerem. Reszta dziedziczy, co oznacza, że jedna edycja aktualizuje każdą stronę, która się nie wypisała.
Dwa root layouty, nie jeden
Route publiczny i route edycyjny mają własne root layouty. Pobierają te same grupy layoutu i renderują je różnymi komponentami, bo header wyrenderowany na serwerze nie da się edytować, a zamontowany na kliencie nie da się zestatycznieć.
Grupy pobierasz sam - jedno zapytanie do API dostawczego, zwracające bloki layoutu dla strony:
// services/layout.ts
export async function fetchChromeLayouts(
pageSlug: string,
previewSecret?: string,
): Promise<CmssyLayoutGroup[]> { /* zapytanie PublicPageLayouts */ }Layout publiczny
CmssyServerLayout z @cmssy/react renderuje grupy po stronie serwera. Potrzebuje rejestru bloków, bo rozwiązuje typy na serwerze:
// 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}
/>
);Draft secret przekazywany jest tylko gdy tryb roboczy jest włączony. Przekaż go bezwarunkowo, a Twój publiczny serwis zacznie serwować nieopublikowane zmiany headera.
Layout edycyjny
Route edycyjny renderuje te same grupy przez Twój wrapper kliencki:
// 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 }}
/>
);Zwróć uwagę, czego brakuje: nie ma propa blocks. Rejestr ładuje leniwie na kliencie sam wrapper:
// 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")} />;
}Leniwe ładowanie nie jest tu optymalizacją, tylko granicą. Moduły bloków mogą zawierać loadery serwerowe czytające konfigurację i zależności wyłącznie serwerowe; łapczywy import rejestru z komponentu klienckiego wciągnąłby to wszystko do bundla przeglądarki.
Oba root layouty muszą ustawiać <html lang> z rozwiązanego locale - w tym ten edycyjny, bo podgląd musi deklarować język, w którym faktycznie się renderuje.
Dlaczego to psuje się po cichu
Pomiń editable, a wszystko dalej wygląda poprawnie. Serwis się buduje, odwiedzający widzą właściwy header, testy przechodzą.
Ale w edytorze header jest teraz markupem wyrenderowanym na serwerze. Da się go zaznaczyć i nie ma żadnych pól - edytor podkreśli go i nie zmieni niczego. Bez błędu, bez ostrzeżenia, bez czerwonego builda.
Dokładnie to sprawdza checkCmssyEditMode i dlatego jego warunek sukcesu czyta się na opak: brak <header> w HTML-u z serwera dla żądania w trybie edycji to stan poprawny. Zobacz testowanie.
Następne kroki
- Route'y i strony - route'y, w których siedzą te sloty.
- Testowanie - smoke test wyłapujący martwy layout.
- Edytor stron - edycja bloków layoutu w praktyce.