Rendu
La route catch-all, le composant de page, les métadonnées, le layout, le routage localisé et la revalidation ISR.
La route catch-all
Un workspace cmssy stocke le contenu ; votre app le rend. Une route catch-all optionnelle - app/[[...path]]/page.tsx - associe chaque slug publié (et son préfixe de langue) à une page. createCmssyPage récupère la page pour le chemin courant, associe chaque bloc à votre composant enregistré et le rend côté serveur.
// app/[[...path]]/page.tsx
import { buildCmssyMetadata, createCmssyPage } from "@cmssy/next/server";
import { cmssy } from "@/cmssy/config";
import { blocks } from "@/cmssy/blocks";
export const revalidate = 3600;
export const dynamicParams = true;
const renderPage = createCmssyPage(cmssy, blocks);
type PageProps = { params: Promise<{ path?: string[] }> };
export async function generateMetadata({ params }: PageProps) {
return buildCmssyMetadata(cmssy, (await params).path);
}
export default function Page({ params }: PageProps) {
return renderPage({ params });
}
La route catch-all est rendue statiquement : elle ne lit jamais
searchParamsniheaders(), donc l'ISR reste actif. L'aperçu des brouillons fonctionne toujours par requête viadraftMode().
Composant de page & configuration
createCmssyPage(config, blocks, options?) renvoie un composant serveur asynchrone. config provient de defineCmssyConfig ; blocks est le tableau des définitions defineBlock que le SDK associe par type. Passez { editor } pour monter l'éditeur visuel sur la route d'aperçu, ou { path } pour fixer une route mono-page.
// cmssy/config.ts
import { defineCmssyConfig } from "@cmssy/next";
export const cmssy = defineCmssyConfig({
apiUrl: process.env.CMSSY_API_URL,
org: process.env.CMSSY_ORG_SLUG,
workspaceSlug: process.env.CMSSY_WORKSPACE_SLUG,
draftSecret: process.env.CMSSY_DRAFT_SECRET,
});
Métadonnées & SEO
buildCmssyMetadata(config, path, options?) renvoie un objet Metadata Next.js complet à partir des champs SEO de la page et du branding du workspace : titre, description, mots-clés, canonique ainsi que les alternatives hreflang par langue, et les cartes Open Graph / Twitter. Appelez-le dans generateMetadata (voir la route ci-dessus).
Passez le chemin catch-all tel que routé - préfixe de langue compris. Le préfixe indique au SDK quelle langue rendre ; le retirer d'abord fait hériter chaque traduction du titre de la langue par défaut et d'une canonique pointant vers l'URL par défaut, ce qui indique aux moteurs de recherche que la traduction est un doublon.
buildCmssyMetadata(cmssy, path, {
image: "https://assets.cmssy.io/og.png",
ogType: "article",
});
Header & footer
Les régions partagées - header, footer - sont des blocs de layout. Rendez-les dans app/[[...path]]/layout.tsx : fetchLayouts récupère les groupes de layout, CmssyServerLayout rend une position, et CmssyLocaleProvider expose la langue active aux blocs client.
// app/[[...path]]/layout.tsx
import { fetchLayouts, resolveSiteLocales, CmssyServerLayout } from "@cmssy/react";
import { splitCmssyLocale } from "@cmssy/core";
import { CmssyLocaleProvider } from "@cmssy/next/client";
import { blocks } from "@/cmssy/blocks";
import { cmssy } from "@/cmssy/config";
export default async function Layout({
children,
params,
}: {
children: React.ReactNode;
params: Promise<{ path?: string[] }>;
}) {
const { path } = await params;
const [{ locale }, siteLocales, groups] = await Promise.all([
splitCmssyLocale(cmssy, path),
resolveSiteLocales(cmssy),
fetchLayouts(cmssy, "/"),
]);
const slot = (position: "header" | "footer") => (
<CmssyServerLayout
groups={groups}
blocks={blocks}
position={position}
locale={locale}
defaultLocale={siteLocales.defaultLocale}
enabledLocales={siteLocales.locales}
/>
);
return (
<html lang={locale}>
<body>
<CmssyLocaleProvider
value={{
current: locale,
default: siteLocales.defaultLocale,
enabled: siteLocales.locales,
}}
>
{slot("header")}
{children}
{slot("footer")}
</CmssyLocaleProvider>
</body>
</html>
);
}
Routage localisé
cmssy encode la langue en préfixe de chemin : /about est la langue par défaut, /pl/about est le polonais. La même route catch-all gère les deux. splitCmssyLocale(config, path) lit les langues activées du workspace et sépare le préfixe du reste - sans requête ni en-têtes, donc compatible statique.
const { locale, path: rest } = await splitCmssyLocale(cmssy, path);
// ["pl", "about"] -> locale "pl", rest ["about"]
// ["about"] -> locale "en" (default), rest ["about"]
ISR & revalidation à la demande
Les pages publiées sont mises en cache et régénérées par l'ISR via export const revalidate. Pour rafraîchir instantanément à la publication, ajoutez une route à la demande et pointez-y le webhook content.changed du workspace. cmssy envoie en POST le path modifié ; la route le revalide, ou tout le site (portée layout) si aucun chemin n'est envoyé. Protégez-la avec un secret partagé.
// app/api/revalidate/route.ts
import { revalidatePath } from "next/cache";
import { NextResponse, type NextRequest } from "next/server";
export async function POST(request: NextRequest) {
const secret = process.env.CMSSY_REVALIDATE_SECRET;
const provided =
request.nextUrl.searchParams.get("secret") ??
request.headers.get("x-revalidate-secret");
if (!secret || provided !== secret) {
return NextResponse.json({ revalidated: false }, { status: 401 });
}
const { path } = (await request.json().catch(() => ({}))) as { path?: string };
if (path) revalidatePath(path);
else revalidatePath("/", "layout");
return NextResponse.json({ revalidated: true, path: path ?? "/" });
}
Prochaines étapes
- Fonctionnalités avancées des blocs - loaders serveur et récupération de données
- Formulaires & générateur de formulaires
- Thème
- Authentification des membres