Ahora con creación de páginas por IA vía el servidor MCP
Renderizado

Renderiza contenido de cmssy en tu app

Una sola ruta catch-all convierte cada página publicada - bloques, layout, SEO e idioma - en HTML renderizado en el servidor en tu propio frontend Next.js.

Renderizado

La ruta catch-all, el componente de página, los metadatos, el layout, el enrutamiento localizado y la revalidación ISR.

Last updated: 24 de julio de 2026

La ruta catch-all

Un workspace de cmssy almacena el contenido; tu app lo renderiza. Una ruta catch-all opcional - app/[[...path]]/page.tsx - asigna cada slug publicado (y su prefijo de idioma) a una página. createCmssyPage obtiene la página para la ruta actual, asocia cada bloque a tu componente registrado y lo renderiza en el servidor.

// 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 ruta catch-all es renderizable estáticamente: nunca lee searchParams ni headers(), así que el ISR sigue activo. La vista previa de borradores sigue funcionando por petición mediante draftMode().


Componente de página y configuración

createCmssyPage(config, blocks, options?) devuelve un componente de servidor asíncrono. config viene de defineCmssyConfig; blocks es el array de definiciones defineBlock que el SDK asocia por type. Pasa { editor } para montar el editor visual en la ruta de vista previa, o { path } para fijar una ruta de una sola página.

// 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,
});

Metadatos y SEO

buildCmssyMetadata(config, path, options?) devuelve un objeto Metadata de Next.js completo a partir de los campos SEO de la página y el branding del workspace: título, descripción, palabras clave, canonical y las alternativas hreflang por idioma, además de las tarjetas Open Graph / Twitter. Llámalo en generateMetadata (ver la ruta de arriba).

Pasa la ruta catch-all tal como se enrutó - con el prefijo de idioma incluido. El prefijo es lo que le dice al SDK qué idioma renderizar; si lo quitas primero, cada traducción hereda el título del idioma por defecto y un canonical que apunta a la URL por defecto, lo que indica a los buscadores que la traducción es un duplicado.

buildCmssyMetadata(cmssy, path, {
  image: "https://assets.cmssy.io/og.png",
  ogType: "article",
});

Header y footer

Las regiones compartidas - header, footer - son bloques de layout. Rendérizalas en app/[[...path]]/layout.tsx: fetchLayouts obtiene los grupos de layout, CmssyServerLayout renderiza una position, y CmssyLocaleProvider expone el idioma activo a los bloques de cliente.

// 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>
  );
}

Enrutamiento localizado

cmssy codifica el idioma como prefijo de ruta: /about es el idioma por defecto, /pl/about es polaco. La misma ruta catch-all gestiona ambos. splitCmssyLocale(config, path) lee los idiomas habilitados del workspace y separa el prefijo del resto - sin petición ni cabeceras, por lo que es seguro para estático.

const { locale, path: rest } = await splitCmssyLocale(cmssy, path);
// ["pl", "about"]  ->  locale "pl", rest ["about"]
// ["about"]        ->  locale "en" (default), rest ["about"]

ISR y revalidación bajo demanda

Las páginas publicadas se cachean y se regeneran mediante ISR con export const revalidate. Para refrescar al instante al publicar, añade una ruta bajo demanda y apunta a ella el webhook content.changed del workspace. cmssy envía por POST la path modificada; la ruta la revalida, o todo el sitio (ámbito layout) cuando no se envía ruta. Protégela con un secreto compartido.

// 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 ?? "/" });
}

Próximos pasos

Renderizado - Cmssy Headless SDK