profound-logoProfound CMS
⌘K
Admin
Theme
DokumenteTutorialBlogPhilosophie
DokumenteTutorialBlogPhilosophie

Anleitungen

FlughafenverzeichnisBereitstellungenBuild & Ship a Stripe Storefront

CMS-Funktionen

Documentation Site TemplateFeature Template BuilderTranslation ServiceOrganizations & Website HeirarchyConnect Profound CMS to your AI clientSettings IntegrationsSettings API KeysSettings UsageEinstellungen-Websites
All Systems Operational
Powered Byprofound-logo
Theme

Build & Ship a Stripe Storefront

Eine praktische Schritt-für-Schritt-Anleitung: Baue ein inhaltsgetriebenes Stripe-Schaufenster auf Profound CMS – ein Katalog, den ein Händler ohne Code pflegt, zwei parametrische Routen, ein Headless-Warenkorb und ein Stripe-gehosteter Checkout.

Der fertige Store in Aktion – eine Kategorie durchstöbern, ein Produkt öffnen, in den Warenkorb legen, zur Kasse gehen.

Ein praxisnaher Leitfaden, der einen inhaltsgesteuerten Store auf Profound CMS aufbaut: ein Produktkatalog (Kategorien + Artikel), im CMS modelliert, Listing- und Detailseiten aus einem gemeinsamen Satz von Routen sowie ein Stripe-gehosteter Checkout, ausgeliefert als Headless-Komponente.

Das Rückgrat besteht aus handgeschriebenem Next.js plus dem Profound-Admin. Claude Code (über den Profound MCP) stemmt die Schwerarbeit bei drei Aufgaben – das Befüllen des Katalogs, das Verdrahten des Designsystems und das Schreiben der Storefront-Komponenten (einschließlich des Headless-Warenkorbs). Drei Teile: Einrichtung, Aufbau, Produktion.

Zahlungen in einer Zeile. Wir nutzen Stripe-hosted Checkout: Der Käufer zahlt auf der Seite von Stripe, nicht auf deiner. Deine App erledigt serverseitig nur zwei Dinge – eine Checkout-Session erstellen und einen Webhook verifizieren. Keine Kartenfelder, keine Stripe Elements, keine PCI-Belastung.

Was du bauen wirst

  • Einen kleinen veröffentlichten Katalog im CMS – drei Kategorien und acht Produkte (das Demo „Edison’s Inventions“) – jedes vom Händler ohne Code bearbeitbar.
  • Zwei parametrische Routen (/products/{item_code}, /categories/{category_code}) plus eine statische /cart, alle aus einem Satz von Komponenten.
  • Einen Headless-Warenkorb (useCart) und Stripe-gehosteten Checkout, wobei der Preis immer serverseitig über eine Stripe-Price-ID aufgelöst wird.
  • Den Store auf Vercel deployt, mit Live-Vorschau und In-place-Bearbeitung für das Team.

Voraussetzungen

  • Bun ≥ 1.3 — curl -fsSL https://bun.sh/install | bash
  • Claude Code mit dem Profound MCP (Teil 1 installiert ihn).
  • Ein Profound CMS-Konto.
  • Ein Stripe-Konto. Dieses Tutorial läuft im Testmodus, damit beim Bauen kein echtes Geld abgebucht wird – der Ablauf ist mit Live-Schlüsseln identisch, du kannst also auch deine echten Kontoschlüssel verwenden, wenn du möchtest. (Im Testmodus sind keine Unternehmens- oder Bankdaten nötig.)
  • Die Stripe CLI (stripe login) für lokale Webhooks.
  • Für das Deployment: die GitHub CLI (gh) und ein Vercel-Konto, das mit GitHub verbunden ist.

Wie die Bausteine zusammenspielen

Profound trennt Content von Rendering:

  • Komponenten definieren die Form von Inhalten. Eine Custom Component mit einem Route Slug-Feld ist routbar (category, item); eine mit dem Tag UI Element ist auf einer Seite platzierbar (nav, product_grid, …).
  • Dokumente sind die Inhalte (ein Produkt, eine Kategorie).
  • UI-Elemente sind Seitenbereiche; jedes skalare Feld nimmt entweder einen statischen Wert oder einen CEL-Ausdruck auf, der zur Renderzeit ausgewertet wird.
  • Parametrische Routen ordnen eine URL einem Dokument + UI-Elementen zu und reichen Routenparameter weiter (meta.params.* in CEL, routeParams in React).
  • Deine Next.js-App liest das Ganze über cms-renderer; Stripe wird als normale API-Routen ergänzt.

