Praktický návod: postavte obsahovo riadený Stripe storefront na Profound CMS — katalóg, ktorý obchodník upravuje bez kódu, dve parametrické trasy, headless košík a pokladňa hostovaná v Stripe.
Dokončený obchod v akcii — prehliadni si kategóriu, otvor produkt, pridaj do košíka, dokonči nákup.
Praktický návod, ktorý vytvorí obsahovo riadený obchod v Profound CMS: produktový katalóg (kategórie + položky) modelovaný v CMS, stránka zoznamu a detailu z jednej sady trás a pokladňa hostovaná v Stripe dodaná ako headless komponent.
Chrbticu tvorí ručne písaný Next.js plus administrácia Profoundu. Claude Code (cez Profound MCP) odvedie ťažkú prácu v troch úlohách — naplnenie katalógu, zapojenie dizajnového systému a napísanie komponentov výkladu (vrátane headless košíka). Tri časti: Nastavenie, Výstavba, Produkcia.
Platby v jednom riadku. Používame pokladňu hostovanú v Stripe: zákazník platí na Stripe stránke, nie na vašej. Vaša aplikácia urobí len dve serverové veci — vytvorí Checkout Session a overí jeden webhook. Žiadne políčka na kartu, žiadne Stripe Elements, žiadna PCI záťaž.
/products/{item_code}, /categories/{category_code}) plus statická /cart, všetko z jednej sady komponentov.useCart) a pokladňa hostovaná v Stripe, pričom cena sa vždy vyráta na serveri zo Stripe Price ID.curl -fsSL https://bun.sh/install | bashstripe login) na lokálne webhoky.gh) a účet vo Verceli pripojený ku GitHubu.Profound oddeľuje obsah od renderovania:
category, item); komponent označený UI Element je umiestniteľný na stránku (nav, product_grid, …).meta.params.* v CEL, routeParams v Reacte).cms-renderer; Stripe pridáte ako bežné API trasy.Jedno pravidlo, ktoré formuje výstavbu: CEL viaže len polia string/number. Takže skalárny chrome (značka v navigácii, päta, nadpisy) sa viaže cez CEL, zatiaľ čo všetko bohatšie alebo kolekcia (mriežka produktov, galéria obrázkov, rich text) sa načítava vo vnútri React komponentu podľa parametra trasy. A Stripe je zdroj pravdy pre cenu — čo je v CMS price, slúži len na zobrazenie; platba sa vždy vyráta na serveri zo Stripe Price ID.
Cieľový stav: malý publikovaný katalóg, aplikácia napojená na jeho čítanie, Stripe nainštalovaný, dizajn pripravený — ešte nič nerendruje.
Zaregistrujte sa v Profounde (WorkOS autentifikácia). Vytvorte web s názvom store, potom skopírujte jeho ID webu (UUID v URL administrácie) a API kľúč na čítanie (Deployments → Create API key). Aplikácia len číta; neskoršie naplnenie katalógu prebehne cez MCP, ktoré sa autentifikuje samostatne.
bunx create-profound-next store
cd store
bun add stripe
Scaffold je projekt Next.js App Router prednastavený pre Profound (cms-renderer SDK, trasa pre všetko, skript generate-schemas, <Refresher>). Nedodáva štýly.
bun add stripe stiahne serverové SDK — jedinú platobnú závislosť, ktorú hostovaná pokladňa potrebuje.
Pridajte svoje hodnoty do .env.local:
# CMS
PROFOUND_API_KEY=<váš kľúč na čítanie>
NEXT_PUBLIC_PROFOUND_WEBSITE_ID=<vaše ID webu>
NEXT_PUBLIC_CMS_API_URL=https://cms.dev.tryprofound.com
NEXT_PUBLIC_BUNNY_CDN_URL=https://cms-profound.b-cdn.net # slúži na obrázky hostované v CMS
# Stripe
STRIPE_SECRET_KEY=sk_test_... # sem testovací kľúč; pri ostrom režime ho vymeňte za live kľúč
STRIPE_WEBHOOK_SECRET=whsec_... # doplní sa v kroku Výstavby 4
NEXT_PUBLIC_SITE_URL=http://localhost:3000
STRIPE_SECRET_KEY získate v Stripe → Developers → API keys. Používame testovací kľúč (sk_test_…), takže počas výstavby nepotečú reálne peniaze; keď ste pripravení prijímať platby, prepnite na live kľúč. Spustite bun dev a otvorte localhost:3000 — starter sa vyrendruje.
Hostovaná pokladňa presmeruje prehliadač na URL Stripe, takže serverový tajný kľúč je všetko, čo Stripe potrebuje — žiadny public key, žiadne klientské Stripe SDK.
category, product_image a itemVytvorte tri Custom Components (Components → Create new component) — zdroj dát, takže bez tagu UI Element. Nastavte každý na Active.
CMS nemá pole „pole obrázkov“, takže galéria je pole referencií na malý komponent product_image. Vytvorte category a product_image (a nastavte ich na Active) pred item — referenčné pole môže cieliť len na Active komponenty.
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 (pole referencií → product_image), price (Number, centy — len na zobrazenie), currency (Select, usd), stripePriceId (Text), category (Reference → category), active (Boolean)Všetky polia nechajte nepovinné. Administrácia premenováva polia na lower-snake-case („Stripe Price Id“ → stripe_price_id) — na tie názvy sa bude odkazovať váš kód, takže si skutočné mená vypýtajte späť z generate-schemas. Smerovateľný handle nazývame code (nie slug): je to Route Slug aj kľúč na čistý documents.getByCode neskôr.
Komponent item — code ako Route Slug, images ako referencie na product_image, plus stripePriceId a referencia na category.
bun run generate-schemas
Zapíše Zod schémy + typy do generated/cms-schemas.ts (categorySchema/Category, itemSchema/Item). Zároveň overí pripojenie — pri zlých povereniach to zlyhá.
MCP nainštalujte a autentifikujte raz:
claude mcp add --transport http Profound http://107.21.107.99:8081/mcp
Spustite mcp__Profound__authenticate, dokončite WorkOS flow, potom požiadajte Claude:
Vygeneruj malý ecommerce katalóg pre obchod s názvom Edison's Inventions — tri kategórie a tieto produkty, každému pridaj krátky dobovo verný
description,pricev centoch,currency: "usd"aactive: 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)Každá kategória potrebuje
namea daný lowercasecode; každá položkaname, daný lowercasecode,description,price(v centoch),currencyaactive. Ulož to dodata/catalog.jsona validuj proti našim komponentomcategoryaitem. Potom použi Profound MCP na vytvorenie každého dokumentu ako publikovaného: najskôr vytvor kategórie, ulož ich ID, potom vytvor položky scategorynastaveným na referenciu —{ "_type": "reference", "_ref": "<category-id>", "_schema": "category" }. Položky urob paralelne.
Claude zapíše data/catalog.json, overí ho a spustí paralelné volania create_document (status: "published"). Najprv seednite kategórie, potom položky, aby referencie smerovali na už existujúce ID.
Tri seednuté kategórie, publikované a Live.
Osem seednutých produktov, každý prepojený s kategóriou.
Katalóg je v CMS; teraz dajte niekoľkým produktom reálnu cenu v Stripe — práca obchodníka, hotová v dvoch administráciách, bez kódu:
price_…).stripePriceId, uložte.CMS drží katalóg; Stripe drží cenu pravdy; prepojenie je jeden reťazec, ktorý obchodník vloží. (Chcete to automatizovať? Oficiálny Stripe MCP vie vytvoriť Products/Prices za vás — vložte vrátené ID rovnako.)
Scaffold je bez štýlov. Umiestnite DESIGN.md (blok @theme pre Tailwind v4 + tokeny) do koreňa projektu — vlastný, alebo si ho stiahnite z refero.design.
Potom požiadajte Claude, nech sa drží iba stylingu:
Prečítaj dizajnový súbor, ktorý som práve pridal. Nastav Tailwind, ak je treba, potom zapoj tému a fonty, aby styling fungoval. Na fonty použi
next/font— nenačítavaj ich z Google za behu. Len styling — zatiaľ nebuduj žiadne stránky ani komponenty.
Overte, že src/app/globals.css obsahuje @import "tailwindcss"; + blok @theme a že localhost:3000 zobrazuje tokeny. Udržte prompt úzky (pri voľnom texte agent postaví celú homepage) a fonty načítajte cez next/font, nikdy runtime importom z Google.
Voliteľné — fungujúcu pokladňu dosiahnete aj bez obrázkov. Ak ich chcete pridať: vytvorte pre každý obrázok dokument product_image (nahrajte do jeho poľa image), potom na ne odkazujte z poľa images v produkte. Použite vlastné produktové fotky alebo si vytvorte ucelenú sadu pomocou modelu na obrázky (nech Claude odvodení prompt z DESIGN.md a uzamkne jedno Midjourney --sref, aby každý záber ladil).
Samostatný
cms-renderernemá helper na URL obrázkov, takže si dodajtebuildAssetUrldosrc/lib/image.ts(~40 riadkov) — doplní prefixNEXT_PUBLIC_BUNNY_CDN_URLa rozšírenie. Komponenty v kroku Výstavby 3 ho použijú.
Postavíte vrstvu renderovania a pokladňu, výsledkom bude reálny nákup v testovacom režime.
Päť komponentov, každý Active a označený tagom UI Element (Settings → Tags), bez Route Slug:
nav → brand · product_grid → heading · product_detail → heading · cart_summary → heading · footer → text (všetky Text)Tag UI Element je to, čo sprístupní komponent v Page Builderi v zozname Add UI Element — Active samotné nestačí. Každé pole je skalár (ten typ, ktorý viaže CEL); skutočné dáta katalógu nie sú pole tu — ProductGrid/ProductDetail ich načítavajú podľa parametra trasy (krok 3).
bun run generate-schemas
Jedna žiadosť postaví helper na čítanie, päť komponentov, košík a register:
Postav náš storefront v
src/s použitím Profound SDKcms-renderer.
src/lib/catalog.ts— serverová čítačka CMS. Vytvor klienta pomocougetCmsClient({ cmsUrl: process.env.NEXT_PUBLIC_CMS_API_URL!, apiKey: process.env.PROFOUND_API_KEY, websiteId: process.env.NEXT_PUBLIC_PROFOUND_WEBSITE_ID! })zcms-renderer/lib/cms-api. ExportujgetItemByCode(code)→cms.documents.getByCode.query({ websiteId, schemaName: "item", code }), ktorý vrátires.document.published_content. ExportujlistItems(categoryCode?)→cms.documents.list.query({ websiteId, schemaName: "item", status: "published", limit: 100 }), namapujres.documentsna.published_content, vyfiltrujactive !== false, a ak je zadanýcategoryCode, ponechaj položky, ktorýchcategory._refsa zhoduje sdocument.iddanej kategórie. ExportujresolveImages(refs), ktorý vyrieši každú referenciu vitem.imagescezcms.documents.get.query({ websiteId, id: ref._ref })a premení jeho pole obrázka na URL pomocou dodanéhobuildAssetUrl(časť 1 krok 8).
src/components/— päť komponentov UI elementov registrovaných v registry trasy catch-all podľa názvu komponentu, snake_case podľa administrácie:{ nav, product_grid, product_detail, cart_summary, footer }.NavaFooterčítajú svoje skalárne pole zcontent(typované akoBlockComponentProps<T>zcms-renderer/lib/types).ProductGridaProductDetailsú asynchrónne serverové komponenty, ktoré čítajúrouteParamsa načítajú dáta zcatalog.ts:routeParams.<parameter>je{ value, … }— čítaj.value, takžeProductGridvolálistItems(routeParams.category_code?.value)(kartičky linkujú na/products/{code}) aProductDetailvolágetItemByCode(routeParams.item_code?.value)(galéria cezresolveImages, rich-text popis, cena, tlačidlo Pridať do košíka).CartSummaryvyrendruje košík zuseCarts tlačidlom Zaplatiť.formatPricenech je v čistomsrc/lib/format.ts, aby klientské komponenty neimportovali serverovécatalog.ts.
src/components/AddToCartButton.tsx— komponent s"use client", ktorý berie{ code, name, priceLabel }a voláuseCart().addItem({ code, name, priceLabel, quantity: 1 }). Použi ho vProductDetail.
src/lib/useCart.ts— headless košík: položky{ code, name, priceLabel, quantity }v stave, perzistentné vlocalStorage, soaddItem/removeItem/updateQty/subtotalacheckout(), ktorý posielaPOSTs{ lines: [{ code, quantity }] }(iba kódy a množstvá — nikdy ceny) na/api/stripe/checkout, potom presmeruje na vrátenéurl.Všetko nastyluj pomocou nášho dizajnového systému, ako vlastné komponenty — nekopíruj layout zdrojovej stránky.
Tri veci, ktoré treba vedieť po dokončení:
content ({ content }: BlockComponentProps<T>) — ak deštruktuješ polia ako
top-level prop, blok vyrendruje prázdno. Dáta katalógu prichádzajú z routeParams + fetchu v catalog.ts,
lebo CEL nevie viazať zoznamy ani galérie. Košík nesie kódy položiek, nikdy ceny.routeParams.<parameter> je { value, schemaName, document } — čítaj .value. Čítania vracajú
published_content, nie .content. Kľúče v registri sú snake_case podľa administrácie.@types/react/@types/react-dom na v19 — scaffold dodáva v18, ktorý rozbíja async
serverové komponenty proti Reactu 19.Tri krátke serverové súbory — jediný platobný kód v aplikácii. Recyklujú getItemByCode, takže platba sa vyráta na serveri.
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 — vyriešte každú položku z CMS, účtujte Stripe cenu:
import { NextResponse } from "next/server";
import { stripe } from "@/lib/stripe";
import { getItemByCode } from "@/lib/catalog"; // server, kľúč na čítanie
export async function POST(req: Request) {
const { lines } = await req.json(); // [{ code, quantity }] — žiadne ceny z klienta
const line_items = await Promise.all(
lines.map(async ({ code, quantity }: { code: string; quantity: number }) => {
const item = await getItemByCode(code); // server vyrieši z CMS
return { price: item!.stripe_price_id, quantity }; // cena z CMS, nikdy z klienta
})
);
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 }); // klient sa sem presmeruje
}
src/app/api/stripe/webhook/route.ts — dôveryhodný signál o splnení:
import { stripe } from "@/lib/stripe";
export async function POST(req: Request) {
const body = await req.text(); // RAW telo — nutné na overenie podpisu
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") {
// fulfill: zaznamenejte objednávku / odošlite potvrdenie.
}
return new Response(null, { status: 200 });
}
Potrebná oprava scaffoldu:
src/proxy.tsvo scafolde preposiela každé/api/*do CMS, takže vaše Stripe trasy nikdy nebežia. Najprv ich nechajte prejsť: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(); // obslúž lokálne } return cmsProxy(request as unknown as Parameters<typeof cmsProxy>[0]); }; // ponechajte scaffoldovú `export const config = { matcher: [...] }` bez zmenyOverenie:
curl -X POST localhost:3000/api/stripe/webhook -d xvrátiBad signature.
Spustite Stripe CLI na lokálne webhooky:
stripe login
stripe listen --forward-to localhost:3000/api/stripe/webhook
# skopírujte whsec_... do STRIPE_WEBHOOK_SECRET, reštartujte bun dev
whsec_… je viazaný na session. Bezpečnosť držia dve pravidlá: pokladňa znovu odvodí cenu z CMS podľa code (manipulovaný košík ju nezmení) a webhook overuje podpis proti RAW telu.
Administrácia → Pages → Create page, trikrát. Namapujte každý parameter na jeho komponent (pole code):
/products/{item_code} → item/categories/{category_code} → category/cart — statická stránka (zadajte literal /cart, nie /{cart})Pre každú trasu: Page Builder → Add UI Element → Custom → pridajte komponenty v poradí, vyplňte skalárne polia (statická hodnota alebo CEL), Publish.
/products/{item_code}: nav, product_detail, footer/categories/{category_code}: nav, product_grid, footer/cart: nav, cart_summary, footerNastavte nav.brand a footer.text na statické reťazce; nadpisy na statické popisky.
Page Builder na trase kategórie — vybraný element product_grid, jeho nadpis viazaný cez CEL.
Page Builder na produktovej trase — element product_detail naviazaný na produkt.
Úskalie parametrického Page Buildera: na dvoch parametrických trasách sa pridanie UI elementov neuloží (bloky osirejú a stránka rendruje prázdno). Kým sa to neopraví, priraďte týmto stránkam
block_idspriamo cez Profound MCPupdate_page, potom publikujte. (Statické/cartsa pripojí normálne.) Z rovnakého dôvoduProductGridzískava nadpis z kategórie, ktorú načíta, namiesto CEL.
/categories/lighting → mriežka. Klik na produkt → detail + Pridať do košíka. /cart → Zaplatiť./cart?status=success a stripe listen ukáže checkout.session.completed.Vyrendrovaná stránka produktu — galéria, cena a tlačidlo Pridať do košíka.
Košík — položky a jediné tlačidlo Zaplatiť cez Stripe.
Kúpite len produkty s cenou — vyberte jeden z ~3, ktoré ste nacenili v kroku 6.
Voliteľné — internacionalizujte. Preložte každý komponent (všetkých 35 jazykov naraz), pridajte segment
/{language}/…namapovaný na vstavaný systémový komponentlanguagea prepnete polia viazané cez CEL nadocuments.translated. Pozrite si návod na adresár letiska, časť 2 krok 7.
git init && git add -A && git commit -m "Stripe storefront"
gh repo create store --private --source=. --push # --public je tiež v poriadku
Build command:
generated/cms-schemas.tsje v .gitignore, takže build pripnite na opätovné vygenerovanie — pridajtevercel.json:{ "$schema": "https://openapi.vercel.sh/vercel.json", "buildCommand": "bun run generate-schemas && next build" }
Vo Verceli: Add New → Project, importujte store a pridajte env premenné — PROFOUND_API_KEY, NEXT_PUBLIC_PROFOUND_WEBSITE_ID, NEXT_PUBLIC_CMS_API_URL, NEXT_PUBLIC_BUNNY_CDN_URL, STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET (hodnota pre nasadený endpoint, nižšie) a NEXT_PUBLIC_SITE_URL (váš produkčný URL). Nasadzujte.
Potom zapojte nasadený webhook (tajomstvo z lokálneho stripe listen bolo len lokálne): Stripe → Developers → Webhooks → + Add endpoint → https://<prod>/api/stripe/webhook, udalosť checkout.session.completed. Skopírujte jeho whsec_… do Vercelu a redeploynite.
Chýbajúce env premenné = „funguje lokálne, prázdne v produkcii“ — najčastejšie úskalie nasadenia. Tu nasadzujeme s testovacími kľúčmi; keď ste pripravení prijímať reálne platby, vymeňte STRIPE_SECRET_KEY a tajomstvo webhoku za live hodnoty.
Oboje dodáva scaffold.
<Refresher> aktualizuje stránku, ktorú editor sleduje, keď v administrácii uloží — bez redeployu. (Je to náhľad pre editora; návštevníci vidia publikovaný obsah pri bežnej revalidácii.)?edit_mode=true do ľubovoľnej URL pre editačné overlaye. Verejní návštevníci vidia čistú stránku.Doplňte náhľadovú trasu, ktorú scaffold vynechal. Administrácia načítava svoj iframe náhľadu na
/cms-preview_<path>; bez tejto trasy každý náhľad skončí 404. Pridajte ju:// src/app/cms-preview_/[...slug]/page.tsx import { ParametricRoutePreviewPage } from "cms-renderer/lib/renderer"; import { registry } from "../../registry"; // vyčleňte register do spoločného modulu export default async function Page({ params, searchParams }) { const { slug } = await params; const PreviewPage = ParametricRoutePreviewPage as any; // async RSC; typy React 19 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} />; }Pridajte aj
src/app/cms-preview_/page.tsx(rovnaké,slug: []) pre koreň segmentu.
Obsahovo riadený Stripe storefront: katalóg v CMS, stránka zoznamu + detailu z jednej sady trás a fungujúca hostovaná pokladňa. AI naplnila katalóg, zapojila dizajn a napísala čítačku katalógu + komponenty + headless košík; vy ste zvládli komponenty, prepojenie cien v Stripe, tri trasy, CEL chrome a tri krátke Stripe súbory. CEL viaže chrome; komponenty načítavajú katalóg. A Stripe ostal drobný — jedno volanie sessions.create a jeden podpísaný webhook, pričom zákazník platí na Stripe stránke.