Behind the scenes
How I built this site
This site is part of my portfolio too: the same automated delivery and infrastructure as code I offer clients, applied here. All the code is public.
Architecture
A static site served from Cloudflare's edge, with three small functions for what needs a server.
- Astro builds static HTML in Spanish (root) and English (/en/), styled with Tailwind CSS. There is no database.
- Content (services, projects, profile, testimonials) is YAML files in the repository, validated against schemas: invalid content fails the build. I edit it in an admin panel (Keystatic) with GitHub login: every save is a commit that deploys itself.
- The CV PDF and the Open Graph images are generated on every build from the same content, so they never go stale.
- Cloudflare Pages serves the site on *.pages.dev. Three Pages Functions: /api/contact validates the form, verifies Turnstile and sends the email through Resend; /api/event stores conversion events in Workers Analytics Engine; /api/keystatic handles the panel's GitHub login.
- Cookie-free analytics with Cloudflare Web Analytics, so there's no consent banner.
Pipeline
GitHub Actions verifies every change before it goes live.
- Lint (ESLint and Prettier), typechecking and unit tests (Vitest) for the HTTP handlers.
- Lighthouse CI on every page, in parallel without holding up the deploy: fails below 95 on performance.
- End-to-end tests with Playwright against the production build: routes in both languages, the contact form, conversion events, axe accessibility checks, SEO, and a privacy guard that scans the HTML and the PDF for phone numbers and client names.
- On every pull request: tofu fmt, validate and plan (posted to the PR), and a preview deploy with its own URL.
- On merge to main: tofu apply and a production deploy.
Infrastructure as code
OpenTofu defines everything the site needs on Cloudflare, in infra/.
- The Cloudflare Pages project and its functions' configuration: secrets as environment variables (including the key that encrypts the panel's session, generated by OpenTofu) and the Analytics Engine dataset.
- The Turnstile widget (spam protection without annoying CAPTCHAs), whose secret key goes straight to the contact function.
- The Web Analytics site.
- State lives in a private Cloudflare R2 bucket; the decision is recorded in an ADR.
- No secret is in the repository: they come from GitHub Actions and Cloudflare secrets.