Overview
Siavash P. is a painter. His work sits in series: the Café-goers run alone is fourteen seasons of numbered pieces, around 1,700 paintings, alongside smaller collections and single works.
All of it lived on a WordPress site that was slow and hard for him to update. I rebuilt it from scratch on his own domain, siavashpd.com, with every old WordPress address forwarding to its new page so nothing already shared or indexed broke.
The brief that mattered was the people on both ends. The artist is not technical and has to add paintings himself, and I maintain the site from another country, so it has to run without me.
Tech Stack
- TanStack Start (React) — pages and server functions in one app, built with Vite
- Cloudflare Workers — the whole site is one Worker
- D1 (SQLite) — through Effect’s SQL client, plain SQL with Effect Schema, no ORM
- R2 — painting photos, served with a one-year cache
- Effect — every server service: gallery, groups, artworks, auth, image store
- Alchemy — all Cloudflare setup declared in code, deployed by GitHub Actions on push
- Tailwind CSS v4 — with the Fraunces font self-hosted
Features
- A gallery you walk through. The home page is a run of rooms, one per series or collection, each with an endless hanging wall, joined by torn-paper edges
- Café-goers seasons shown as fanned piles of paintings, like a hand of cards that opens on hover
- A page for every painting: the work on its wall, a museum label, full-screen zoom, swipe or arrow keys to the next
- Smooth page transitions — a clicked painting flies into its own page
- Admin panel the artist runs alone: drag in a folder of photos, and each one is shrunk to three sizes in the browser before upload, with retries, so a slow line still gets them through
- Drafts and publishing, reordering, covers for each pile, moving works between groups
- Its own login, with hashed passwords, rate limits and email password reset
- Every public page cached at Cloudflare’s edge under a content version, so an admin change shows on the next visit and a cached visit costs one database row
- Share previews, sitemap, canonical links, and the artist’s email kept out of the page HTML so scrapers don’t find it
Outcome
Live on the artist’s own domain since October 2026. He has his own admin account and manages the work himself.
The full archive moved over, ~1,700 photos, mostly through a GitHub Actions job that drove the real admin uploader from GitHub’s servers. Google Search Console took the sitemap on the first try.