www.pcbjam.com was the last piece of the stack on Vercel. It is now a
Cloudflare Pages project (pcbjam-site) deployed by deploy-site.yml on
every push to main touching site/** — content must not wait for a
release tag.
The Astro adapter is gone entirely: the build is pure static and the one
dynamic route, /api/waitlist, is a Pages Function. Going adapter-free
(rather than swapping in @astrojs/cloudflare, which has dropped Pages
support and only targets Workers) removes three problems at once — no
Astro/adapter major-version coupling, Footer.astro's build-time execSync
keeps working because prerendering stays in Node, and image optimisation
stays plain build-time sharp with no Cloudflare Images binding.
Verified against a real Pages runtime (wrangler pages dev): 21/21 parity
probes pass, versus 19/21 on live Vercel. The scripted runbook is in
deploy/site/ — every mutating step is dry-run by default.
Four behaviour differences were found by measurement and are handled here:
- The blog post's COOP/COEP was already broken in production. vercel.json
scoped the headers to the bare URL, but the page's own canonical is the
trailing-slash form, which served 200 with no isolation headers — so
search arrivals lost SharedArrayBuffer and the embedded Gerber viewer
degraded. public/_headers covers both forms.
- Pages answers unknown URLs with the homepage at HTTP 200 when the
output has no 404.html — a soft-404 that invites indexing junk URLs as
the homepage. Hence src/pages/404.astro.
- Vercel's edge refused cross-site form POSTs ("Cross-site POST form
submissions are forbidden"); Pages does not, and a cross-site <form>
submit needs no CORS permission to be sent, so the allowlist cannot
stop it. The Function reproduces the guard; JSON posts stay exempt as
that is demo.pcbjam.com's allowlisted path.
- Cache-Control: immutable on /_astro/* came from the Vercel adapter's
generated route config, so it is now an explicit _headers rule.
Secrets move to `wrangler pages secret put --project-name pcbjam-site`
(RESEND_API_KEY, RESEND_SEGMENT_ID, WAITLIST_FROM_EMAIL);
WAITLIST_ALLOWED_ORIGINS stays unset so the allowlist stays in code.
Local dev reads .dev.vars, now gitignored — the root repo's **/.dev.vars
does not cover a nested git repo.
privacy.md and cookies.md named Vercel as a GDPR Art. 28 processor; those
mentions are removed and the existing Cloudflare entry widened to cover
website hosting.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LAmkjM7okPdScp9XLW1JVr
77 lines
3.3 KiB
Shell
Executable file
77 lines
3.3 KiB
Shell
Executable file
#!/usr/bin/env bash
|
|
# Freeze the LIVE Vercel behaviour as the reference every later step is compared
|
|
# against. Read-only; needs no Cloudflare credentials. Run this while Vercel is
|
|
# still serving — you cannot re-create it afterwards.
|
|
#
|
|
# deploy/site/00-baseline.sh [--force]
|
|
#
|
|
# Two probes are EXPECTED to fail here: post_coi (the blog post's COOP/COEP) is
|
|
# genuinely broken in production today — the page's canonical is the
|
|
# trailing-slash URL and that URL serves 200 with no isolation headers. Recording
|
|
# it is the point: 08-verify-prod.sh asserts those same probes PASS afterwards,
|
|
# so the migration proves it fixed the bug rather than porting it.
|
|
set -euo pipefail
|
|
. "$(dirname "$0")/lib/common.sh"
|
|
. "$(dirname "$0")/lib/parity.sh"
|
|
|
|
require_cmd curl dig awk sed jq
|
|
|
|
OUT="$STATE_DIR/baseline/vercel"
|
|
if [ -d "$OUT" ] && [ "${1:-}" != "--force" ]; then
|
|
die "baseline already exists at $OUT — refusing to overwrite (use --force).
|
|
Re-baselining after cutover would silently replace the reference."
|
|
fi
|
|
mkdir -p "$OUT"
|
|
|
|
section "confirming $PROD_BASE is still served by Vercel"
|
|
hdrs="$(_headers "$PROD_BASE/")"
|
|
if [ -z "$(_hdr "$hdrs" x-vercel-id)" ]; then
|
|
die "no x-vercel-id header on $PROD_BASE — this host is not on Vercel any more.
|
|
Baselining a Cloudflare response as 'the Vercel reference' would be useless."
|
|
fi
|
|
echo "ok: x-vercel-id present"
|
|
|
|
section "DNS + SOA snapshot"
|
|
{
|
|
echo "# captured $(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
echo "apex_cname=$(dig +short CNAME "$ZONE_NAME" || true)"
|
|
echo "www_cname=$(dig +short CNAME "www.$ZONE_NAME" || true)"
|
|
echo "ns=$(dig +short NS "$ZONE_NAME" | sort | tr '\n' ' ')"
|
|
# The SOA minimum is the NEGATIVE cache TTL. It is the number that makes
|
|
# delete-then-create dangerous: a resolver that asks while the record is gone
|
|
# caches NODATA for this long, and you cannot flush it.
|
|
echo "soa=$(dig +short SOA "$ZONE_NAME" || true)"
|
|
echo "soa_minimum_ttl=$(dig +short SOA "$ZONE_NAME" | awk '{print $NF}')"
|
|
} | tee "$OUT/dns.txt"
|
|
|
|
section "page titles (used to detect a soft-404 later)"
|
|
printf 'home_title=%s\n' "$(_title "$PROD_BASE/")" | tee "$OUT/titles.txt"
|
|
|
|
section "header snapshots"
|
|
for p in / /pricing /blog /privacy /blog/porting-kicad-graphics-to-webgl-in-2026 /gerber-demo/boot.js; do
|
|
f="$(printf '%s' "$p" | sed 's|/|_|g')"; [ "$f" = "_" ] && f="_home"
|
|
t="$(_trace "$PROD_BASE$p")"; eff="$(printf '%s' "$t" | cut -f1)"
|
|
{
|
|
echo "# requested: $PROD_BASE$p"
|
|
echo "# effective: $eff (hops $(printf '%s' "$t" | cut -f3))"
|
|
# Drop volatile headers so a later diff shows real changes, not timestamps.
|
|
_headers "$eff" | grep -vE '^(date|age|etag|last-modified|content-length|server|cf-ray|cf-cache-status|nel|report-to|alt-svc|set-cookie|x-vercel-id|x-vercel-cache|x-matched-path|expires|via):' | sort
|
|
} > "$OUT/$f.headers"
|
|
echo " $p -> $OUT/$f.headers"
|
|
done
|
|
|
|
rc=0
|
|
assert_parity "$PROD_BASE" --scope prod || rc=$?
|
|
assert_apex_redirect "$APEX_BASE" || rc=$?
|
|
|
|
{
|
|
echo "# baseline captured $(date -u +%Y-%m-%dT%H:%M:%SZ) against $PROD_BASE (Vercel)"
|
|
echo "# parity exit code: $rc (non-zero is EXPECTED — see the header of this script)"
|
|
} > "$OUT/parity-exit.txt"
|
|
|
|
section "done"
|
|
echo "Baseline written to $OUT"
|
|
echo
|
|
echo "Read the FAIL rows above and keep them: they are the 'before' half of the"
|
|
echo "COOP/COEP fix. 08-verify-prod.sh requires those same probes to pass."
|
|
echo "done: file://$OUT"
|