Teraz z AI - twórz strony przez serwer MCP

Zaawansowane funkcje bloków

Bloki layoutu, komponenty serwerowe i klienckie, pobieranie danych przez server loadery.

Last updated: 29 czerwca 2026

Bloki layoutu

Header, footer i inne współdzielone regiony to bloki layoutu — takie same jak bloki strony, ale oznaczone layoutPositions i renderowane przez CmssyServerLayout per pozycja.

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

Renderuj pozycję w app/layout.tsx przez CmssyServerLayout.


Komponenty serwerowe i klienckie

Bloki to standardowe komponenty Next.js. Domyślnie renderują się jako komponenty serwerowe — zero JS po stronie klienta. Dodaj "use client" na górze komponentu tylko gdy potrzebuje hooków, handlerów zdarzeń, API przeglądarki lub animacji klienckich.

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

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

Animacje scroll / wejścia wymagają komponentu klienckiego. Jeśli blok używa reveal-on-scroll (framer-motion whileInView, initial={{ opacity: 0 }}, IntersectionObserver), musi być komponentem klienckim ("use client"), żeby animacja zadziałała — inaczej serwerowy HTML zostaje przy opacity:0 i treść nigdy się nie pojawi na opublikowanej stronie (w edytorze wygląda dobrze, bo renderuje się po stronie klienta).


Pobieranie danych: server loadery

Większość bloków powinna pobierać dane po stronie serwera, podczas SSR, przez loader. Loader uruchamia się w CmssyServerPage i przekazuje wynik do komponentu jako prop data — dzięki temu treść trafia do serwerowego HTML (indeksowalna, bez mignięcia ładowania), a zależności server-only nigdy nie trafiają do bundla klienta.

import { defineBlock } from "@cmssy/react";

export const blogPostsBlock = defineBlock({
  type: "blog-posts",
  component: BlogPosts, // otrzymuje { content, context, data }
  loader: async ({ content, context }) => {
    // uruchamia się tylko na serwerze; zwraca dane serializowalne dla RSC
    const { loadPosts } = await import("./load-posts");
    return loadPosts({ limit: Number(content.postsPerPage) || 9 });
  },
});

Loader nie uruchamia się w edytorze — tam komponent otrzymuje data: undefined, więc zawsze renderuj sensowny fallback. Zwracana wartość przechodzi przez granicę serwer→klient, więc musi być serializowalna dla RSC: zwykłe obiekty, tablice i prymitywy — bez funkcji i instancji klas.

Trzymaj zależności server-only lub ciężkie za dynamicznym import() wewnątrz loadera (jak wyżej), a współdzielone pomocniki serwerowe zabezpiecz typeof window !== "undefined", żeby głośno padły, jeśli kiedykolwiek trafią do bundla przeglądarki.

Wywoływanie API dostarczania

Użyj createCmssyClient(...).queryScoped(...) z @cmssy/react. queryScoped automatycznie wstrzykuje workspaceId: gdy zapytanie deklaruje $workspaceId i go nie przekażesz, SDK rozwiązuje je z workspaceSlug i dodaje zarówno zmienną, jak i nagłówek x-workspace-id.

// load-posts.ts — pomocnik server-only, importowany przez 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 działa tylko na serwerze");
  return client.queryScoped(PUBLIC_PAGES_QUERY, vars);
}

Dane po stronie klienta i context bloku

Po interaktywność po pierwszym renderze (wyszukiwanie, paginacja, filtrowanie) zasil stan kliencki z SSR-owego data i dopobierz resztę po stronie klienta tym samym createCmssyClient. context bloku niesie też dane, których często potrzebujesz bez pobierania:

context.locale;     // { current, default, enabled }
context.isPreview;  // true w edytorze
context.forms;      // rozwiązane definicje formularzy (gdy są)
context.auth;       // { isAuthenticated, member } gdy config.auth jest ustawione
context.workspace;  // { id, slug } gdy rozwiązane

Zobacz Uwierzytelnianie członków po context.auth i bezpieczny flow auth.


Następne kroki