Staggered releases

Web first, then desktop, then mobile.

7 minmedium

Last lesson. With the compatibility matrix from the previous lesson in hand, you ship a coordinated release. The order matters — backwards from how you think, sometimes.

The staggered order

Staggered release timeline

Time ────────────────────────────────────────────────► Step 1 ┌──────────┐ │ API │ v1.5 → Postgres migration runs FIRST │ (b/c │ │ compat) │ └──────────┘ Step 2 ┌──────────┐ │ Web │ Deploy to Vercel. Instant. │ Admin │ └──────────┘ Step 3 ┌──────────┐ │ Desktop │ Auto-updater pushes to existing │ release │ installs. Slow trickle. └──────────┘ Step 4 ┌──────────┐ │ Mobile │ Submit to stores. Days of review. │ release │ Days of user updates. └──────────┘
API first, then frontends. Mobile last because it's the slowest to roll out.

Why API first

The API is the surface that EVERYTHING depends on. If you update mobile first, mobile v1.5 calls a v1.5 endpoint that doesn't exist yet. 500s. So:

  1. Backwards-compatible API change (the matrix says: existing clients still work).
  2. Database migration runs as part of API deploy.
  3. Old API endpoints kept alive for clients still on old versions.

The constraint: every API deploy must be backwards-compatible with the OLDEST supported client. The matrix tells you what that is.

Why web second

Web is instant — push to Vercel, every visitor on next refresh gets the new version. So you ship API + web on the same day, often within minutes. The user experience: load the page → new features.

Why desktop third

Desktop has the auto-updater (from the Desktop course), so it pushes faster than mobile but slower than web. Typical rollout:

  • Day 0: API + web deployed.
  • Day 1: cut desktop release. Auto-updater nudges users.
  • Day 3: ~50% of active desktop users on new version.
  • Day 7: ~85% on new version.

Why not day 0 for desktop? Because if you discover an API bug, you only have to roll back the API + web. Pulling desktop is more complex (existing installs need to revert).

Why mobile last

Mobile is two layers of slow:

  1. App Store / Play Store review (1-7 days).
  2. User update behavior (1-30 days).

Implication: mobile is shipping NEXT week's API change THIS week, so that by the time your code is in users' hands, the API supports it. Mobile builds are always 1-2 sprints ahead in their assumed API.

Expo OTA is the cheat code. For JavaScript-only changes (no new native modules), Expo Updates lets you push a JS bundle directly to existing installs in minutes — no store review. Use OTA aggressively for bug fixes; reserve full builds for native changes. The mobile course covers this.

The rollback plan

For every release, write a 1-page rollback plan BEFORE you deploy:

releases/v1.5-rollback.md
# v1.5 Rollback plan
## What ships
- API: adds /api/featured-products endpoint
- Web: featured-products carousel on homepage
- Desktop: not affected
- Mobile: not affected
## If broken
1. API rollback: `fly deploy --image api:v1.4` (DB migration is additive — safe to leave)
2. Web rollback: in Vercel dashboard → promote previous deployment
3. Desktop: no action
4. Mobile: no action
## Smoke tests after each step
- API: curl /healthz returns 200
- Web: homepage loads
- Test user can log in

Boring document. Saves you when it's 2am.

Coordinating the people side

Release coordination is half technical, half social:

  • Release notes per surface. Don't conflate. Web users don't care about your iOS-only fix.
  • One channel for the whole release. A Slack thread per release where status lands. Easier to scan than 15 PRs.
  • Freeze window. Friday afternoon is not when you deploy. Sunday before a holiday is not when you deploy.
  • Post-deploy checklist. Smoke-test each surface within an hour. Page someone if anything looks off.

Pulse + Sentinel earn their keep

After deploy, Pulse (your Grit observability) tells you request volumes per surface. If web traffic drops 80% in 5 min, something's broken. Sentinel rate-limits keep a misbehaving client from taking down the API while you investigate.

Quick check

You release v1.5 — API and web on Monday, desktop on Wednesday, mobile next week. Wednesday afternoon a user reports a bug that only repros on the API change. What's the cleanest rollback?

Try it

For chapter 5's assignment, cut a coordinated release:

  1. Pick a small but real feature (e.g., add is_featured to products + show on each surface).
  2. Update docs/compat-matrix.md.
  3. Write a 1-page releases/vX.Y-rollback.md before deploying.
  4. Deploy API + web (Vercel-style).
  5. Wait 24h, then cut desktop with the auto-updater.
  6. Cut a mobile EAS build, OR ship as an OTA update if the change is JS-only.
  7. Practice the rollback: pretend the API change broke something. Walk the rollback steps in your doc. Time it. Aim for < 10 minutes.

You finished Building Web + Desktop + Mobile 🎉

Five chapters, 13 lessons. You can now:

  • Scaffold a four-surface project: API + web + admin + mobile + desktop on one monorepo
  • Share types and Zod schemas across all surfaces with one command
  • Build a feature with surface-appropriate UX on each platform, backed by one API
  • Handle offline reads + writes on mobile (persistence) and desktop (outbox)
  • Resolve cross-surface conflicts with an explicit policy
  • Coordinate staggered releases without breaking older clients, with a written rollback plan

Where to go from here

You've completed the full Grit kit course set. Some directions:

  • Ship a real product. Honestly — the courses gave you the map; shipping closes the loop.
  • Open-source contribution: PRs to github.com/MUKE-coder/grit welcome — generators, kits, docs.
  • Build a Grit UI component for the registry — the catalog keeps growing.
  • Share your story: tweet, write a post, record a video. The community grows from real builders showing real work.

Thanks for finishing the course. Go ship something.

Spot a typo? Have an idea?

Help us improve this lesson. One click opens a GitHub issue with the lesson URL pre-filled — suggest clearer wording, report a bug, or request more depth. The course keeps improving thanks to learners like you.

Suggest an improvement on GitHub