shiner.app

browse.shiner.app

This site — the canonical registry of everything I’ve built, republished from a database row.

  • React 19
  • TypeScript
  • Vite
  • Tailwind v4
  • shadcn/ui
  • Supabase
  • Cloudflare Pages

Features

  • Projects are Supabase rows — adding one is adding a row
  • Every page prerendered to static HTML
  • Self-republishing via database webhook → deploy hook
  • Automated Playwright screenshot pipeline
  • Per-page Open Graph / social-card meta
  • Case studies stored as markdown in the registry
Screenshot of browse.shiner.app

The brief

My projects were described in three places — the personal site, the business site, and reality — and every new ship meant hand-editing at least two of them. This site is the fix: one canonical registry, edited in one place, that republishes itself.

How it was built

  • Projects are database rows. A Supabase projects table (public-read RLS) holds every title, blurb, stack list, link, and the case study you're reading right now, as markdown in a column.
  • The site is static anyway. At build time a script pulls the table, Vite builds the app, and every page — index and details — is server-rendered to plain HTML. Social crawlers (LinkedIn, Discord, Slack) execute no JavaScript, so static HTML is the difference between a rich preview card and a blank one.
  • The rebuild is a webhook. A Supabase database webhook fires a Cloudflare Pages deploy hook on any row change. Add a row, and about a minute later the site has republished itself — no rebuild, no redeploy, no git push from me.
  • Screenshots are automated. A Playwright script visits each live URL, captures it, uploads to Supabase Storage, and stamps the URL back onto the row.

Stack notes

Vite + React 19 + TypeScript strict, Tailwind v4, shadcn/ui, react-markdown. Cloudflare Pages on the shiner.app umbrella — the front door for everything else that will live there.