A product catalogue with collections, image galleries and real checkout, built with
Next.js (output: "export") and Draftbase.
Everything renders at build time and deploys to GitHub Pages as static HTML.
Checkout runs on Stripe Payment Links — hosted pages whose URL is stored on the product entry. That means no cart server, no Stripe keys anywhere in this repo, and nothing to secure at runtime.
Step up from example-blog: this one adds
list-valued media fields, a json field, and the React/RSC side of the renderer.
npm install
cp .env.example .env # add your API keys
npm run seed # creates the templates + 5 sample products in your org
npm run devProducts seed without a payment link. Paste a Stripe Payment Link into each product's
stripePaymentLink field in Draftbase to turn on the Buy button.
collection
| Field | Type | |
|---|---|---|
title |
text | required |
slug |
text | required, used as the URL |
description |
richText | |
image |
media |
product
| Field | Type | |
|---|---|---|
title |
text | required |
slug |
text | required, used as the URL |
price |
number | required, major units (24.5 = $24.50) |
currency |
text | one of USD/EUR/GBP/CAD, formatted with Intl.NumberFormat |
summary |
text | max 200 chars |
description |
richText | |
images |
media list | resolves to an array; the first is the card image |
collection |
reference → collection |
|
stripePaymentLink |
text | a https://buy.stripe.com/... URL |
inStock |
boolean | false renders a disabled Sold out button |
options |
json | [{ "name": "Size", "values": ["S","M","L"] }] |
src/lib/draftbase.tsimportsserver-only, so importing it from a Client Component is a build error — that is the guardrail keeping the key server-side.generateStaticParams()enumerates every product and collection page;output: "export"requires it and will not fall back to on-demand rendering.- Rich text is rendered with
<MDXContent>from@draftbase/renderer, a React Server Component. Pass acomponentsmap to render your own MDX components. - Images use plain
<img>:next/image's optimiser needs a server, which a static export does not have.
A cart needs somewhere to hold state and something holding a Stripe secret key to create a session. Both mean a server, which means hosting cost and an attack surface. Payment Links move all of that to Stripe: the URL on the entry is public by design, and the money never touches this site.
The trade-off is real — one product per checkout, and the options shown on the product
page are display-only. When you need multi-item carts, inventory or per-variant pricing,
move to Stripe Checkout Sessions behind a serverless function and drop output: "export".
- The delivery key is read as
process.env.DRAFTBASE_API_KEYin server code only. Next inlinesNEXT_PUBLIC_*variables into the browser bundle — do not rename it toNEXT_PUBLIC_DRAFTBASE_API_KEY. - Use a delivery-scoped key: read-only, published entries only. Drafts are invisible to it, so an unpublished product cannot leak into a build.
- The management key is only for
npm run seed. Keep it in your local.envand out of CI — it can write and delete content. - No Stripe key of any kind belongs in this repo. Payment Links are URLs, not credentials.
.envis gitignored; only the empty.env.exampleis committed.
Verify after a build: grep -r "$(grep DRAFTBASE_API_KEY .env | cut -d= -f2)" out/ should
find nothing.
- Settings → Pages → Source: GitHub Actions.
- Settings → Secrets and variables → Actions → add
DRAFTBASE_API_KEY(delivery-scoped). - Optional: on the same page, add a repository variable
DRAFTBASE_ENVIRONMENTif your content lives in an environment other thanproduction. - Push to
main.
This repo deploys to the custom domain in public/CNAME, so it sets no
basePath. Forking to a project site (<user>.github.io/<repo>)? Delete public/CNAME and add
basePath: "/<repo>" to next.config.mjs.
Add a Draftbase webhook pointing at:
POST https://api.github.com/repos/<owner>/example-ecommerce/dispatches
{ "event_type": "draftbase-publish" }
with an Authorization: Bearer <fine-grained PAT> header scoped to this repo with
Contents: read and write. That token lives in Draftbase's webhook config — never here.
- Cart, inventory counts, per-variant pricing, orders, accounts — all need a server. See the trade-off section above.
- Search — see example-course for a build-time index.
- Draft previews — need a running server.