Die eine Regel, die den Aufbau bestimmt: CEL bindet nur string-/number-Felder. Skalare Chrome-Elemente (Marke in der Navigation, Footer, Überschriften) werden mit CEL gebunden, während alles, was reichhaltig oder eine Sammlung ist (ein Produktgitter, eine Bildergalerie, Rich Text), innerhalb der React-Komponente über den Routenparameter abgerufen wird. Und Stripe ist die Preis-Quelle der Wahrheit – der price im CMS dient nur der Anzeige; die Belastung wird immer serverseitig aus einer Stripe-Price-ID aufgelöst.

Teil 1 — Einrichtung

Zielzustand: ein kleiner veröffentlichter Katalog, die App ist angebunden, Stripe installiert, das Design sitzt – noch wird nichts gerendert.

1. Registrieren und die Website anlegen

Bei Profound anmelden (WorkOS-Auth). Eine Website namens store erstellen, dann deren Website-ID (die UUID in der Admin-URL) und einen Read-Tier-API-Key (Deployments → Create API key) kopieren. Die App liest nur; das spätere Katalog-Seeding läuft über den MCP, der separat authentifiziert.

2. App scaffolden, verbinden und Stripe hinzufügen

bunx create-profound-next store
cd store
bun add stripe

Das Scaffold ist ein Next.js-App-Router-Projekt, das bereits für Profound vorkonfiguriert ist (cms-renderer-SDK, eine Catch-all-Route, ein generate-schemas-Script, ein <Refresher>). Styling ist keines enthalten. bun add stripe holt das Server-SDK – die einzige Zahlungsabhängigkeit, die ein gehosteter Checkout braucht.

Eigene Werte in .env.local eintragen:

# CMS
PROFOUND_API_KEY=<dein Read-Key>
NEXT_PUBLIC_PROFOUND_WEBSITE_ID=<deine Website-ID>
NEXT_PUBLIC_CMS_API_URL=https://cms.dev.tryprofound.com
NEXT_PUBLIC_BUNNY_CDN_URL=https://cms-profound.b-cdn.net   # liefert CMS-gehostete Bilder aus

# Stripe
STRIPE_SECRET_KEY=sk_test_...            # hier Testschlüssel; auf Live-Key umstellen, wenn du live gehst
STRIPE_WEBHOOK_SECRET=whsec_...          # wird in Aufbau, Schritt 4, eingetragen
NEXT_PUBLIC_SITE_URL=http://localhost:3000

STRIPE_SECRET_KEY aus Stripe → Developers → API keys holen. Wir verwenden einen Testschlüssel (sk_test_…), damit beim Aufbau kein echtes Geld fließt; tausche ihn aus, sobald du echte Zahlungen annehmen möchtest. bun dev starten und localhost:3000 öffnen – der Starter rendert.

Hosted Checkout leitet den Browser auf eine Stripe-URL um, daher benötigt Stripe nur den geheimen Serversschlüssel – kein Publishable Key, kein clientseitiges Stripe-SDK.

3. Die Komponenten category, product_image und item definieren

Drei Custom Components erstellen (Components → Create new component) – die Datenquelle, also ohne UI-Element-Tag. Jede auf Active setzen.

Das CMS hat kein „Array von Bildern“-Feld, daher ist eine Galerie ein Array von Referenzen auf eine kleine product_image-Komponente. category und product_image (und auf Active setzen) vor item erstellen – ein Referenzfeld kann nur auf aktive Komponenten zielen.

  • category – name (Text), code (Text, Route Slug), description (Rich Text), heroImage (Image)
  • product_image – image (Image)
  • item – name (Text), code (Text, Route Slug), description (Rich Text), images (Array von Referenzen → product_image), price (Number, Cent – nur Anzeige), currency (Select, usd), stripePriceId (Text), category (Reference → category), active (Boolean)

