profound-logoProfound CMS
⌘K
Admin
Theme
DocsTutorialBlogPhilosophy
DocsTutorialBlogPhilosophy

Tutorials

Build & Ship an Airport DirectoryDeploymentsBuild & Ship a Stripe Storefront

Feature

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

Build & Ship a Stripe Storefront

Praktyczny przewodnik: zbuduj oparty na treści sklep Stripe w Profound CMS — katalog edytowany przez sprzedawcę bez kodu, dwie trasy parametryczne, bezgłowy koszyk i kasę hostowaną przez Stripe.

Gotowy sklep w działaniu — przeglądaj kategorię, otwieraj produkt, dodawaj do koszyka, finalizuj zakup.

Praktyczny przewodnik, w którym budujesz oparty na treści sklep w Profound CMS: katalog produktów (kategorie + pozycje) zamodelowany w CMS-ie, strony listingu i szczegółów z jednej puli tras oraz kasę hostowaną przez Stripe dostarczoną jako bezgłowy komponent.

Trzon to ręcznie pisany Next.js plus panel Profound. Claude Code (za pośrednictwem Profound MCP) wykonuje ciężką pracę w trzech zadaniach — zasila katalog, spina system projektowy i tworzy komponenty witryny (w tym bezgłowy koszyk). Trzy części: Setup, Build, Production.

Płatności w jednej linijce. Korzystamy ze Stripe-hosted Checkout: kupujący płaci na stronie Stripe, nie na Twojej. Twoja aplikacja robi tylko dwie rzeczy po stronie serwera — tworzy sesję Checkout i weryfikuje jeden webhook. Bez pól karty, bez Stripe Elements, bez ciężaru PCI.

