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

Rutas y páginas

Tres formas de petición llegan a tu app y necesitan tres cosas distintas. Este es el cableado que sirve a las tres.

Cópialo entero. Las piezas dependen unas de otras, y las dependencias no son evidentes.

El modelo mental

Tres formas de petición llegan a tu app, y cada una necesita algo distinto:

  • Un visitante: contenido publicado, renderizado en servidor, estático donde se pueda. Velocidad, y el CMS fuera de la ruta de renderizado.
  • Vista previa de borrador (la cookie /api/draft): contenido borrador en la ruta pública, sin editor. Alguien revisa un cambio, no lo edita.
  • El iframe del editor (cmssyEdit=1 más un cmssySecret que coincida): contenido borrador más el puente de edición, en su propia ruta dinámica.

La tercera es la que sorprende. Una página estática nunca ve la cadena de consulta, así que no puede saber que la están editando. Por eso existe /cmssy-edit, y todo lo demás se deriva de ahí.

1. Configuración

// cmssy.config.ts
import { defineCmssyConfig } from "@cmssy/next";

export const cmssy = defineCmssyConfig({
  org: process.env.CMSSY_ORG_SLUG,
  workspaceSlug: process.env.CMSSY_WORKSPACE_SLUG,
  draftSecret: process.env.CMSSY_DRAFT_SECRET,
});

Pasa process.env en crudo. Un fallback ?? "" convierte una variable ausente en una vacía, y el error aflora más tarde, en un sitio sin relación.

Este módulo lee el entorno del servidor. Nunca importes un valor desde él -ni desde un módulo que lo importe- en un componente "use client". Los tipos no importan, se borran. Los valores arrastran process.env al bundle del navegador.

2. Middleware

// proxy.ts
import { createCmssyProxy } from "@cmssy/next/middleware";
import { cmssy } from "@/cmssy.config";

export const proxy = createCmssyProxy(cmssy, {
  // Solo si tus URL llevan el idioma (/es/about) Y tus rutas son caminos
  // estáticos en vez de una catch-all.
  stripLocalePrefix: true,
});

// Next lo analiza en tiempo de compilación, así que el matcher debe ser un
// literal: una constante importada se rechaza.
export const config = { matcher: ["/((?!_next/|api/|.*\\..*).*)"] };

El preset resuelve el idioma, envía el tráfico verificado del editor a /cmssy-edit llevando ese idioma y la marca de edición, aplica la CSP que permite al panel enmarcar tu sitio y recorta el prefijo de idioma si lo pediste, en ese orden.

El orden no es un detalle, y cada forma de equivocarse ya se ha desplegado alguna vez:

  • Resolver el locale después del rewrite y la vista previa del editor se renderiza en el idioma equivocado: una ruta no puede leer un prefijo que nunca ve.
  • Olvidar la marca de edición y la cabecera y el pie llegan como marcado que el editor puede seleccionar pero no rellenar.

3. La página pública

Una sola ruta catch-all renderiza todas las páginas publicadas. Los metadatos vienen de un helper tuyo, construido sobre la API de entrega: el SDK entrega datos, tu aplicación es dueña de sus rutas.

// app/[[...path]]/page.tsx
import { createCmssyPage } from "@cmssy/next/server";
import { cmssy } from "@/cmssy/config";
import { blocks } from "@/cmssy/blocks";
import { buildPageMetadata } from "@/services/seo";

export const revalidate = 3600;
export const dynamicParams = true;

export async function generateMetadata({ params }) {
  const { path } = await params;
  // Tal como se enrutó, con prefijo: el prefijo ES el idioma.
  return buildPageMetadata(path);
}

export default createCmssyPage(cmssy, blocks);

buildPageMetadata es tuyo, no del SDK. Consulta los campos SEO de la página y devuelve un objeto Metadata de Next.js; consulta SEO para ver qué contiene.

4. La ruta de edición

// app/cmssy-edit/[[...path]]/page.tsx
import { createCmssyEditPage } from "@cmssy/next/server";
import { cmssy } from "@/cmssy.config";
import { blocks } from "@/cmssy/blocks";
import { CmssyEditor } from "@/cmssy/editor";

export const dynamic = "force-dynamic";

export default createCmssyEditPage(cmssy, blocks, { editor: CmssyEditor });

Omítelo y la vista previa del editor sale en blanco. Es la forma más común de romper una app de cmssy, y ningún build te avisará: consulta pruebas.

5. El puente del editor

// cmssy/editor.tsx
"use client";
import { CmssyLazyEditor } from "@cmssy/react/client";
import type { CmssyEditorProps } from "@cmssy/next";

export function CmssyEditor(props: CmssyEditorProps) {
  return <CmssyLazyEditor {...props} load={() => import("./blocks")} />;
}

El registro se carga de forma perezosa en el cliente, así que tus loaders de bloque -que corren en servidor y leen la configuración- nunca llegan al bundle del navegador.

Caché

Publicar no despliega. La ruta pública cachea según su propio revalidate, y cmssy puede llamar a un webhook de revalidación al publicar:

export const revalidate = 3600;
export const dynamicParams = true;

La ruta de edición es la excepción: es force-dynamic a propósito, porque una página de edición cacheada mostraría el borrador de ayer.

Siguientes pasos

  • Layouts: cabecera y pie como bloques editables.
  • Pruebas: demostrar que la ruta de edición sigue funcionando.
  • Cómo funciona cmssy: qué ocurre entre la petición y el renderizado.