Funciones avanzadas de bloques

Bloques de layout, componentes de servidor vs cliente y obtención de datos con loaders de servidor.

29 de junio de 2026

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 { defineBlock, fields } from "@cmssy/react";
import Header from "./Header";

export const headerBlock = defineBlock({
  type: "header",
  label: "Header",
  layoutPositions: ["header"],
  component: Header,
  props: {
    logo: fields.media({ label: "Logo" }),
    links: fields.repeater({
      label: "Navigation",
      itemSchema: {
        label: fields.text({ label: "Label" }),
        url: fields.link({ label: "URL" }),
      },
    }),
  },
});

Renderiza la posición en el layout de tu ruta con CmssyServerLayout, y a través del puente de edición en la ruta de edición: consulta Layouts.


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 en opacity:0 y 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 your app supplies it
context.workspace;  // { id, slug } when resolved
context.page;       // { id, slug, pageType } - the page this block is on

Consulta Autenticación de miembros para context.auth y el flujo de autenticación seguro.

context.page (SDK 10.2+) es la forma en que un bloque se refiere a sí mismo: un loader de entradas relacionadas excluye la entrada actual por context.page.id, y un índice de sección lista los hijos de context.page.slug. Solo identidad - sin ancestros ni árbol de navegación - y ausente en los bloques de layout, que pertenecen a un layout y no a una página.


Próximos pasos