cloudflare · astro · ci/cd

How this site is built

A personal site is a small system, which makes it a good place to practice the habits I would want on a large one: everything in version control, no manual deploy steps, and as little running code as possible.

Static first

Every page is generated at build time with Astro. The fonts are self-hosted and the only scripts are two small files: one types the headline on the home page, the other submits the contact form. A visit is a handful of requests to a single origin.

One Worker, one job

The site runs on Cloudflare Workers with static assets. The Worker script is only invoked for /api/*; every other request is answered straight from the asset store without running any code.

// wrangler.jsonc
"routes": [{ "pattern": "ismaelvicencio.cl", "custom_domain": true }],
"assets": {
  "directory": "./dist",
  "binding": "ASSETS",
  "run_worker_first": ["/api/*"],
  "not_found_handling": "404-page"
}

A second Worker, about ten lines long, answers on www and redirects to the apex domain. Keeping it in the repository means the redirect is deployed and reviewed like everything else, instead of living as a rule in a dashboard.

A CSP with nothing to allow

The only third party is Turnstile, on the contact form. Everything else is served by the site itself, so the Content Security Policy can start from default-src 'none' and open very little:

Content-Security-Policy: default-src 'none';
  script-src 'self' https://challenges.cloudflare.com; style-src 'self';
  img-src 'self' data:; font-src 'self'; connect-src 'self';
  frame-src https://challenges.cloudflare.com; base-uri 'self';
  form-action 'self'; frame-ancestors 'none'

That shaped a few decisions. Styles are emitted as files rather than inlined, and code blocks are highlighted with class names instead of inline styles.

Deploys happen on push

A GitHub Actions workflow runs lint, type checks, tests, and the build on every pull request. On main it also deploys both Workers. The actions are pinned to commit SHAs, and the Cloudflare token it uses can only touch Workers on this one account and zone.

The terminal on the home page is the receipt: the commit and build time it shows are read from the build that is being served.

What is still missing

Infrastructure as code for the zone settings, and a note on how the contact form is protected with Turnstile and rate limiting. Each will get its own note.