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:

  1. Content remains static: Ultra-fast, zero maintenance, cacheable at the edge.
  2. Redirects stay clean: Chain flattening ensures zero extra hops or SEO penalties.
  3. 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.