Jetzt mit KI-gestütztem Page Building über den MCP-Server
Documentation

Erweiterte Block-Funktionen

Layout-Blöcke, Tailwind-Styling, Server- vs. Client-Komponenten und Datenabruf mit Server-Loadern.

Last updated: 29. Juni 2026

Layout-Blöcke

Header, Footer und andere gemeinsame Bereiche sind Layout-Blöcke — identisch mit Seiten-Blöcken, aber mit layoutPositions markiert und von CmssyServerLayout pro Position gerendert.

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

Rendere die Position in app/layout.tsx mit CmssyServerLayout.


Styling mit Tailwind v4

Blöcke werden mit Tailwind gestylt. Importiere dein globales Stylesheet einmal und nutze dann Utility-Klassen in deinen Komponenten.

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

Server- & Client-Komponenten

Blöcke sind Standard-Next.js-Komponenten. Standardmäßig rendern sie als Server-Komponenten — null Client-JS. Füge "use client" nur dann oben in einer Komponente hinzu, wenn sie Hooks, Event-Handler, Browser-APIs oder Client-Animationen braucht.

"use client";
import { useState } from "react";

export default function StatsCounter({ content }: { content: Record<string, unknown> }) {
  const [n, setN] = useState(0);
  // ...
}

Scroll-/Eingangsanimationen brauchen eine Client-Komponente. Wenn ein Block Reveal-on-Scroll nutzt (framer-motion whileInView, initial={{ opacity: 0 }}, IntersectionObserver), muss er eine Client-Komponente sein ("use client"), damit die Animation läuft — sonst bleibt das Server-HTML bei opacity:0 und der Inhalt blendet auf der veröffentlichten Seite nie ein (im Editor sieht es gut aus, denn der rendert client-seitig).


Daten abrufen: Server-Loader

Die meisten Blöcke sollten auf dem Server, während des SSR, per loader laden. Der Loader läuft in CmssyServerPage und übergibt sein Ergebnis als data-Prop an die Komponente — so landet der Inhalt im server-gerenderten HTML (crawlbar, kein Lade-Flackern) und server-only-Abhängigkeiten erreichen nie das Client-Bundle.

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

Der Loader läuft nicht im Editor — dort erhält die Komponente data: undefined, also rendere immer einen sinnvollen Fallback. Der Rückgabewert überquert die Server→Client-Grenze und muss daher RSC-serialisierbar sein: einfache Objekte, Arrays und Primitive — keine Funktionen oder Klasseninstanzen.

Halte server-only- oder schwere Abhängigkeiten hinter einem dynamischen import() im Loader (wie oben) und sichere gemeinsame Server-Helper mit typeof window !== "undefined" ab, damit sie laut fehlschlagen, falls sie je für den Browser gebundelt werden.

Die Delivery-API aufrufen

Nutze createCmssyClient(...).queryScoped(...) aus @cmssy/react. queryScoped injiziert workspaceId automatisch: Wenn deine Query $workspaceId deklariert und du sie nicht übergibst, löst das SDK sie aus deinem workspaceSlug auf und fügt sowohl die Variable als auch den x-workspace-id-Header hinzu.

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

Client-seitige Daten & der Block-Context

Für Interaktivität nach dem First Paint (Suche, Pagination, Filterung) initialisiere den Client-State aus dem SSR-data und lade den Rest client-seitig mit demselben createCmssyClient. Der Block-context trägt außerdem Daten, die du oft ohne Fetch brauchst:

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

Siehe Mitglieder-Authentifizierung für context.auth und den sicheren Auth-Flow.


Nächste Schritte

Advanced Block Features - Cmssy Headless SDK