Alle Felder optional lassen. Der Admin setzt Feldnamen auf Lower-Snake-Case („Stripe Price Id“ → stripe_price_id) – darauf greift dein Code zu, also lies die echten Namen anschließend aus generate-schemas aus. Wir nennen das routbare Handle code (nicht slug): Es ist sowohl der Route Slug als auch der Schlüssel für ein sauberes documents.getByCode später.

Die item-Komponente – code als Route Slug, images als Referenzen auf product_image, plus stripePriceId und eine category-Referenz.

4. Komponenten als lokale Typen ziehen

bun run generate-schemas

Schreibt Zod-Schemas + Typen nach generated/cms-schemas.ts (categorySchema/Category, itemSchema/Item). Dient gleichzeitig als Verbindungstest – falsche Zugangsdaten schlagen hier fehl.

5. Den Katalog über den Profound MCP befüllen

Den MCP einmal installieren und authentifizieren:

claude mcp add --transport http Profound http://107.21.107.99:8081/mcp

mcp__Profound__authenticate ausführen, den WorkOS-Flow abschließen, dann Claude instruieren:

Erstelle einen kleinen E-Commerce-Katalog für einen Store namens Edison’s Inventions – drei Kategorien und diese Produkte, jeweils mit einer kurzen, zeitgenauen description, einem price in Cent, currency: "usd" und active: true:

  • Lighting & Power (code: lighting): Incandescent Lightbulb (incandescent-lightbulb, $24), Electric Dynamo (electric-dynamo, $890), Electric Pen (electric-pen, $49)
  • Sound Recording (code: sound): Tinfoil Phonograph (tinfoil-phonograph, $249), Carbon Microphone (carbon-microphone, $59), Dictaphone (dictaphone, $179)
  • Motion Pictures (code: motion): Kinetoscope (kinetoscope, $399), Kinetograph Camera (kinetograph, $549)

Jede Kategorie braucht name und den kleingeschriebenen code; jeder Artikel name, denselben kleingeschriebenen code, die description, price (in Cent), currency und active. Speichere alles in data/catalog.json und validiere es gegen unsere Komponenten category und item. Anschließend per Profound MCP jedes Dokument als veröffentlicht anlegen: erst die Kategorien erstellen, ihre IDs sichern, dann die Artikel mit category als Referenz – { "_type": "reference", "_ref": "<category-id>", "_schema": "category" }. Lege die Artikel parallel an.

Claude erstellt data/catalog.json, validiert es und verteilt parallele create_document-Aufrufe (status: "published"). Kategorien vor Artikeln befüllen, damit Referenzen auf bereits vorhandene IDs zeigen.

Die drei befüllten Kategorien, veröffentlicht und live.

Die acht befüllten Produkte, jeweils mit einer Kategorie verknüpft.

6. Stripe-Preise anlegen und einige Produkte verbinden

Der Katalog ist im CMS; jetzt bekommen ein paar Produkte einen realen Stripe-Preis – die Aufgabe des Händlers, erledigt in zwei Admin-Oberflächen, ganz ohne Code:

  1. Stripe-Dashboard → Products → + Add product, einen einmaligen Preis hinterlegen, die Price-ID (price_…) kopieren.
  2. Für ca. drei Highlight-Produkte wiederholen (z. B. Lightbulb, Phonograph, Kinetoscope).
  3. Profound-Admin → item → Documents → die jeweils passende Price-ID in stripePriceId einfügen, speichern.

Das CMS verwaltet den Katalog; Stripe ist die Preisquelle; die Verbindung ist eine Zeichenkette, die der Händler einfügt. (Lieber automatisieren? Der offizielle Stripe MCP kann Products/Prices für dich anlegen – die zurückgegebenen IDs trägst du ebenso ein.)

7. Designsystem integrieren und per KI verdrahten

Das Scaffold ist ungestylt. Lege eine DESIGN.md an (ein Tailwind-v4-@theme-Block + Tokens) im Projektstamm – dein eigener oder lade einen von refero.design herunter. Anschließend Claude mit Fokus auf Styling anweisen:

