Deploying Both — Cheat Sheet
Full-Stack Integration · 3 topics. Download the PDF or the Instagram carousel and share it.
Cheat Sheet · AiCanCode.org
Deploying Both
Full-Stack Integration3 topicsQuick revision reference
1
Deploying a Frontend and a Backend
Two deployables means two pipelines, and the ordering between them matters: a backend change can break a frontend that is still serving the previous bundle.
- ✓Vercel builds and serves the frontend with instant rollback; Fly runs the container with migrations as a release command
- ✓A failing release command aborts the deploy and leaves the previous version serving, which is the desired behaviour
- ✓Browsers keep running the previous bundle after a deploy, so the backend must ship first and additively
- ✓Rename fields with expand, migrate, contract across three releases rather than one breaking change
- ✓A migration does not roll back with a redeploy — additive changes are what make rollback possible at all
Vercel builds; Fly migrates then rolls
# Frontend on Vercel — no config needed for the common case
# push to main -> production build and deploy
# push to a branch -> a preview URL, per commit
# build command: next build; env vars per environment
# Rollback is instant, because previous builds stay served.
# Backend on Fly — a container, and migrations
# fly.toml
[deploy]
release_command = "alembic upgrade head" # runs BEFORE the new
# version takes traffic
[http_service]
internal_port = 8000
auto_stop_machines = "suspend"
min_machines_running = 1 # 0 means cold starts
[[http_service.checks]]
path = "/health" # unhealthy -> no traffic
# .github/workflows/deploy.yml
- run: flyctl deploy --remote-only
env: { FLY_API_TOKEN: '${{ secrets.FLY_API_TOKEN }}' }
# A release_command failure aborts the deploy and the old version
# keeps serving — which is exactly what you want from a bad migration.
# Health check discipline: /health should verify the database, and
# a deploy should not be considered done until it passes.2
Domains, Cookies and TLS in Production
The domain layout decides whether cookies work. Two subdomains of one site is a completely different situation from two unrelated hosts, and only the first lets a normal cookie through.
- ✓Same-origin governs CORS; same-site governs cookies — two localhost ports are same-site, two real domains usually are not
- ✓Putting the API on a subdomain of the app keeps cookies same-site and Lax working, which is the simplest layout
- ✓SameSite=None requires Secure and brings CSRF protection back into scope
- ✓A Domain attribute shares the cookie with every subdomain, including ones you may not control
- ✓A CSP connect-src that omits the API origin blocks your own fetches and looks exactly like a CORS failure
Subdomains are same-site; fly.dev is not
// SAME-ORIGIN scheme + host + port identical (CORS cares) // SAME-SITE the registrable domain matches (COOKIES care) aicancode.org and api.aicancode.org // different origins -> CORS applies // SAME SITE -> a SameSite=Lax cookie IS sent aicancode.org and tool-hub-api.fly.dev // different origins AND cross-site // -> a Lax cookie is NEVER sent. Auth silently fails. // The three workable layouts: // 1. api.aicancode.org (a CNAME to Fly) // same-site, Lax works, cookies are simple. Best default. // 2. aicancode.org/api (proxied through Next rewrites) // same-ORIGIN, so no CORS either. Costs an extra hop. // 3. a genuinely different domain // needs SameSite=None; Secure, plus CSRF protection, and // browsers increasingly restrict third-party cookies anyway. // Layout 3 is where people end up by accident, by shipping with the // platform-provided hostname. Set up the custom domain before // launch, not after the first login bug.
3
Debugging Across the Seam
When something fails between two deployables, the first job is deciding which side it is on. A method for that is worth more than knowledge of any particular bug.
- ✓Check the network tab before forming a theory — no request row at all means the bug is client-side
- ✓A CORS error with no server headers is often a 500 that crashed before the CORS middleware ran
- ✓Postman and curl ignore CORS and send no Origin, so "works in Postman" always points at CORS or cookies
- ✓Server-rendered requests need cookies forwarded explicitly, which is why a 401 can appear only on first load
- ✓A request id echoed and logged on both ends is the single highest-value piece of full-stack observability
Network tab, status, curl, payload, logs
// 1. Did the request leave the browser?
// Network tab, Fetch/XHR filter. No row at all -> a client bug:
// a handler that never fired, a guard that returned early, a
// URL built as "undefined/problems" from a missing env var.
// 2. What status came back?
// (failed) with no status -> network, DNS, CORS or CSP
// 401/403 -> auth: check the Request Headers for
// the cookie or Authorization header
// 422 -> read the body; it names the fields
// 5xx -> server side; go to the logs
// 3. Does curl reproduce it?
curl -i https://api.aicancode.org/problems \
-H "Origin: https://aicancode.org" -b "sid=..."
// curl works, browser fails -> CORS, cookies, or CSP
// curl fails too -> a genuine server bug
// 4. Is it the payload? Compare what you SENT with what the API
// expects. "Copy as cURL" from the network tab gives the exact
// request, headers included.
// 5. Server logs, filtered by the request id from step 2.
// The single most useful habit: check the network tab before
// forming a theory. Most disagreements about whose bug it is end
// there.Learn this free with Aria, your AI tutor → AiCanCode.org/learn/full-stack