Teraz z AI - twórz strony przez serwer MCP

Route'y i strony

Do Twojej aplikacji docierają trzy kształty żądań i każdy potrzebuje czegoś innego. Oto okablowanie, które obsługuje wszystkie trzy.

Skopiuj to w całości. Elementy zależą od siebie nawzajem, a te zależności nie są oczywiste.

Model myślowy

Do Twojej aplikacji docierają trzy kształty żądań i każdy potrzebuje czegoś innego:

  • Odwiedzający - treść opublikowana, renderowana na serwerze, statycznie gdzie się da. Szybkość i trzymanie CMS-a poza ścieżką renderowania.
  • Podgląd roboczy (ciasteczko /api/draft) - treść robocza na publicznym routcie, bez edytora. Ktoś przegląda zmianę, a nie ją edytuje.
  • Ramka edytora (cmssyEdit=1 plus pasujący cmssySecret) - treść robocza plus most edycyjny, na własnym dynamicznym routcie.

Ten trzeci punkt zaskakuje ludzi. Statyczna strona nigdy nie widzi query stringa, więc nie może wiedzieć, że jest edytowana. Dlatego istnieje /cmssy-edit, i wszystko poniżej z tego wynika.

1. Konfiguracja

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

Przekazuj process.env surowo. Fallback ?? "" zamienia brakującą zmienną w pustą, a błąd wychodzi później, w zupełnie niezwiązanym miejscu.

Ten moduł czyta env serwera. Nigdy nie importuj z niego wartości - ani z modułu, który go importuje - w komponencie "use client". Typy są w porządku, są wymazywane. Wartości wciągają process.env do bundla przeglądarki.

2. Middleware

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

export const proxy = createCmssyProxy(cmssy, {
  // Tylko jeśli Twoje URL-e niosą język (/pl/about) ORAZ Twoje route'y to
  // ścieżki statyczne, a nie catch-all.
  stripLocalePrefix: true,
});

// Next parsuje to w czasie kompilacji, więc matcher musi być literałem -
// zaimportowana stała jest odrzucana.
export const config = { matcher: ["/((?!_next/|api/|.*\\..*).*)"] };

Preset rozwiązuje język, kieruje zweryfikowany ruch edytora na /cmssy-edit niosąc ten język i flagę edycji, nakłada CSP pozwalające panelowi osadzić Twój serwis w ramce i ścina prefiks języka, jeśli o to poprosiłeś - w tej kolejności.

Kolejność to nie szczegół, a każdy sposób jej zepsucia już raz trafił na produkcję:

  • Rozwiąż locale po przepisaniu, a podgląd w edytorze wyrenderuje się w złym języku - route nie odczyta prefiksu, którego nigdy nie widzi.
  • Zapomnij o fladze edycji, a header i footer przyjdą jako markup, który edytor może zaznaczyć, ale nie może wypełnić.

3. Strona publiczna

Jeden catch-all route renderuje każdą opublikowaną stronę. Metadane biorą się z helpera, który należy do Ciebie, zbudowanego na API dostawczym - SDK daje dane, Twoja aplikacja jest właścicielem swoich route'ów:

// 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;
  // Tak jak w routingu, z prefiksem: prefiks JEST językiem.
  return buildPageMetadata(path);
}

export default createCmssyPage(cmssy, blocks);

buildPageMetadata jest Twój, nie SDK. Odpytuje pola SEO strony i zwraca obiekt Metadata Next.js - zobacz SEO, co dokładnie zawiera.

4. Route edycyjny

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

Pomiń ten plik, a podgląd w edytorze będzie pusty. To najczęstszy sposób na zepsucie aplikacji cmssy, a nic w buildzie Cię nie ostrzeże - zobacz testowanie.

5. Most edytora

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

Rejestr ładuje się leniwie na kliencie, więc Twoje loadery bloków - które działają po stronie serwera i czytają konfigurację - nigdy nie trafiają do bundla przeglądarki.

Cache

Publikacja to nie deploy. Publiczny route cache'uje się według własnego revalidate, a cmssy może przy publikacji wywołać webhook rewalidacji, żeby unieważnić go wcześniej:

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

Route edycyjny jest wyjątkiem: jest force-dynamic celowo, bo scache'owana strona edycji pokazywałaby wczorajszy draft.

Następne kroki