Skip to content

How is the site deployed, and what is the provisioning checklist? #16

Closed

Description

@Abuubkar

Question

Decide the deploy shape and the ordered list of accounts to create later:

  • Cloudflare project shape from the hosting research, build command, output directory, preview versus production, secrets.
  • The provisioning checklist: Cloudflare account, domain purchase and DNS, Turnstile site, email path setup, Google Workspace mailbox, PostHog project. For each: what it produces (keys, IDs, records) and which env variable it fills.
  • What can be verified before accounts exist, and what only after.

Produce the deploy plan and checklist as the resolution. A wizard skill run may follow when the accounts are created.

Activity

  1. self-assigned this
    on Sep 5, 2026
  2. Abuubkar commented on Sep 5, 2026

    @Abuubkar
    OwnerAuthor

    Resolved by grilling, on top of the Cloudflare SSG research and the email research.

    Deploy shape

    One Worker with Static Assets. Prerendered pages are served as assets without invoking the Worker; only POST /api/assessment reaches it. No second Worker, no Pages project, no Wrangler environments — those exist to isolate multiple deployments and there is one site.

    vite build          →  dist/client/**            prerendered HTML + hashed assets
                           dist/<ssr>/**             Worker bundle
                           generated wrangler.json   assets.directory auto-populated
    wrangler deploy     →  reads .wrangler/deploy/config.json → generated config
    

    assets.directory is never hand-written — the Vite plugin fills it. Other assets.* keys (html_handling, not_found_handling) may be set in wrangler.jsonc and are carried through.

    Ignore Cloudflare's "bare wrangler deploy auto-detects TanStack Start" shortcut: it generates config pointing at .output/server/index.mjs and .output/public, which are Nitro paths, not the Vite-plugin layout used here.

    Caching: Static Assets default to public, max-age=0, must-revalidate + ETag. Add public/_headers making Vite's hashed /assets/* immutable; leave HTML on the revalidating default. _headers never applies to Worker responses, so the POST handler sets its own (Cache-Control: no-store, per #12).

    Preview vs production

    #15 put CI in GitHub Actions, so deployment is Actions-driven rather than Workers Builds:

    Trigger Command Result
    pull request wrangler versions upload Preview URL, production untouched
    push to main wrangler deploy version created, 100% traffic

    Versions and deployments are decoupled, so rollback is a deployment change rather than a rebuild.

    Preview URLs are indexable, and #14 ships the site index, follow. The mitigation is already in place: #14's canonical tag points at VITE_SITE_URL, so preview builds must be built with the production VITE_SITE_URL, marking every preview as a duplicate of production. Setting workers_dev: false would remove the risk entirely but also disables Preview URLs, since they depend on it — not worth losing previews for a residual risk that canonical already covers.

    Provisioning checklist

    Ordered by dependency. Each step names what it produces and which variable it fills.

    1 · Cloudflare account

    Free plan. → CLOUDFLARE_ACCOUNT_ID (GitHub secret). Create a scoped API token with Workers Scripts: Edit → CLOUDFLARE_API_TOKEN (GitHub secret).

    2 · Domain and DNS

    Purchase, point nameservers at Cloudflare, wait for the zone to go active. → VITE_SITE_URL, and the domain hard-coded into public/robots.txt and public/sitemap.xml (#14 — these cannot be written before this step).

    3 · Google Workspace mailbox

    The address that receives submissions. Apex MX stays pointed at Google throughout — nothing in this checklist changes it. → ASSESSMENT_TO, and VITE_CONTACT_EMAIL for #12's failure-panel mailto:.

    4 · Email Routing on a subdomain ⚠️

    Enable Email Routing on forms.<domain>, not the apex. Cloudflare writes the subdomain's MX, SPF and DKIM records. Enabling it on the apex would replace Google's MX and break the client's mail.

    Test this step before relying on the free lane. The email research could not confirm from any primary source that a routing subdomain is an acceptable from domain for free verified-destination sends. If it is rejected, the fallback is Workers Paid (USD 5/month) plus Email Sending onboarding — a cost change, not a redesign. Confirm here rather than discovering it at launch.

    5 · Verified destination address

    Add the step-3 mailbox as a verified destination; Cloudflare emails a verification link. Sends to verified destinations are free on every plan and count against no quota. Declare the EMAIL send_email binding in wrangler.jsonc with destination_address set to it. Sender: assessment@forms.<domain>.

    6 · Turnstile widget

    Create an invisible widget (#13… #12). Add the production hostname; Cloudflare recommends production sitekeys not allow localhost. → VITE_TURNSTILE_SITEKEY (build-time) and TURNSTILE_SECRET via wrangler secret put. Set secrets.required in wrangler.jsonc so a deploy missing it fails loudly.

    7 · PostHog project

    Cloud US. → VITE_POSTHOG_PROJECT_TOKEN. Flip three project settings: enable Cookieless server hash mode, enable Discard client IP data, leave Record user sessions off.

    8 · WAF rate-limiting rule

    A rate-limiting rule on POST /api/assessment (#12 deliberately kept this out of code to keep the Worker stateless). Produces no variable — it exists only in the dashboard, so it is invisible to the repo and easy to forget on a rebuild. Worth a note in the runbook.

    9 · Custom domain on the Worker

    Attach <domain> (and www if wanted) to the Worker.

    10 · Assets and launch

    public/og.png (1200×630), favicon.ico, apple-touch-icon.png; robots.txt and sitemap.xml written with the real domain; submit the sitemap in Google Search Console.

    11 · Copy gate

    pnpm placeholders must exit clean before the production deploy — the hard gate from #15's deploy workflow, protecting #14's decision to ship indexable.

    What can be verified before any account exists

    Everything except the network itself. This is the map's standing constraint discharged.

    Verified locally How
    Full page, prerendered vite dev, then vite preview on the production build
    Form POST end to end wrangler dev runs the Worker in workerd via Miniflare, no login
    Turnstile pass and fail paths test keys: 1x…AA passes, 2x…AA fails, 3x…AA returns "token already spent"
    Email send EMAIL binding simulated; message written to console and local files
    Analytics no-ops cleanly empty VITE_POSTHOG_PROJECT_TOKEN
    Lint, format, types, build vp check, vp build
    Placeholder inventory pnpm placeholders
    Only verifiable after accounts
    DNS propagation and zone activation
    Whether the free send lane accepts a routing-subdomain sender (step 4)
    Real email arriving in the Workspace inbox, and its spam placement
    A real Turnstile challenge against a real hostname
    PostHog ingestion, and how much adblockers suppress (#13)
    WAF rate-limiting behaviour
    Preview URL and custom-domain routing

    Variable map

    Variable Kind From step
    CLOUDFLARE_ACCOUNT_ID / CLOUDFLARE_API_TOKEN GitHub secret 1
    VITE_SITE_URL build-time 2
    ASSESSMENT_TO wrangler var 3
    VITE_CONTACT_EMAIL build-time 3
    EMAIL wrangler binding 5
    VITE_TURNSTILE_SITEKEY build-time 6
    TURNSTILE_SECRET Worker secret 6
    VITE_POSTHOG_PROJECT_TOKEN build-time 7

    Known risks carried into production

    A wizard skill run is the natural follow-up once someone sits down to do steps 1–10.

  3. Abuubkar commented on Sep 5, 2026

    @Abuubkar
    OwnerAuthor

    Resolved. Deploy shape, ordered provisioning checklist, and the before/after-accounts verification split in the resolution comment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

wayfinder:grillingWayfinder ticket — HITL conversation

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions