Post
Static Content, Edge Transactions: Cloudflare Functions, Redirects, and Stripe in SSG
Web sites usually fall into two traps. Either they start out static and get rewritten into heavy SSR frameworks the moment marketing asks for a Stripe payment button, or they launch as a massive Node server just to render text and images that change twice a week.
Neither approach feels right. Static sites are unbeatable for speed, cost, and resilience: when your content is pre-baked HTML on an edge CDN, there is no database to crash, no cold boot delay, and no monthly server bill. But real business sites still need transactions: forms, subscriptions, webhook handlers, and path migrations.
With SSG 1.8.11, the boundary is explicit: static for content, edge functions for transactions. You get pure static HTML by default, plus zero-latency Cloudflare redirects and edge Functions when you need them—without turning your build pipeline into a complex node stack.
Here is how the integration works, how SSG handles redirects, and how to build a complete Stripe Checkout flow in under 5 minutes.
High-Level Architecture
SSG compiles your Markdown and templates into static assets (output/), while generating Cloudflare control files (_redirects and _routes.json) and outputting your edge handlers.
graph TD
subgraph Build Phase
A[Markdown Content & Templates] --> B[SSG Binary]
C[Config .ssg.yaml] --> B
D[Worker Templates / functions/] --> B
B --> E[Static HTML / CSS / Assets]
B --> F[_redirects File]
B --> G[_routes.json File]
B --> H[Edge Functions Directory]
end
subgraph Edge Deployment on Cloudflare
E --> I[Cloudflare Pages CDN]
F --> I
G --> J[Cloudflare Routing Engine]
H --> K[Cloudflare Pages Functions / Workers]
J -- "Static Asset Path" --> I
J -- "Dynamic Path /api/*" --> K
end
Because _routes.json explicitly instructs Cloudflare which paths hit a Function (/api/*), 99% of your traffic bypasses Worker execution entirely. You pay zero worker invocation costs for static content visits.
The Redirects Engine: Zero-Hop Flattening
Redirects are where SEO goes to die if managed poorly. When migrating sites or restructuring URLs, chained redirects (/old-page → /interim-page → /new-page) incur severe latency penalties and SEO degradation.
SSG's redirect engine solves this at build time by reading redirects: blocks and frontmatter aliases:, validating paths, and flattening redirect chains automatically.
1# .ssg.yaml
2redirects:
3 - from: /old-pricing
4 to: /pricing
5 status: 301
6 - from: /blog/*
7 to: /articles/:splat
8 status: 301
9 - from: /discontinued-product
10 to: /products
11 status: 410
If /old-pricing points to /pricing, and frontmatter on a new page sets aliases: [/pricing], SSG resolves A → B → C into two direct rules: A → C and B → C. Visitors and search bots take exactly one hop.
Migrating from Next.js?
If you are migrating off Next.js, you don't need to rewrite your redirect rules by hand. Run:
1ssg import redirects next.config.ts
Or pass a JSON dump from next.config.js:
1ssg import redirects --from-json redirects.json
SSG translates Next.js wildcard syntax (/:slug*) into standard _redirects splats (/* → :splat), maps permanent flags to HTTP status codes, and warns you about any un-convertible regex constraints.
Wiring Edge Workers & Pages Functions
Adding dynamic endpoints to an SSG site requires adding a worker: section to your .ssg.yaml:
1worker:
2 dir: workers/stripe-checkout
3 mode: functions
4 routes_include:
5 - /api/*
When you run ssg, it packages the static files and wires the functions/ directory for deployment. During local development:
1ssg --config .ssg.yaml --http --watch
SSG automatically boots wrangler dev side-by-side with its static preview server. You get live reload on static pages and instant hot-reload on edge API routes.
Step-by-Step: Adding Stripe Checkout & Webhooks
Let's walk through building a production-ready Stripe Checkout flow using the scaffolded worker template.
1. Flow Overview
sequenceDiagram
autonumber
actor Customer
participant Frontend as Static HTML Page
participant Worker as Cloudflare Function (/api/checkout)
participant Stripe as Stripe API
participant Webhook as Webhook Handler (/api/stripe-webhook)
Customer->>Frontend: Clicks "Buy Now"
Frontend->>Worker: POST /api/checkout { priceId }
Worker->>Stripe: Create Checkout Session
Stripe-->>Worker: Return Session URL
Worker-->>Frontend: Redirect JSON { url }
Frontend->>Stripe: Browser navigates to Checkout
Customer->>Stripe: Completes Payment
Stripe-->>Webhook: POST /api/stripe-webhook (payment_intent.succeeded)
Webhook->>Webhook: Verify HMAC Signature
Webhook-->>Stripe: 200 OK (Process order)
2. Scaffold the Worker Template
Run the built-in generator:
1ssg new worker stripe-checkout
This creates a lightweight, zero-dependency Pages Functions structure under workers/stripe-checkout/functions/api/:
checkout.ts: Handles session creation.stripe-webhook.ts: Validates cryptographically signed Webhook events from Stripe.
3. The Edge Function Code
Here is the clean, dependency-free checkout handler (checkout.ts):
1export async function onRequestPost(context) {
2 const { request, env } = context;
3 const { priceId } = await request.json();
4
5 if (!priceId) {
6 return new Response(JSON.stringify({ error: 'Missing priceId' }), { status: 400 });
7 }
8
9 // Call Stripe REST API directly using fetch (no heavy SDK needed on the edge)
10 const params = new URLSearchParams({
11 'mode': 'payment',
12 'success_url': `${env.SITE_URL}/success?session_id={CHECKOUT_SESSION_ID}`,
13 'cancel_url': `${env.SITE_URL}/pricing`,
14 'line_items[0][price]': priceId,
15 'line_items[0][quantity]': '1',
16 });
17
18 const stripeRes = await fetch('https://api.stripe.com/v1/checkout/sessions', {
19 method: 'POST',
20 headers: {
21 'Authorization': `Bearer ${env.STRIPE_SECRET_KEY}`,
22 'Content-Type': 'application/x-www-form-urlencoded',
23 },
24 body: params.toString(),
25 });
26
27 const data = await stripeRes.json();
28 if (!stripeRes.ok) {
29 return new Response(JSON.stringify({ error: data.error?.message }), { status: 500 });
30 }
31
32 return new Response(JSON.stringify({ url: data.url }), {
33 headers: { 'Content-Type': 'application/json' },
34 });
35}
4. Front-End Integration
On your static product or pricing page (content/pages/pricing.md or template):
1<button id="buy-btn" data-price="price_1NXXXXXXXXXXXXX">Buy Product ($29)</button>
2
3<script>
4document.getElementById('buy-btn').addEventListener('click', async (e) => {
5 const priceId = e.target.getAttribute('data-price');
6
7 const res = await fetch('/api/checkout', {
8 method: 'POST',
9 headers: { 'Content-Type': 'application/json' },
10 body: JSON.stringify({ priceId }),
11 });
12
13 const data = await res.json();
14 if (data.url) {
15 window.location.href = data.url;
16 } else {
17 alert('Payment initialization failed: ' + data.error);
18 }
19});
20</script>
5. Configuring Secrets & Deploying
Store your Stripe API credentials securely on Cloudflare (never put keys in .ssg.yaml or Git):
1wrangler pages secret put STRIPE_SECRET_KEY
2wrangler pages secret put STRIPE_WEBHOOK_SECRET
Finally, build and deploy the entire application with one command:
1ssg --config .ssg.yaml --deploy cloudflare --deploy-project my-storefront
SSG compiles your static content, constructs _redirects and _routes.json, uploads static assets, and deploys your Pages Functions via Cloudflare's edge pipeline.
Conclusion
Building modern web applications doesn't require choosing between static speed and dynamic capabilties. By pairing SSG with Cloudflare Pages Functions:
- Content remains static: Ultra-fast, zero maintenance, cacheable at the edge.
- Redirects stay clean: Chain flattening ensures zero extra hops or SEO penalties.
- Transactions run on the edge: Lightweight serverless functions handle payments and APIs without a full-blown application server.
Keep your static sites fast, simple, and pragmatic.