sahin.io

All skills and agents

production-deployment

What production is for sahin.io/angel-investments (Cloudflare Pages behind the sahin.io proxy), what a push to main runs, how public pages are routed and rendered, and how to run the static build locally. Use before touching the deploy workflow, the worker, routing, redirects or static generation, or when asked whether something is live.

Source file
.claude/skills/production-deployment/SKILL.md

Production and deployment

Moved out of CLAUDE.md on 2026-10-11 (owner: keep CLAUDE.md thin). Shipping a change is the ship-to-production skill; checking it first is deploy-gate.

Production is Cloudflare Pages (project angel-investments, origin angel-investments-aiq.pages.dev), reached through the sahin.io reverse proxy at https://sahin.io/angel-investments/. Manus (angelport-xm6wmuy7.manus.space) is not production and webdev_save_checkpoint does not deploy.

A push to main runs .github/workflows/deploy-cloudflare.yml: pnpm check → pnpm test → Vite build → scripts/generate-static.ts → scripts/validate-public-seo-build.mjs → esbuild of cloudflare/_worker.ts → wrangler pages deploy dist/cloudflare → sahin.io zone purge of the entry points and revoked bundles with a 404 check (scripts/cloudflare-purge.ts, fails the run on failure) → proxy prerender purge (when the PURGE_SALT secret exists) → scripts/verify-live-deploy.ts → IndexNow notification.

How public pages work

  • server/seoRouteManifest.ts createSeoRouteManifest() is the only inventory of public routes; renderRouteHtml() renders each one to static HTML. There is no Express SSR and no SPA fallback.
  • server/publicRouting.ts resolvePublicRequest() is the single (pure) routing decision used by both the Cloudflare worker and the Express dev server (server/publicSite.ts): exact manifest match → canonical 301 for trailing-slash/case variants → /sources/{id} interstitial → legacy redirect → allowlisted asset → true 404.
  • shared/publicUrls.ts builds every public path under https://sahin.io/angel-investments/: /{company}, /{category}, /{parent-category}/{subcategory} (parents pinned in data/subcategory-parents.json), investor directories at /{company}/angel-investors, /{category}/angel-investors, /{parent-category}/{subcategory}/angel-investors, the investor index /angel-investors with firm pages at /angel-investors/{firm}, /lists/{list}. When telling the owner about URLs, write them in full (sahin.io/angel-investments/anthropic/angel-investors). Never hand-build a URL; read .claude/skills/public-url-conventions/SKILL.md before adding or changing any route or slug.
  • server/legacyRedirects.ts builds dist/cloudflare/legacy-redirect-map.json: an exact map of retired URL families (/category/{slug}, /subcategory/{slug}, /{slug}-angels-investors, /investors/{slug}, /{slug}-funding, /compare/…, market/sector pages, …) to canonical routes. Owner-reviewed one-off entries live in data/legacy-redirects.json. A redirect whose source becomes a real route is dropped automatically; everything unmapped is a real 404.

Run the static build locally

NODE_ENV=production npx vite build --config vite.config.cloudflare.ts
NODE_ENV=development npx tsx scripts/generate-static.ts
node scripts/validate-public-seo-build.mjs
npx esbuild cloudflare/_worker.ts --bundle --platform=neutral --target=es2022 --format=esm --outfile=dist/cloudflare/_worker.js
npx wrangler pages dev dist/cloudflare --port 8788   # then curl http://localhost:8788/angel-investments/anthropic