Case study
A portfolio for someone who has never opened a terminal
The artist drags a folder of images into the GitHub web UI, commits, and two minutes later their site is live. No code, no secrets, and no bill, ever.
Next.js · GitHub Actions · GitHub Pages · AVIF
Context
Building a portfolio site for an artist friend is a recurring task. An earlier site already had every runtime piece, but three things kept it from being reusable: the content sync only ran on my machine with my credentials, the artist's identity was hardcoded, and a second site meant copying files by hand.
The hard constraint: everything on free tiers, and structurally unable to incur a charge. Not “capped” or “alerted”; unable.
Decisions
Everything is GitHub
Code, content, CI, hosting, issues and Copilot on one service with one login. Cloudflare can host, but has no git hosting or issue tracker, and R2 was the one service in the stack without a spend cap. Vercel has no issue tracker and still wants a git remote. GitHub was the only option covering all five jobs, and Pages on a public repo has no billing dimension at all: there is no pay-as-you-go option to decline. Consolidating deleted the storage bucket, the S3 client, four repo secrets, a budget alert, a quota guard, and every signup but one.
Content is the only configuration
One folder per project under content/, plus a reserved _about/ folder holding the artist's name, bio and links in a single markdown file. The template holds no per-site values, so “Use this template” twice produces two working sites with no hand-editing in between.
Never re-encode the artist's AVIF
Image quality is the whole product. Re-encoding lossy AVIF into lossy AVIF is generation loss, and it shows worst on the largest variant, where no downscale hides it. So a source already at or below the target width is copied byte for byte, and only narrower variants are encoded. A test enforces it.
Derivatives are never committed
Generated images would rewrite hundreds of files on any width change, and git keeps every copy forever. They are built in CI, cached under content-hash keys in actions/cache, and shipped to Pages as a build artifact. No gh-pages branch, no bot commits, and a warm build is a no-op.
Zero secrets, even at the cost of a click
The workflow uses only the built-in GITHUB_TOKEN. Enabling Pages is a repository setting that token cannot change; automating it needs a personal access token. Minting one for a non-coder to save a single click would bring back the only secret the design had deleted. So the artist flips one toggle, and if they forget, the build fails fast with a plain-English message telling them which one.
Consequences
- Setup is three clicks in the web UI for the artist, and nothing for me. GitHub already lists repos generated from a template, so there is no registry to maintain.
- The binding limit is capacity, not cost: 1 GB of published site, which at AVIF and 4000px is a few thousand images. Each site reports its usage at
/usageand the build warns past 70% instead of failing. - Accepted costs: repos must be public, so unannounced work is visible; there are no PR previews, which Vercel gave for free; and deleted originals stay in git history.
- Staying on personal accounts means I can push code to an artist's repo but never change its settings. That is the price of avoiding organizations, and the right side of the ownership line to be on.