Lies die Design-Datei, die ich gerade hinzugefügt habe. Richte Tailwind ein, falls nötig, und verknüpfe das Theme und die Fonts, damit das Styling funktioniert. Verwende next/font für Schriften – lade sie nicht zur Laufzeit von Google. Nur das Styling – baue noch keine Seiten oder Komponenten.

Prüfen, dass src/app/globals.css @import "tailwindcss"; plus den @theme-Block enthält und dass localhost:3000 die Tokens zeigt. Den Prompt knapp halten (bei offenen Formulierungen baut ein Agent schnell eine komplette Homepage), Schriften mit next/font laden, nie über einen Laufzeitimport von Google.

8. Produktbilder hinzufügen (optional)

Optional – ein funktionierender Checkout braucht keine Bilder. Für Bilder: ein product_image-Dokument pro Bild anlegen (Upload ins image-Feld), dann diese Referenzen im images-Array des Produkts setzen. Eigene Produktfotos nutzen oder mit einem Bildmodell eine stimmige Serie erzeugen lassen (Claude kann auf Basis von DESIGN.md passende Prompts ableiten und mit einem Midjourney---sref eine einheitliche Optik sichern).

Das eigenständige cms-renderer hat keinen Image-URL-Helper, daher buildAssetUrl in src/lib/image.ts einbauen (~40 Zeilen) – es setzt NEXT_PUBLIC_BUNNY_CDN_URL voran und ergänzt die Erweiterung. Die Komponenten in Aufbau, Schritt 3, verwenden ihn.

Teil 2 — Aufbau

Baue die Rendering-Schicht und den Checkout – am Ende steht ein echter Kauf im Testmodus.

1. Fünf UI-Element-Komponenten definieren

Fünf Komponenten, jeweils Active und mit dem Tag UI Element (Settings → Tags), kein Route Slug:

  • nav → brand · product_grid → heading · product_detail → heading · cart_summary → heading · footer → text (alle Text)

Der Tag UI Element sorgt dafür, dass eine Komponente in der Liste Add UI Element des Page Builders erscheint – Active allein reicht nicht. Jedes Feld ist ein Skalar (die Art, die CEL bindet); die eigentlichen Katalogdaten sind hier kein Feld – ProductGrid/ProductDetail holen sie in Schritt 3 über den Routenparameter.

2. Typen erneut generieren

bun run generate-schemas

3. Katalogleser, Komponenten und Headless-Warenkorb generieren

Ein Prompt erstellt den Lese-Helper, die fünf Komponenten, den Warenkorb und das Registry:

Baue unsere Storefront in src/ mit dem Profound-cms-renderer-SDK.

src/lib/catalog.ts – ein serverseitiger CMS-Reader. Erzeuge einen Client mit getCmsClient({ cmsUrl: process.env.NEXT_PUBLIC_CMS_API_URL!, apiKey: process.env.PROFOUND_API_KEY, websiteId: process.env.NEXT_PUBLIC_PROFOUND_WEBSITE_ID! }) aus cms-renderer/lib/cms-api. Exportiere getItemByCode(code) → cms.documents.getByCode.query({ websiteId, schemaName: "item", code }), das res.document.published_content zurückgibt. Exportiere listItems(categoryCode?) → cms.documents.list.query({ websiteId, schemaName: "item", status: "published", limit: 100 }), mappe res.documents auf .published_content, filtere active !== false, und wenn categoryCode übergeben wird, behalte nur Items, deren category._ref der document.id der Kategorie entspricht. Exportiere resolveImages(refs), das jede item.images-Referenz über cms.documents.get.query({ websiteId, id: ref._ref }) auflöst und das Bildfeld mit dem eingebauten buildAssetUrl (Teil 1, Schritt 8) in eine URL umwandelt.

