Guide
Cloudflare Workers / Pages Functions
SSG generates static HTML. The transactional parts of a commercial site — payments, form submissions, dynamic pricing, server-side conversion tracking — need code that runs per request. On Cloudflare Pages that code is a Pages Function, and SSG wires one into the same build and the same deployment.
The split is deliberate: SSG for content, Workers for transactions. The
content pages stay static (fast, cacheable, cheap); only the handful of /api/*
routes hit a Function.
The worker: section
1worker:
2 dir: workers/contact-form # a Pages Functions project (contains functions/)
3 mode: functions # "functions" (default) or "worker"
4 routes_include: # what reaches the Function; default ["/api/*"]
5 - /api/*
6 routes_exclude: [] # carve static paths back out if needed
7 wrangler_config: "" # optional wrangler.toml outside the project root
At build time SSG:
- copies
dir(itsfunctions/tree, or a prebuilt_worker.js) into the output, and - writes
_routes.jsonfromroutes_include/routes_exclude, so every path not matched byincludeis served as a static asset and never invokes the Function.
worker: is empty by default; without it the build is static-only, unchanged.
mode: functions vs mode: worker
| Mode | dir contains |
Deploy path |
|---|---|---|
functions (default) |
a functions/ directory of .ts/.js handlers |
wrangler pages deploy (Cloudflare builds them) |
worker |
a prebuilt, bundled _worker.js |
pure-Go Direct Upload — no Node/wrangler needed |
Use functions for normal development. Use worker when you want SSG's
dependency-free Direct Upload deploy and have already bundled your Worker
yourself (e.g. with esbuild in CI).
Scaffolding a Function
ssg new worker <template> drops a batteries-included, npm-dependency-free
template under ./workers/<template>/ and prints the worker: block to add:
| Template | What it does |
|---|---|
contact-form |
POST /api/contact — Turnstile verify, email via MailChannels (Resend optional) |
stripe-checkout |
POST /api/checkout (Checkout Session) + POST /api/stripe-webhook (HMAC signature verify) |
dynamic-price |
GET /api/price/:sku from KV or an upstream API, plus a client snippet |
conversions-proxy |
POST /api/track — server-side Meta CAPI relay with SHA-256-hashed PII |
1ssg new worker stripe-checkout
Each template ships a README.md listing the secrets it needs.
Secrets
Secrets never live in .ssg.yaml or in the Function source — set them per Pages
project with wrangler:
1wrangler pages secret put STRIPE_SECRET_KEY
2wrangler pages secret put TURNSTILE_SECRET
The Function reads them from its env binding at runtime.
Local development
1ssg --config .ssg.yaml --http --watch
When worker: is set, --watch defaults its runner to wrangler dev started
from the worker directory, so the static preview and the Functions run side by
side. An explicit watch_runner (or --wrangler/--workerd) overrides that
default.
Deploy
1ssg --config .ssg.yaml --deploy cloudflare --deploy-project my-site
- With a
functions/tree, SSG shells out tonpx wrangler pages deployso Cloudflare builds the Functions.CLOUDFLARE_API_TOKENandCLOUDFLARE_ACCOUNT_IDare read from the environment. - With
mode: worker(a prebuilt_worker.js), the pure-Go Direct Upload path is used — no wrangler, no Node.
If wrangler is required but missing, the deploy fails with an actionable
message; switch to mode: worker with a prebuilt bundle to avoid the Node
dependency.
What SSG does not do
- No JS/TS bundler — Cloudflare Pages builds Functions from source;
mode: workercovers prebuilt bundles. - No secret management — that is
wrangler pages secret put. - No KV/Durable Object provisioning — bind those in the Pages dashboard.