מדריך מעשי: בנו חנות Stripe מונעת-תוכן על Profound CMS — קטלוג שסוחר עורך ללא קוד, שני נתיבים פרמטריים, עגלה Headless וצ'ק-אאוט שמתארח ב-Stripe.
החנות המוגמרת בפעולה — עיינו בקטגוריה, פתחו מוצר, הוסיפו לעגלה, בצעו צ'ק-אאוט.
מדריך מעשי שבונה חנות מונחית-תוכן על Profound CMS: קטלוג מוצרים (קטגוריות + פריטים) מדולל ב-CMS, עמודי רשימה ופרטי מאותה קבוצת נתיבי ניווט, וצ'ק-אאוט מתארח ב-Stripe שנשלח כרכיב Headless.
עמוד השדרה הוא Next.js שנכתב ביד וכן הממשק של Profound. Claude Code (באמצעות ה-Profound MCP) מבצע את עיקר העבודה בשלושה תפקידים — זריעת הקטלוג, חיבור מערכת העיצוב וכתיבת רכיבי החנות (כולל העגלה ה-Headless). שלושה חלקים: הגדרה, בנייה, פרודקשן.
תשלומים בשורה אחת. אנחנו משתמשים ב-Stripe-hosted Checkout: הלקוח משלם בדף של Stripe, לא שלכם. האפליקציה שלכם עושה רק שני דברים בצד השרת — יוצרת סשן Checkout ומוודאת webhook אחד. אין שדות כרטיס, אין Stripe Elements, אין נטל PCI.
/products/{item_code}, /categories/{category_code}) ועוד /cart סטטי, כולם מאותה קבוצת רכיבים.useCart) וצ'ק-אאוט שמתארח ב-Stripe, כשמחיר המוצר נפתר תמיד בצד השרת מתוך מזהה מחיר של Stripe.curl -fsSL https://bun.sh/install | bashstripe login) עבור webhooks מקומיים.gh) וחשבון Vercel המחובר ל-GitHub.Profound מפריד בין תוכן להצגה:
category, item); רכיב שמתוייג כ-UI Element ניתן להצבה בעמוד (nav, product_grid, …).meta.params.* ב-CEL, routeParams ב-React).cms-renderer; Stripe מתווספת כנתיבי API רגילים.הכלל היחיד שמעצב את הבנייה: CEL נקשר רק לשדות מסוג string/number. לכן אלמנטים סקלריים (לוגו בניווט, פוטר, כותרות) נקשרים עם CEL, בעוד שכל דבר עשיר או אוסף (רשת מוצרים, גלריית תמונות, טקסט עשיר) נשלף בתוך רכיב ה-React לפי פרמטר הנתיב. ו-Stripe היא מקור האמת לתמחור — השדה price ב-CMS הוא להצגה בלבד; החיוב תמיד נפתר בצד השרת מתוך מזהה מחיר של Stripe.
מצב סיום: קטלוג קטן שפורסם, האפליקציה מחוברת לקרוא אותו, Stripe מותקן, העיצוב במקומו — עדיין שום דבר לא מוצג.
הירשמו ל-Profound (אימות WorkOS). צרו אתר בשם store, ואז העתיקו את מזהה האתר שלו (ה-UUID בכתובת ה-URL של האדמין) ואת מפתח ה-API ברמת קריאה (Deployments → Create API key). האפליקציה רק קוראת; זריעת הקטלוג מאוחר יותר תעשה דרך ה-MCP, שמאמת בנפרד.
bunx create-profound-next store
cd store
bun add stripe
השלד הוא פרויקט Next.js App Router שמוכן מראש ל-Profound (SDK של cms-renderer, נתיב catch-all, סקריפט generate-schemas, <Refresher>). הוא אינו מגיע עם סגנון.
bun add stripe מושך את ה-SDK של השרת — התלות היחידה שתהליך צ'ק-אאוט מתארח דורש.
הוסיפו את הערכים שלכם ל-.env.local:
# CMS
PROFOUND_API_KEY=<your read key>
NEXT_PUBLIC_PROFOUND_WEBSITE_ID=<your website id>
NEXT_PUBLIC_CMS_API_URL=https://cms.dev.tryprofound.com
NEXT_PUBLIC_BUNNY_CDN_URL=https://cms-profound.b-cdn.net # משרת תמונות המתארחות ב-CMS
# Stripe
STRIPE_SECRET_KEY=sk_test_... # מפתח בדיקה כאן; החליפו למפתח החי כשתעלו לאוויר
STRIPE_WEBHOOK_SECRET=whsec_... # מתמלא בשלב הבנייה 4
NEXT_PUBLIC_SITE_URL=http://localhost:3000
קחו את STRIPE_SECRET_KEY מתוך Stripe → Developers → API keys. אנחנו משתמשים במפתח בדיקה (sk_test_…) כדי שהבנייה לא תעביר כסף אמיתי; החליפו למפתח החי כשהבאתם מוכנים לקבל תשלומים אמיתיים. הריצו bun dev ופתחו localhost:3000 — השלד מוצג.
צ'ק-אאוט מתארח מפנה את הדפדפן ל-URL של Stripe, לכן מפתח השרת הסודי הוא כל מה ש-Stripe צריך — אין מפתח פומבי, אין SDK קליינט של Stripe.
category, product_image ו-itemצרו שלושה Custom Components (Components → Create new component) — מקור הנתונים, לכן בלי תג UI Element. קבעו כל אחד ל-Active.
ל-CMS אין שדה "מערך תמונות", לכן הגלריה היא מערך הפניות לרכיב קטן product_image. צרו category ו-product_image (וקבעו אותם ל-Active) לפני item — שדה הפניה יכול לכוון רק לרכיבים פעילים.
category — name (טקסט), code (טקסט, Route Slug), description (טקסט עשיר), heroImage (תמונה)product_image — image (תמונה)item — name (טקסט), code (טקסט, Route Slug), description (טקסט עשיר), images (מערך הפניות → product_image), price (מספר, בסנטים — להצגה בלבד), currency (Select, usd), stripePriceId (טקסט), category (Reference → category), active (בוליאני)השאירו את כל השדות כלא חובה. האדמין מתרגם את שמות השדות ל-snake_case תחתון ("Stripe Price Id" →
stripe_price_id) — זה מה שהקוד שלכם ישתמש בו, לכן קראו את השמות האמיתיים בחזרה מ-generate-schemas בהמשך. אנו משתמשים ב-code (לא slug) כידית הניתוב: זהו Route Slug וגם המפתח לשליפת documents.getByCode נקייה מאוחר יותר.
רכיב item — code כ-Route Slug, images כהפניות ל-product_image, יחד עם stripePriceId והפניה ל-category.
bun run generate-schemas
כותב סכמות Zod + טיפוסים ל-generated/cms-schemas.ts (categorySchema/Category,
itemSchema/Item). משמש גם כבדיקת חיבור — פרטי התחברות שגויים ייכשלו כאן.
התקינו ואמתו את ה-MCP פעם אחת:
claude mcp add --transport http Profound http://107.21.107.99:8081/mcp
הריצו mcp__Profound__authenticate, השלימו את זרימת WorkOS, ואז בקשו מ-Claude:
צור קטלוג מסחר קטן לחנות בשם Edison's Inventions — שלוש קטגוריות והמוצרים האלה, עם
descriptionקצר ומדויק לתקופה לכל אחד,priceבסנטים,currency: "usd", ו-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)לכל קטגוריה דרושים
nameו-codeבאותיות קטנות; לכל פריט דרושיםname, אותוcodeבאותיות קטנות,description,price(בסנטים),currency, ו-active. שמור את זה ב-data/catalog.jsonואמת מול רכיביcategoryו-itemשלנו. לאחר מכן השתמש ב-Profound MCP כדי ליצור כל אחד כמסמך שפורסם: צור תחילה את הקטגוריות, קלט את המזהים שלהן, ואז צור את הפריטים עםcategoryשהוגדר כהפניה —{ "_type": "reference", "_ref": "<category-id>", "_schema": "category" }. השאר אתstripePriceIdריק לעת עתה. בצע את יצירת הפריטים במקביל.
Claude כותב את data/catalog.json, מאמת אותו, ומפרס מקבילית של קריאות create_document (status: "published"). זריעו קטגוריות לפני פריטים כך שההפניות יצביעו על מזהים קיימים.
שלוש הקטגוריות שנזרעו, פורסמו וחיות.
שמונה המוצרים שנזרעו, כל אחד מקושר לקטגוריה.
הקטלוג נמצא ב-CMS; עכשיו תנו למספר מוצרים מחיר אמיתי ב-Stripe — העבודה של הסוחר, נעשית בשני ממשקי ניהול, ללא קוד:
price_…).stripePriceId, שמרו.ה-CMS מחזיק את הקטלוג; Stripe מחזיק את המחיר הרשמי; הקישור הוא מחרוזת אחת שהסוחר מדביק. (מעדיפים לאוטומט? Stripe MCP הרשמי יכול ליצור עבורכם את המוצרים/מחירים — הדביקו את המזהים שהוחזרו באותה צורה.)
השלד מגיע ללא עיצוב. הציבו DESIGN.md (בלוק @theme של Tailwind v4 + טוקנים) בשורש הפרויקט — שלכם או כזה שהורדתם מ-refero.design.
לאחר מכן בקשו מ-Claude, בתחום הסגנון בלבד:
קרא את קובץ העיצוב שהוספתי. הגדר Tailwind אם צריך, ואז חבר את התמה והגופנים כך שהסגנון יעבוד. השתמש ב-
next/fontעבור גופנים — אל תטען אותם מ-Google בזמן ריצה. רק הסגנון — אל תבנה עדיין עמודים או רכיבים.
ודאו של-src/app/globals.css יש @import "tailwindcss"; + בלוק @theme וש-localhost:3000 מציג את הטוקנים. שמרו על הנחיה ממוקדת (אם פתוחה מדי, סוכן יוצר עמוד בית שלם), וטענו גופנים דרך next/font, לעולם לא יבוא ריצה מגוגל.
רשות — ניתן להגיע לצ'ק-אאוט עובד בלי תמונות. כדי להוסיף אותן: צרו מסמך product_image לכל תמונה (העלו אל שדה image שלו), ואז הפנו אליהם ממערך images של המוצר. הביאו צילום מוצר משלכם, או צרו סדרה קוהרנטית עם מודל תמונה (תנו ל-Claude להפיק הנחיות מותאמות-מותג מתוך DESIGN.md ונעלו --sref אחד של Midjourney כדי שכל צילום יתאים).
ל-
cms-rendererהעצמאי אין עזר לכתובת תמונה, לכן הוסיפו פונקצייתbuildAssetUrlל-src/lib/image.ts(~40 שורות) — היא מוסיפה קידומתNEXT_PUBLIC_BUNNY_CDN_URLומוסיפה את הסיומת. הרכיבים בשלב הבנייה 3 משתמשים בה.
בנו את שכבת ההצגה ואת הצ'ק-אאוט, וסיימו ברכישה אמיתית במצב בדיקה.
חמישה רכיבים, כל אחד Active ומתויג כ-UI Element (Settings → Tags), ללא Route Slug:
nav → brand · product_grid → heading · product_detail → heading ·
cart_summary → heading · footer → text (כולם טקסט)תג UI Element הוא מה שמאפשר לרכיב להופיע ברשימת Add UI Element של Page Builder — Active לבדו לא מספיק. כל שדה הוא סקלרי (הסוג ש-CEL נקשר אליו); נתוני הקטלוג עצמם אינם שדה כאן — ProductGrid/ProductDetail משיגים אותם לפי פרמטר נתיב (שלב 3).
bun run generate-schemas
הנחיה אחת בונה את עזר הקריאה, חמשת הרכיבים, העגלה והרישום:
בנה את החנות שלנו בתוך
src/, באמצעות SDK של Profoundcms-renderer.
src/lib/catalog.ts— קורא CMS בצד השרת. צור קליינט עםgetCmsClient({ cmsUrl: process.env.NEXT_PUBLIC_CMS_API_URL!, apiKey: process.env.PROFOUND_API_KEY, websiteId: process.env.NEXT_PUBLIC_PROFOUND_WEBSITE_ID! })מתוךcms-renderer/lib/cms-api. ייצאgetItemByCode(code)→cms.documents.getByCode.query({ websiteId, schemaName: "item", code })המחזיר אתres.document.published_content. ייצאlistItems(categoryCode?)→cms.documents.list.query({ websiteId, schemaName: "item", status: "published", limit: 100 }), ממפה אתres.documentsל-.published_content, מסנןactive !== false, ואםcategoryCodeניתן — שומר פריטים שה-category._refשלהם שווה למזהה המסמך של הקטגוריה. ייצאresolveImages(refs)שמפתר כל הפניה ב-item.imagesדרךcms.documents.get.query({ websiteId, id: ref._ref })והופך את שדה התמונה ל-URL עםbuildAssetUrlשהוספנו (חלק 1 שלב 8).
src/components/— חמישה רכיבי UI רשומים ברישום של הנתיב ה-catch-all לפי שם הרכיב, ב-snake_case המתאים לאדמין:{ nav, product_grid, product_detail, cart_summary, footer }.Navו-Footerקוראים את השדה הסקלרי דרך ה-propcontent(מטיפוסBlockComponentProps<T>מתוךcms-renderer/lib/types).ProductGridו-ProductDetailהם רכיבי שרת אסינכרוניים שקוראים אתrouteParamsומשיגים מתוךcatalog.ts:routeParams.<param>הוא{ value, … }— קראו את.value, כך ש-ProductGridקוראlistItems(routeParams.category_code?.value)(קלפים מקשרים ל-/products/{code}) ו-ProductDetailקוראgetItemByCode(routeParams.item_code?.value)(גלריה דרךresolveImages, תיאור טקסט עשיר, מחיר, כפתור הוספה לעגלה).CartSummaryמציג את העגלה מ-useCartעם כפתור תשלום. השאירו אתformatPriceבקובץ טהורsrc/lib/format.tsכדי שרכיבי קליינט לא ייבאו אתcatalog.tsששייך לשרת.
src/components/AddToCartButton.tsx— כפתור"use client"שמקבל{ code, name, priceLabel }וקוראuseCart().addItem({ code, name, priceLabel, quantity: 1 }). השתמשו בו בתוךProductDetail.
src/lib/useCart.ts— עגלה Headless: פריטי שורה{ code, name, priceLabel, quantity }במצב, נשמרים ב-localStorage, חושפיםaddItem/removeItem/updateQty/subtotalו-checkout()ששולח POST עם{ lines: [{ code, quantity }] }(קודים וכמויות בלבד — לעולם לא מחירים) ל-/api/stripe/checkout, ואז מפנה ל-urlשהוחזר.עצבו הכול עם מערכת העיצוב שלנו, כרכיבים משלנו — אל תעתיקו את פריסת אתר המקור.
שלושה דברים לדעת לאחר שזה רץ:
content ({ content }: BlockComponentProps<T>) — פרקו שדות כ-props ברמת העל ובלוק לא יתציג. נתוני הקטלוג מגיעים מ-routeParams + שליפה מתוך catalog.ts לפי פרמטר, כי CEL לא נקשר לרשימות או גלריות. העגלה מחזיקה קודי פריטים בלבד, לעולם לא מחירים.routeParams.<param> הוא { value, schemaName, document } — קראו .value. שליפות מחזירות את
published_content, לא .content. מפתחות הרישום הם ב-snake_case כדי להתאים לאדמין.@types/react/@types/react-dom ל-v19 — השלד מגיע עם v18, מה ששובר רכיבי שרת אסינכרוניים מול React 19.שלושה קבצי שרת קצרים — קוד התשלומים היחיד באפליקציה. הם עושים שימוש חוזר ב-getItemByCode, כך שהחיוב נפתר בצד השרת.
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 — פתרו כל פריט מה-CMS, גבו את המחיר של Stripe:
import { NextResponse } from "next/server";
import { stripe } from "@/lib/stripe";
import { getItemByCode } from "@/lib/catalog"; // בצד השרת, מפתח ברמת קריאה
export async function POST(req: Request) {
const { lines } = await req.json(); // [{ code, quantity }] — אין מחירים מהלקוח
const line_items = await Promise.all(
lines.map(async ({ code, quantity }: { code: string; quantity: number }) => {
const item = await getItemByCode(code); // השרת פותר מתוך ה-CMS
return { price: item!.stripe_price_id, quantity }; // מחיר מתוך ה-CMS, אף פעם לא מהלקוח
})
);
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 }); // הלקוח מופנה לכאן
}
src/app/api/stripe/webhook/route.ts — אות מילוי מהימן:
import { stripe } from "@/lib/stripe";
export async function POST(req: Request) {
const body = await req.text(); // גוף RAW — נדרש לאימות החתימה
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") {
// מילוי: תעדו את ההזמנה / שלחו קבלה.
}
return new Response(null, { status: 200 });
}
תיקון חובה לשלד:
src/proxy.tsשל השלד מעביר כל/api/*ל-CMS, כך שנתיבי Stripe שלכם לא יפעלו. אפשרו להם לעבור קודם: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(); // טופלו מקומית } return cmsProxy(request as unknown as Parameters<typeof cmsProxy>[0]); }; // השאירו את `export const config = { matcher: [...] }` של השלד ללא שינויודאו:
curl -X POST localhost:3000/api/stripe/webhook -d xמחזירBad signature.
הריצו את Stripe CLI עבור webhooks מקומיים:
stripe login
stripe listen --forward-to localhost:3000/api/stripe/webhook
# העתיקו את whsec_... אל STRIPE_WEBHOOK_SECRET, אתחלו מחדש את bun dev
ה-whsec_… הוא לכל סשן. שני כללים מגנים על האבטחה: checkout מחדש פותר את המחיר מה-CMS לפי code (עגלה מזויפת לא יכולה לשנותו), וה-webhook מאמת את החתימה מול הגוף הגולמי.
אדמין → Pages → Create page, שלוש פעמים. מיפו כל פרמטר לרכיב שלו (שדה slug code):
/products/{item_code} → item/categories/{category_code} → category/cart — עמוד סטטי (הזינו את /cart כערך מילולי, לא /{cart})לכל נתיב: Page Builder → Add UI Element → Custom → הוסיפו רכיבים בסדר, מלאו שדות סקלריים (ערך סטטי או CEL), פרסמו.
/products/{item_code}: nav, product_detail, footer/categories/{category_code}: nav, product_grid, footer/cart: nav, cart_summary, footerקבעו nav.brand ו-footer.text למחרוזות סטטיות; כותרות לתוויות סטטיות.
Page Builder בנתיב הקטגוריה — רכיב product_grid נבחר, הכותרת שלו קשורה ב-CEL.
Page Builder בנתיב המוצר — רכיב product_detail על קשירת מוצר.
מוקש Page Builder פרמטרי: בשני הנתיבים הפרמטריים, הוספת רכיבי UI אינה נשמרת (בלוקים יתייתמו והעמוד יוצג ריק). עד שזה יתוקן, חברו את
block_idsשל העמודים האלה ישירות דרך Profound MCPupdate_page, ואז פרסמו. (העמוד הסטטי/cartמתחבר כרגיל.) מאותה סיבה,ProductGridמפיק את הכותרת מהקטגוריה שהוא משיג במקום דרך CEL.
/categories/lighting → הגריד. לחצו על מוצר → פירוט + הוספה לעגלה. /cart → תשלום./cart?status=success, ו-stripe listen יציג checkout.session.completed.עמוד מוצר מוצג — גלריה, מחיר וכפתור הוספה לעגלה.
העגלה — פריטי שורה וכפתור תשלום יחיד עם Stripe.
רק פריטים שמחירם הוגדר ניתנים לרכישה — קנו אחד משלושת הפריטים שהגדרתם בשלב 6.
אופציונלי — בינאום. תרגמו כל רכיב (כל 35 השפות בבת אחת), הוסיפו מקטע
/{language}/…שממופה לרכיב המערכת המובנהlanguage, והחליפו שדות הקשורים ב-CEL ל-documents.translated. ראו את מדריך ספר הטלפונים בשדה התעופה, חלק 2 שלב 7.
git init && git add -A && git commit -m "Stripe storefront"
gh repo create store --private --source=. --push # --public גם בסדר
פקודת Build:
generated/cms-schemas.tsאינו נכלל ב-git, לכן הצמידו את הבילד כך שייווצר מחדש — הוסיפוvercel.json:{ "$schema": "https://openapi.vercel.sh/vercel.json", "buildCommand": "bun run generate-schemas && next build" }
ב-Vercel: Add New → Project, ייבאו את store, והוסיפו את משתני הסביבה — PROFOUND_API_KEY,
NEXT_PUBLIC_PROFOUND_WEBSITE_ID, NEXT_PUBLIC_CMS_API_URL, NEXT_PUBLIC_BUNNY_CDN_URL,
STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET (הערך של נקודה קצה בפרודקשן, להלן), ו-
NEXT_PUBLIC_SITE_URL (כתובת הפרודקט שלכם). פרסו.
לאחר מכן חברו את ה-webhook המשוגר (סוד ה-stripe listen המקומי היה רק לוקאלי): Stripe
→ Developers → Webhooks → + Add endpoint → https://<prod>/api/stripe/webhook, אירוע
checkout.session.completed. העתיקו את ה-whsec_… ל-Vercel ופרסו מחדש.
משתני סביבה חסרים = "עובד מקומית, ריק בפרודקשן" — מלכודת הפריסה מספר 1. אנחנו מפרסים עם מפתחות בדיקה כאן; החליפו STRIPE_SECRET_KEY וסוד ה-webhook לערכי חי כשאתם מוכנים לקבל תשלומים אמיתיים.
שניהם מגיעים עם השלד.
<Refresher> מעדכן את העמוד שאתם מציגים כשעורך שומר באדמין — בלי פריסה מחדש. (זו תצוגה מקדימה לעורך; מבקרים רואים תוכן שפורסם עם ריענון רגיל.)?edit_mode=true לכל URL לקבלת שכבות עריכה. מבקרים רגילים מקבלים את העמוד הנקי.הוסיפו את נתיב התצוגה המקדימה שהשלד השמיט. האדמין טוען את iframe התצוגה המקדימה שלו ב-
/cms-preview_<path>; ללא הנתיב הזה כל תצוגה מקדימה תחזיר 404. הוסיפו אותו:// src/app/cms-preview_/[...slug]/page.tsx import { ParametricRoutePreviewPage } from "cms-renderer/lib/renderer"; import { registry } from "../../registry"; // חלצו את הרישום למודול משותף export default async function Page({ params, searchParams }) { const { slug } = await params; const PreviewPage = ParametricRoutePreviewPage as any; // רכיב שרת אסינכרוני; טיפוסי 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} />; }הוסיפו גם את
src/app/cms-preview_/page.tsx(אותו הדבר,slug: []) עבור שורש הסגמנט.
חנות Stripe מונעת-תוכן: קטלוג ב-CMS, עמודי רשימה + פרטים מאותה קבוצת נתיבים, וצ'ק-אאוט מתארח שעובד. ה-AI זרעה את הקטלוג, חיברה את העיצוב וכתבה את קורא הקטלוג + הרכיבים + העגלה ה-Headless; אתם ביצעתם את הרכיבים, את קישורי המחיר ב-Stripe, את שלושת הנתיבים, ה-Chrome ב-CEL, ושלושה קבצי Stripe קצרים. CEL קושר את האלמנטים הסקלריים; הרכיבים משיגים את הקטלוג. ו-Stripe נשאר קטן — קריאה אחת ל-sessions.create ו-webhook חתום אחד, כשהלקוח משלם בדף של Stripe עצמו.