src/components/ – fünf UI-Element-Komponenten, die in der Catch-all-Route-Registry unter dem Komponentennamen registriert werden, snake_case wie im Admin: { nav, product_grid, product_detail, cart_summary, footer }. Nav und Footer lesen ihr Skalarfeld über die content-Prop (typisiert mit BlockComponentProps<T> aus cms-renderer/lib/types). ProductGrid und ProductDetail sind asynchrone Serverkomponenten, die routeParams lesen und aus catalog.ts nachladen: routeParams.<param> ist { value, … } – auf .value zugreifen, also ruft ProductGrid listItems(routeParams.category_code?.value) auf (Karten verlinken auf /products/{code}) und ProductDetail getItemByCode(routeParams.item_code?.value) (Galerie via resolveImages, Rich-Text-Beschreibung, Preis, Add-to-cart). CartSummary rendert den Warenkorb aus useCart mit einem Pay-Button. formatPrice in eine reine src/lib/format.ts auslagern, damit Client-Komponenten nicht das serverseitige catalog.ts importieren.

src/components/AddToCartButton.tsx – ein "use client"-Button mit den Props { code, name, priceLabel }, der useCart().addItem({ code, name, priceLabel, quantity: 1 }) aufruft. In ProductDetail verwenden.

src/lib/useCart.ts – ein Headless-Warenkorb: Positionen { code, name, priceLabel, quantity } im State, persistiert in localStorage, mit addItem/removeItem/updateQty/subtotal und einem checkout(), das { lines: [{ code, quantity }] } (nur Codes und Mengen – keine Preise) an /api/stripe/checkout postet und anschließend auf die zurückgegebene url umleitet.

Alles mit unserem Designsystem stylen, als eigene Komponenten – nicht das Layout der Quellseite kopieren.

Drei Dinge, die du danach wissen solltest:

  • Skalare Chrome-Werte kommen über content ({ content }: BlockComponentProps<T>) – Felder als Top-Level-Props zu dekonstruieren, lässt den Block leer. Katalogdaten kommen aus routeParams + einem catalog.ts-Fetch, weil CEL keine Listen oder Galerien bindet. Der Warenkorb trägt Artikel-Codes, nie Preise.
  • routeParams.<param> ist { value, schemaName, document } – auf .value zugreifen. Lesevorgänge liefern published_content, nicht .content. Registry-Schlüssel sind snake_case, passend zum Admin.
  • @types/react/@types/react-dom auf v19 anheben – das Scaffold liefert v18, was asynchrone Server-Komponentenblöcke in React 19 bricht.

4. Stripe-Servercode schreiben (die Achse)

Drei kurze Serverdateien – der gesamte Zahlungscode der App. Sie verwenden getItemByCode, sodass die Belastung serverseitig aufgelöst wird.

src/lib/stripe.ts:

import Stripe from "stripe";
export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);

src/app/api/stripe/checkout/route.ts – jedes Item aus dem CMS auflösen, den Stripe-Preis belasten:

import { NextResponse } from "next/server";
import { stripe } from "@/lib/stripe";
import { getItemByCode } from "@/lib/catalog"; // serverseitig, Read-Tier-Key

export async function POST(req: Request) {
  const { lines } = await req.json();                // [{ code, quantity }] — keine Preise vom Client
  const line_items = await Promise.all(
    lines.map(async ({ code, quantity }: { code: string; quantity: number }) => {
      const item = await getItemByCode(code);          // Server löst über das CMS auf
      return { price: item!.stripe_price_id, quantity }; // Preis aus dem CMS, nie vom Client
    })
  );
  const session = await stripe.checkout.sessions.create({
    mode: "payment",
    line_items,
    success_url: `${process.env.NEXT_PUBLIC_SITE_URL}/cart?status=success`,
    cancel_url: `${process.env.NEXT_PUBLIC_SITE_URL}/cart?status=cancelled`,
  });
  return NextResponse.json({ url: session.url });     // Client leitet hierhin weiter
}

src/app/api/stripe/webhook/route.ts – das vertrauenswürdige Fulfillment-Signal:

import { stripe } from "@/lib/stripe";

export async function POST(req: Request) {
  const body = await req.text();                      // RAW-Body – nötig für Signaturprüfung
  const sig = req.headers.get("stripe-signature")!;
  let event;
  try {
    event = stripe.webhooks.constructEvent(body, sig, process.env.STRIPE_WEBHOOK_SECRET!);
  } catch {
    return new Response("Bad signature", { status: 400 });
  }
  if (event.type === "checkout.session.completed") {
    // Fulfillment: Bestellung erfassen / Quittung senden.
  }
  return new Response(null, { status: 200 });
}