Co zbudujesz

  • Niewielki opublikowany katalog w CMS-ie — trzy kategorie i osiem produktów (demo „Edison's Inventions”) — każdy edytowalny przez sprzedawcę bez kodu.
  • Dwie trasy parametryczne (/products/{item_code}, /categories/{category_code}) plus statyczna /cart, wszystkie oparte na jednym zestawie komponentów.
  • Bez głowy koszyk (useCart) oraz kasę hostowaną przez Stripe, przy czym cena jest zawsze rozwiązywana po stronie serwera z identyfikatora Stripe Price.
  • Sklep wdrożony na Vercel z podglądem na żywo i edycją in-place dla zespołu.

Wymagania wstępne

  • Bun ≥ 1.3 — curl -fsSL https://bun.sh/install | bash
  • Claude Code z Profound MCP (część 1 go instaluje).
  • Konto w Profound CMS.
  • Konto w Stripe. Ten samouczek działa w trybie testowym, więc podczas budowy nic nie pobiera prawdziwych pieniędzy — ale przepływ jest identyczny z kluczami produkcyjnymi, więc możesz użyć swoich prawdziwych kluczy, jeśli wolisz. (Tryb testowy nie wymaga danych firmy ani konta bankowego.)
  • Stripe CLI (stripe login) do lokalnych webhooków.
  • Do wdrożenia: GitHub CLI (gh) i konto Vercel połączone z GitHubem.

Jak elementy współgrają

Profound rozdziela treść od renderowania:

  • Komponenty definiują kształt treści. Niestandardowy komponent z polem Route Slug jest routowalny (category, item); komponent oznaczony tagiem UI Element można umieszczać na stronie (nav, product_grid, …).
  • Dokumenty to treść (produkt, kategoria).
  • Elementy UI to sekcje strony; każde pole skalarne przyjmuje wartość statyczną lub wyrażenie CEL, oceniane w momencie renderowania.
  • Trasy parametryczne mapują adres URL na dokument + elementy UI, przekazując parametry trasy (meta.params.* w CEL, routeParams w React).
  • Twoja aplikacja Next.js odczytuje to przez cms-renderer; Stripe jest dodany jako zwykłe trasy API.

Jedyna zasada, która kształtuje budowę: CEL wiąże tylko pola typu string/number. Więc skalarną oprawę (marka w nawigacji, stopka, nagłówki) spinamy z CEL, podczas gdy wszystko, co bogatsze lub kolekcja (siatka produktów, galeria zdjęć, rich text) jest pobierane wewnątrz komponentu React na podstawie parametru trasy. A Stripe jest źródłem prawdy o cenach — price w CMS-ie służy tylko do wyświetlania; obciążenie zawsze rozwiązuje się po stronie serwera na podstawie identyfikatora Stripe Price.

Część 1 — Setup

Stan końcowy: niewielki opublikowany katalog, aplikacja podłączona do jego odczytu, Stripe zainstalowany, design wpięty — nic jeszcze nie jest renderowane.

1. Zarejestruj się i utwórz witrynę

Zarejestruj się w Profound (uwierzytelnianie WorkOS). Utwórz witrynę o nazwie store, a następnie skopiuj jej identyfikator witryny (UUID w adresie panelu) oraz klucz API w trybie odczytu (Deployments → Create API key). Aplikacja tylko odczytuje; późniejsze zasilanie katalogu przechodzi przez MCP, które uwierzytelnia się osobno.

2. Zaszablonuj aplikację, połącz ją i dodaj Stripe

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

Szablon to projekt Next.js App Router wstępnie okablowany dla Profound (cms-renderer SDK, trasa catch-all, skrypt generate-schemas, <Refresher>). Nie zawiera stylowania. bun add stripe pobiera serwerowe SDK — jedyną zależność płatniczą, jakiej potrzebuje hostowana kasa.

Dodaj swoje wartości do .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   # serves CMS-hosted images

# Stripe
STRIPE_SECRET_KEY=sk_test_...            # test key here; swap to your live key when you go live
STRIPE_WEBHOOK_SECRET=whsec_...          # filled in Build step 4
NEXT_PUBLIC_SITE_URL=http://localhost:3000

Pobierz STRIPE_SECRET_KEY ze Stripe → Developers → API keys. Używamy klucza testowego (sk_test_…), więc budowa nigdy nie przenosi prawdziwych pieniędzy; kiedy będziesz gotów przyjmować realne płatności, przełącz się na klucz produkcyjny. Uruchom bun dev i otwórz localhost:3000 — startowy widok się renderuje.

Hostowana kasa przekierowuje przeglądarkę na adres Stripe, więc serwerowy klucz tajny to wszystko, czego Stripe potrzebuje — bez klucza publicznego, bez klientskiego SDK Stripe.

3. Zdefiniuj komponenty category, product_image i item

Utwórz trzy Custom Components (Components → Create new component) — źródło danych, więc bez tagu UI Element. Ustaw każdy jako Active.

CMS nie ma pola „tablica obrazów”, więc galeria to tablica referencji do niewielkiego komponentu product_image. Utwórz category i product_image (i ustaw je jako Active) przed item — pole referencyjne może wskazywać tylko na aktywne 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 (tablica referencji → product_image), price (Number, grosze — tylko do wyświetlania), currency (Select, usd), stripePriceId (Text), category (Reference → category), active (Boolean)

Zostaw wszystkie pola jako opcjonalne. Panel administracyjny zamienia nazwy pól na lower_snake_case („Stripe Price Id” → stripe_price_id) — na nich opiera się Twój kod, więc odczytaj prawdziwe nazwy przez generate-schemas. Trasowy uchwyt nazywamy code (nie slug): to Route Slug i klucz do czystego documents.getByCode później.

Komponent item — code jako Route Slug, images jako referencje do product_image, plus stripePriceId i referencja category.

4. Pobierz komponenty do lokalnych typów

bun run generate-schemas

Zapisuje schematy Zod + typy do generated/cms-schemas.ts (categorySchema/Category, itemSchema/Item). Działa też jako kontrola połączenia — błędne dane logowania spowodują tutaj błąd.

5. Zasil katalog przez Profound MCP

Zainstaluj i uwierzytelnij MCP raz:

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

Uruchom mcp__Profound__authenticate, przejdź przez proces WorkOS, a następnie poproś Claude'a:

Wygeneruj niewielki katalog e-commerce dla sklepu o nazwie Edison's Inventions — trzy kategorie i następujące produkty, z krótkim, osadzonym w epoce description dla każdego, price w groszach, currency: "usd" oraz 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 $)

Każda kategoria potrzebuje name i tego z małych liter code; każda pozycja potrzebuje name, tego z małych liter code, description, price (w centach), currency i active. Zapisz to w data/catalog.json i zweryfikuj względem naszych komponentów category i item. Następnie użyj Profound MCP, aby utworzyć każdy dokument jako opublikowany: najpierw twórz kategorie, zapisz ich identyfikatory, potem twórz produkty z ustawionym category jako referencję — { "_type": "reference", "_ref": "<category-id>", "_schema": "category" }. Pozycje wstawiaj równolegle. stripePriceId pozostaw puste.

Claude zapisuje data/catalog.json, waliduje go i wysyła równoległe wywołania create_document (status: "published"). Zasil kategorie przed produktami, aby referencje wskazywały na istniejące identyfikatory.

Trzy zasilone kategorie, opublikowane i Live.

Osiem zasilonych produktów, każdy powiązany z kategorią.

6. Utwórz ceny w Stripe i połącz kilka produktów

Katalog jest w CMS-ie; teraz nadaj kilku produktom prawdziwą cenę Stripe — zadanie sprzedawcy, wykonane w dwóch panelach, bez kodu:

  1. Stripe Dashboard → Products → + Add product, ustaw jednorazową cenę, skopiuj identyfikator ceny (price_…).
  2. Zrób to dla ~3 kluczowych produktów (np. Lightbulb, Phonograph, Kinetoscope).
  3. Panel Profound → item → Documents → wklej każdy identyfikator ceny do stripePriceId, zapisz.

CMS przechowuje katalog; Stripe przechowuje właściwą cenę; połączeniem jest jeden ciąg, który sprzedawca wkleja. (Wolisz automatyzację? Oficjalny Stripe MCP może utworzyć Products/Prices za Ciebie — skopiuj zwrócone identyfikatory w ten sam sposób.)

7. Dodaj system projektowy i podepnij go z pomocą AI

Szablon nie ma stylowania. Umieść DESIGN.md (blok @theme Tailwinda v4 + tokeny) w katalogu głównym projektu — własny albo pobrany z refero.design. Następnie poproś Claude'a, ograniczając go tylko do stylowania:

Przeczytaj plik design, który właśnie dodałem. Skonfiguruj Tailwinda, jeśli trzeba, a następnie podepnij motyw i fonty, aby stylowanie działało. Użyj next/font do fontów — nie ładuj ich z Google w czasie rzeczywistym. Tylko stylowanie — nie buduj jeszcze żadnych stron ani komponentów.

Sprawdź, czy src/app/globals.css ma @import "tailwindcss"; + blok @theme, oraz czy localhost:3000 prezentuje tokeny. Trzymaj prompt krótko (przy otwartym pytaniu agent potrafi wystawić całą stronę główną) i ładuj fonty przez next/font, nigdy importem Google w runtime.

8. Dodaj zdjęcia produktów (opcjonalnie)

Opcjonalne — do działającej kasy dojdziesz bez zdjęć. Aby je dodać: utwórz dokument product_image na każdy obraz (wgraj plik do pola image), a następnie zreferuj je w tablicy images produktu. Użyj własnych zdjęć albo wygeneruj spójny zestaw modelem graficznym (niech Claude wyprowadzi spójne prompty z DESIGN.md i zablokuj jedno --sref w Midjourney, by każde ujęcie miało tę samą stylistykę).

Samodzielny cms-renderer nie ma helpera do URL obrazów, więc dodaj buildAssetUrl do src/lib/image.ts (~40 linijek) — poprzedza NEXT_PUBLIC_BUNNY_CDN_URL i dodaje rozszerzenie. Komponenty z kroku 3 części Build korzystają z niego.

Część 2 — Build

Zbuduj warstwę renderowania i kasę, kończąc na realnym zakupie w trybie testowym.

1. Zdefiniuj pięć komponentów elementów UI

Pięć komponentów, każdy Active i z tagiem UI Element (Settings → Tags), bez Route Slug:

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

Tag UI Element sprawia, że komponent pojawia się na liście Add UI Element w Page Builderze — samo Active nie wystarczy. Każde pole jest skalarem (tym, który CEL wiąże); właściwe dane katalogu nie są tutaj polem — ProductGrid/ProductDetail pobierają je według parametru trasy (krok 3).

2. Wygeneruj typy ponownie

bun run generate-schemas

3. Wygeneruj czytnik katalogu, komponenty i bezgłowy koszyk

Jedno polecenie tworzy helper do odczytu, pięć komponentów, koszyk i rejestr:

Zbuduj naszą witrynę w src/, używając Profound cms-renderer SDK.

src/lib/catalog.ts — serwerowy czytnik CMS. Utwórz klienta za pomocą getCmsClient({ cmsUrl: process.env.NEXT_PUBLIC_CMS_API_URL!, apiKey: process.env.PROFOUND_API_KEY, websiteId: process.env.NEXT_PUBLIC_PROFOUND_WEBSITE_ID! }) z cms-renderer/lib/cms-api. Wyeksportuj getItemByCode(code) → cms.documents.getByCode.query({ websiteId, schemaName: "item", code }), zwracający res.document.published_content. Wyeksportuj listItems(categoryCode?) → cms.documents.list.query({ websiteId, schemaName: "item", status: "published", limit: 100 }), mapuj res.documents do .published_content, filtruj active !== false, a jeśli podano categoryCode, zachowaj pozycje, których category._ref równa się identyfikatorowi kategorii. Wyeksportuj resolveImages(refs) rozwiązujący każdą referencję item.images przez cms.documents.get.query({ websiteId, id: ref._ref }) i zamieniający jej pole obrazu na URL przy pomocy zaimplementowanego buildAssetUrl (część 1, krok 8).

src/components/ — pięć komponentów elementów UI zarejestrowanych w rejestrze trasy catch-all po nazwie komponentu, snake_case jak w panelu: { nav, product_grid, product_detail, cart_summary, footer }. Nav i Footer czytają swoje pola skalara z propów content (typ BlockComponentProps<T> z cms-renderer/lib/types). ProductGrid i ProductDetail to asynchroniczne komponenty serwerowe odczytujące routeParams i pobierające dane z catalog.ts: routeParams.<param> to { value, … } — odczytuj .value, więc ProductGrid wywołuje listItems(routeParams.category_code?.value) (karty linkują do /products/{code}) i ProductDetail wywołuje getItemByCode(routeParams.item_code?.value) (galeria przez resolveImages, opis rich-text, cena, Dodaj do koszyka). CartSummary renderuje koszyk z useCart z przyciskiem Zapłać. Trzymaj formatPrice w czystym src/lib/format.ts, żeby komponenty klienckie nie importowały serwerowego catalog.ts.

src/components/AddToCartButton.tsx — przycisk "use client" przyjmujący { code, name, priceLabel } i wywołujący useCart().addItem({ code, name, priceLabel, quantity: 1 }). Użyj go w ProductDetail.

src/lib/useCart.ts — bezgłowy koszyk: pozycje { code, name, priceLabel, quantity } w stanie, utrwalane w localStorage, wystawiający addItem/removeItem/updateQty/subtotal oraz checkout() wysyłający POST { lines: [{ code, quantity }] } (tylko kody i ilości — nigdy ceny) na /api/stripe/checkout, po czym przekierowujący do zwróconego url.

Styluj wszystko naszym systemem projektowym, jako własne komponenty — nie kopiuj układu strony źródłowej.

Trzy rzeczy warte zapamiętania po wykonaniu polecenia:

  • Skalarny „chrome” przychodzi przez content ({ content }: BlockComponentProps<T>) — zdekonstruowanie pól jako propów najwyższego poziomu spowoduje pusty rendering. Dane katalogu pochodzą z routeParams + pobrania w catalog.ts, bo CEL nie wiąże list ani galerii. Koszyk niesie kody pozycji, nigdy ceny.
  • routeParams.<param> to { value, schemaName, document } — czytaj .value. Odczyty zwracają published_content, nie .content. Klucze rejestru są snake_case, tak jak w panelu.
  • Podnieś @types/react/@types/react-dom do v19 — szablon dostarcza v18, co łamie bloki asynchronicznych komponentów serwerowych na React 19.

4. Napisz kod serwerowy Stripe (kręgosłup)

Trzy krótkie pliki serwerowe — jedyny kod płatności w aplikacji. Ponownie używają getItemByCode, więc obciążenie rozwiązuje się po stronie serwera.

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 — rozwiąż każdy produkt z CMS-u, obciąż ceną Stripe:

import { NextResponse } from "next/server";
import { stripe } from "@/lib/stripe";
import { getItemByCode } from "@/lib/catalog"; // server-side, read-tier key

export async function POST(req: Request) {
  const { lines } = await req.json();                // [{ code, quantity }] — no prices from the client
  const line_items = await Promise.all(
    lines.map(async ({ code, quantity }: { code: string; quantity: number }) => {
      const item = await getItemByCode(code);          // server resolves from the CMS
      return { price: item!.stripe_price_id, quantity }; // price from CMS, never 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 redirects here
}

src/app/api/stripe/webhook/route.ts — zaufany sygnał realizacji zamówienia:

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

export async function POST(req: Request) {
  const body = await req.text();                      // RAW body — required for signature verification
  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: record the order / send a receipt.
  }
  return new Response(null, { status: 200 });
}

Wymagana poprawka szablonu: src/proxy.ts przekierowuje każde /api/* do CMS-u, więc Twoje trasy Stripe nigdy się nie wykonują. Przepuść je najpierw:

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();                       // handle locally
  }
  return cmsProxy(request as unknown as Parameters<typeof cmsProxy>[0]);
};
// keep the scaffold's `export const config = { matcher: [...] }` unchanged

Weryfikacja: curl -X POST localhost:3000/api/stripe/webhook -d x zwraca Bad signature.

Uruchom Stripe CLI do lokalnych webhooków:

stripe login
stripe listen --forward-to localhost:3000/api/stripe/webhook
# copy the whsec_... into STRIPE_WEBHOOK_SECRET, restart bun dev

whsec_… jest per sesję. O bezpieczeństwo dbają dwie zasady: checkout ponownie wyprowadza cenę z CMS-u po code (manipulacja koszykiem nie zmieni jej), a webhook weryfikuje sygnaturę względem surowego ciała.

5. Utwórz trasy

Panel → Pages → Create page, trzy razy. Przypisz każdy parametr do komponentu (pole slug code):

  1. /products/{item_code} → item
  2. /categories/{category_code} → category
  3. /cart — statyczna strona (wpisz dosłownie /cart, nie /{cart})

6. Dodaj elementy UI, podepnij CEL, opublikuj

Dla każdej trasy: Page Builder → Add UI Element → Custom → dodaj komponenty w kolejności, wypełnij pola skalarne (wartość statyczna lub CEL), Publish.

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

Ustaw nav.brand i footer.text na statyczne ciągi; nagłówki na statyczne etykiety.

Page Builder na trasie kategorii — zaznaczony element product_grid, nagłówek powiązany przez CEL.

Page Builder na trasie produktu — element product_detail na powiązaniu produktu.

Pułapka parametrycznego Page Buildera: na dwóch trasach parametrycznych dodane elementy UI się nie zapisują (bloki się osierocają i strona renderuje się pusta). Do czasu naprawy, powiąż block_ids tych stron bezpośrednio przez Profound MCP update_page, a następnie opublikuj. (Statyczna /cart podpina się normalnie.) Z tego samego powodu ProductGrid wyprowadza nagłówek z kategorii, którą pobiera, zamiast przez CEL.

7. Renderuj i kup

  • /categories/lighting → siatka. Kliknij produkt → szczegóły + Dodaj do koszyka. /cart → Zapłać.
  • Płatność przekierowuje do Stripe-hosted checkout. Użyj testowej karty 4242 4242 4242 4242, dowolnej przyszłej daty ważności/CVC. Wrócisz do /cart?status=success, a stripe listen pokaże checkout.session.completed.

Renderowana strona produktu — galeria, cena i przycisk Dodaj do koszyka.

Koszyk — pozycje i pojedynczy przycisk Pay-with-Stripe.

Tylko produkty z ceną są możliwe do zakupu — kup jeden z ~3, którym nadałeś ceny w kroku 6.

Opcjonalnie — umiędzynarodowienie. Przetłumacz każdy komponent (wszystkie 35 języków naraz), dodaj segment /{language}/… odwzorowany na wbudowany komponent systemowy language i przełącz pola powiązane przez CEL na documents.translated. Zobacz poradnik dotyczący katalogu lotnisk, część 2, krok 7.

Część 3 — Production

1. Wypuść: GitHub, potem Vercel

git init && git add -A && git commit -m "Stripe storefront"
gh repo create store --private --source=. --push   # --public is fine too

Polecenie build: generated/cms-schemas.ts jest w .gitignore, więc przypnij build, aby wygenerował go ponownie — dodaj vercel.json:

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

W Vercel: Add New → Project, zaimportuj store i dodaj zmienne środowiskowe — PROFOUND_API_KEY, NEXT_PUBLIC_PROFOUND_WEBSITE_ID, NEXT_PUBLIC_CMS_API_URL, NEXT_PUBLIC_BUNNY_CDN_URL, STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET (wartość dla wersji wdrożonej, poniżej) oraz NEXT_PUBLIC_SITE_URL (Twój adres produkcyjny). Wdróż.

Następnie podepnij webhook wdrożony (sekret stripe listen był tylko lokalnie): Stripe → Developers → Webhooks → + Add endpoint → https://<prod>/api/stripe/webhook, zdarzenie checkout.session.completed. Skopiuj jego whsec_… do Vercel i wdroż ponownie.

Brakująca zmienna środowiskowa = „działa lokalnie, puste w produkcji” — najczęstsza pułapka wdrożeniowa. Wdrażamy tutaj z kluczami testowymi; kiedy będziesz gotów przyjmować realne płatności, przełącz STRIPE_SECRET_KEY oraz sekret webhooka na wartości produkcyjne.

2. Podgląd na żywo i edycja in-place

Oba elementy dostarcza szablon.

  • Podgląd na żywo: <Refresher> aktualizuje stronę, którą edytor przegląda, gdy zapisuje w panelu — bez redeployu. (To podgląd dla edytora; odwiedzający widzą opublikowaną treść po zwykłej rewaluacji.)
  • Edycja in-place: dodaj ?edit_mode=true do dowolnego URL-a, by zobaczyć nakładki edycyjne. Publiczni odwiedzający oglądają czystą stronę.

Dodaj trasę preview, której brakuje w szablonie. Panel ładuje swoje iframe podglądu pod /cms-preview_<path>; bez tej trasy każdy podgląd kończy się 404. Dodaj ją:

// src/app/cms-preview_/[...slug]/page.tsx
import { ParametricRoutePreviewPage } from "cms-renderer/lib/renderer";
import { registry } from "../../registry";   // extract your registry to a shared module
export default async function Page({ params, searchParams }) {
  const { slug } = await params;
  const PreviewPage = ParametricRoutePreviewPage as any; // async RSC; React 19 types
  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} />;
}

Dodaj również src/app/cms-preview_/page.tsx (to samo, slug: []) dla korzenia segmentu.

I to cała budowa

Sklep Stripe oparty na treściach: katalog w CMS-ie, strony listingu + szczegółów z jednej puli tras i działająca kasa hostowana. AI zasiliło katalog, spieło design i napisało czytnik katalogu + komponenty + bezgłowy koszyk; Ty przygotowałeś komponenty, podlinkowałeś ceny Stripe, ustawiłeś trzy trasy, chrom CEL oraz trzy krótkie pliki Stripe. CEL wiąże oprawę; komponenty pobierają katalog. A Stripe pozostał zwięzły — jedno wywołanie sessions.create i jeden podpisany webhook, z kupującym płacącym na stronie Stripe.

Continue Reading
Previous‹Deployments