Funciones avanzadas de bloques
Bloques de layout, estilos con Tailwind, componentes de servidor vs cliente y obtención de datos con loaders de servidor.
Bloques de layout
El header, el footer y otras regiones compartidas son bloques de layout — iguales que los bloques de página, pero marcados con layoutPositions y renderizados por CmssyServerLayout por posición.
// blocks/header/block.ts
import type { ComponentType } from "react";
import { defineBlock, fields } from "@cmssy/react";
import Component from "./src";
export const headerBlock = defineBlock({
type: "header",
label: "Header",
layoutPositions: ["header"],
component: Component as unknown as ComponentType<{ content: Record<string, unknown> }>,
props: {
logo: fields.media({ label: "Logo" }),
links: fields.repeater({
label: "Navigation",
itemSchema: {
label: fields.singleLine({ label: "Label" }),
url: fields.link({ label: "URL" }),
},
}),
},
});
Renderiza la posición en app/layout.tsx con CmssyServerLayout.
Estilos con Tailwind v4
Los bloques se estilizan con Tailwind. Importa tu hoja de estilos global una vez y luego usa clases utilitarias en tus componentes.
/* styles/main.css */
@import "tailwindcss";
@theme {
--color-background: oklch(1 0 0);
--color-foreground: oklch(0.145 0 0);
--color-primary: oklch(0.205 0 0);
}
Componentes de servidor y cliente
Los bloques son componentes estándar de Next.js. Por defecto se renderizan como componentes de servidor — cero JS en el cliente. Añade "use client" al inicio de un componente solo cuando necesite hooks, manejadores de eventos, APIs del navegador o animaciones en el cliente.
"use client";
import { useState } from "react";
export default function StatsCounter({ content }: { content: Record<string, unknown> }) {
const [n, setN] = useState(0);
// ...
}
Las animaciones de scroll / entrada necesitan un componente de cliente. Si un bloque usa reveal-on-scroll (framer-motion
whileInView,initial={{ opacity: 0 }}, IntersectionObserver), debe ser un componente de cliente ("use client") para que la animación se ejecute — de lo contrario el HTML del servidor se queda enopacity:0y el contenido nunca aparece en el sitio publicado (en el editor se ve bien, porque renderiza en el cliente).
Obtener datos: loaders de servidor
La mayoría de los bloques deberían obtener datos en el servidor, durante el SSR, mediante un loader. El loader se ejecuta en CmssyServerPage y pasa su resultado al componente como la prop data — así el contenido llega al HTML renderizado en el servidor (rastreable, sin parpadeo de carga) y las dependencias server-only nunca llegan al bundle del cliente.
import { defineBlock } from "@cmssy/react";
export const blogPostsBlock = defineBlock({
type: "blog-posts",
component: BlogPosts, // receives { content, context, data }
loader: async ({ content, context }) => {
// runs on the server only; returns RSC-serializable data
const { loadPosts } = await import("./load-posts");
return loadPosts({ limit: Number(content.postsPerPage) || 9 });
},
});
El loader no se ejecuta en el editor — allí el componente recibe data: undefined, así que renderiza siempre un fallback razonable. El valor devuelto cruza la frontera servidor→cliente, por lo que debe ser serializable para RSC: objetos planos, arrays y primitivos — sin funciones ni instancias de clases.
Mantén las dependencias server-only o pesadas detrás de un import() dinámico dentro del loader (como arriba), y protege los helpers de servidor compartidos con typeof window !== "undefined" para que fallen ruidosamente si alguna vez se empaquetan para el navegador.
Llamar a la API de delivery
Usa createCmssyClient(...).queryScoped(...) de @cmssy/react. queryScoped inyecta workspaceId automáticamente: cuando tu query declara $workspaceId y no lo pasas, el SDK lo resuelve a partir de tu workspaceSlug y añade tanto la variable como la cabecera x-workspace-id.
// load-posts.ts — a server-only helper, imported via dynamic import()
import { createCmssyClient } from "@cmssy/react";
import { cmssy } from "@/cmssy.config";
const client = createCmssyClient(cmssy);
export async function loadPosts(vars: { limit: number }) {
if (typeof window !== "undefined") throw new Error("loadPosts is server-only");
return client.queryScoped(PUBLIC_PAGES_QUERY, vars);
}
Datos en el cliente y el context del bloque
Para interactividad tras el primer render (búsqueda, paginación, filtrado), inicializa el estado del cliente con el data del SSR y obtén el resto en el cliente con el mismo createCmssyClient. El context del bloque también lleva datos que a menudo necesitas sin hacer fetch:
context.locale; // { current, default, enabled }
context.isPreview; // true inside the editor
context.forms; // resolved form definitions (when present)
context.auth; // { isAuthenticated, member } when config.auth is set
context.workspace; // { id, slug } when resolved
Consulta Autenticación de miembros para context.auth y el flujo de autenticación seguro.