Notwendiger Scaffold-Fix: Das Scaffold leitet src/proxy.ts jede /api/* an das CMS weiter, daher laufen deine Stripe-Routen nie. Lass sie zuerst passieren:

import { createCmsProxy } from "cms-renderer/lib/proxy";
import { NextResponse, type NextRequest } from "next/server";
import { cmsConfig } from "@/lib/cms-config";

const cmsProxy = createCmsProxy({ upstream: cmsConfig.cmsUrl });
const LOCAL_API_PREFIXES = ["/api/stripe"];

export const proxy = async (request: NextRequest) => {
  if (LOCAL_API_PREFIXES.some((p) => request.nextUrl.pathname.startsWith(p))) {
    return NextResponse.next();                       // lokal behandeln
  }
  return cmsProxy(request as unknown as Parameters<typeof cmsProxy>[0]);
};
// die `export const config = { matcher: [...] }` des Scaffolds unverändert lassen

Prüfen: curl -X POST localhost:3000/api/stripe/webhook -d x gibt Bad signature zurück.

Stripe CLI für lokale Webhooks starten:

stripe login
stripe listen --forward-to localhost:3000/api/stripe/webhook
# das whsec_... in STRIPE_WEBHOOK_SECRET eintragen, bun dev neu starten

Das whsec_… gilt pro Sitzung. Zwei Regeln sichern das Ganze: Der Checkout ermittelt den Preis per code erneut über das CMS (ein manipuliertes Cart kann ihn nicht ändern), und der Webhook verifiziert die Signatur gegen den rohen Body.

5. Routen anlegen

Admin → Pages → dreimal Seite erstellen. Jede Route mit ihrem Dokument verknüpfen (Slug-Feld code):

  1. /products/{item_code} → item
  2. /categories/{category_code} → category
  3. /cart – eine statische Seite (den Literal /cart eintragen, nicht /{cart})

6. UI-Elemente hinzufügen, CEL verdrahten, veröffentlichen

Für jede Route: Page Builder → Add UI Element → Custom → Komponenten in Reihenfolge hinzufügen, skalare Felder füllen (statischer Wert oder CEL), Publish.

  • /products/{item_code}: nav, product_detail, footer
  • /categories/{category_code}: nav, product_grid, footer
  • /cart: nav, cart_summary, footer

nav.brand und footer.text als statische Strings setzen; Überschriften als statische Bezeichnungen.

Der Page Builder auf der Kategorieroute – das Element product_grid ausgewählt, die Überschrift per CEL gebunden.

Der Page Builder auf der Produktroute – das Element product_detail auf einer Produktbindung.

Parametrischer Page-Builder-Knackpunkt: Auf den beiden parametrischen Routen bleiben hinzugefügte UI-Elemente nicht erhalten (Blöcke verwaisen, die Seite rendert leer). Bis das behoben ist, die block_ids dieser Seiten direkt über den Profound MCP update_page verdrahten und anschließend veröffentlichen. (Die statische /cart lässt sich normal verknüpfen.) Aus demselben Grund leitet ProductGrid seine Überschrift aus der geladenen Kategorie statt über CEL ab.

7. Rendern und kaufen

  • /categories/lighting → das Grid. Produkt anklicken → Detail + Add to cart. /cart → Pay.
  • Zahlung leitet zu Stripe-hosted Checkout. Testkarte 4242 4242 4242 4242, beliebiges zukünftiges Ablaufdatum/CVC nutzen. Zurück kehrst du zu /cart?status=success, und stripe listen zeigt checkout.session.completed.

Eine gerenderte Produktseite – Galerie, Preis und Add-to-cart.

Der Warenkorb – Positionen und ein einzelner Pay-with-Stripe-Button.

Nur Produkte mit Preis sind kaufbar – kaufe eines der ~3 Produkte, die du in Schritt 6 bepreist hast.

Optional – internationalisieren. Übersetze jede Komponente (alle 35 Sprachen auf einmal), füge ein Segment /{language}/… hinzu, das der eingebauten Systemkomponente language zugeordnet ist, und wechsle CEL-gebundene Felder zu documents.translated. Siehe das Airport-Directory-Tutorial, Teil 2, Schritt 7.

Teil 3 — Produktion

1. Ausliefern: GitHub, dann Vercel

git init && git add -A && git commit -m "Stripe storefront"
gh repo create store --private --source=. --push   # --public geht natürlich auch

Build-Befehl: generated/cms-schemas.ts ist gitignored, daher den Build darauf festnageln, die Dateien neu zu erzeugen – vercel.json hinzufügen:

{ "$schema": "https://openapi.vercel.sh/vercel.json", "buildCommand": "bun run generate-schemas && next build" }

In Vercel: Add New → Project, store importieren und die Umgebungsvariablen eintragen – PROFOUND_API_KEY, NEXT_PUBLIC_PROFOUND_WEBSITE_ID, NEXT_PUBLIC_CMS_API_URL, NEXT_PUBLIC_BUNNY_CDN_URL, STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET (der Wert für den Deployment-Endpunkt, siehe unten) und NEXT_PUBLIC_SITE_URL (deine Produktions-URL). Deployen.

Dann den deployten Webhook verdrahten (der lokale stripe listen-Schlüssel galt nur lokal): Stripe → Developers → Webhooks → + Add endpoint → https://<prod>/api/stripe/webhook, Event checkout.session.completed. Das whsec_… nach Vercel kopieren und redeployen.

Fehlende Umgebungsvariablen = „lokal ok, in Produktion leer“ – der Deploy-Klassiker. Wir deployen hier mit Testschlüsseln; stelle STRIPE_SECRET_KEY und das Webhook-Secret auf deine Live-Werte um, sobald du echte Zahlungen akzeptieren möchtest.

2. Live-Vorschau und In-place-Bearbeitung

Beides kommt mit dem Scaffold.

  • Live preview: Der <Refresher> aktualisiert die Seite in der Vorschau, sobald ein Editor im Admin speichert – kein Redeploy nötig. (Er ist eine Vorschau für den Editor; Besucher sehen veröffentlichte Inhalte mit normaler Revalidation.)
  • In-place Editing: ?edit_mode=true an jede URL hängen, um Bearbeitungs-Overlays zu sehen. Öffentliche Besucher bekommen die saubere Seite.

Die fehlende Preview-Route ergänzen. Der Admin lädt sein Preview-iframe unter /cms-preview_<path>; ohne diese Route gibt es 404. Ergänze sie:

// src/app/cms-preview_/[...slug]/page.tsx
import { ParametricRoutePreviewPage } from "cms-renderer/lib/renderer";
import { registry } from "../../registry";   // Registry in ein Shared-Modul auslagern
export default async function Page({ params, searchParams }) {
  const { slug } = await params;
  const PreviewPage = ParametricRoutePreviewPage as any; // async RSC; React-19-Typen
  return <PreviewPage registry={registry} apiKey={process.env.PROFOUND_API_KEY ?? ""}
    websiteId={process.env.NEXT_PUBLIC_PROFOUND_WEBSITE_ID ?? ""}
    cmsUrl={process.env.NEXT_PUBLIC_CMS_API_URL ?? "https://cms.dev.tryprofound.com"}
    params={Promise.resolve({ slug })} searchParams={searchParams} />;
}

src/app/cms-preview_/page.tsx ebenfalls hinzufügen (gleich, slug: []) für das Segment-Root.

Das war der Build

Eine inhaltsgesteuerte Stripe-Storefront: ein CMS-Katalog, Listing- + Detailseiten aus einem einzigen Routensatz und ein funktionierender gehosteter Checkout. KI hat den Katalog befüllt, das Design verdrahtet und den Katalogleser + Komponenten + Headless-Warenkorb geschrieben; du hast die Komponenten, die Stripe-Preisverknüpfungen, die drei Routen, das CEL-Chrome und drei kurze Stripe-Dateien erledigt. CEL bindet das Chrome; Komponenten holen den Katalog. Und Stripe blieb schlank – ein sessions.create-Aufruf und ein signierter Webhook, bei dem der Käufer auf der Stripe-eigenen Seite bezahlt.

Continue Reading
Previous‹Bereitstellungen