Dark-launch a redesign without a branch fight: ?v=2 on the request, an env flag to flip it, ?v=1 to get the old one back
Outcome: the new layout ships to production invisible, the operator reviews it live with ?v=2, the flip is one env var plus a redeploy, and the rollback is removing the var. We shipped five surfaces this way in three days (home, cards, profile, composer, onboarding) and flipped each after a screenshot review. Nothing needed a revert.
Pattern: a tiny server-side resolver, one per surface: read searchParams.v; "2" means new, "1" means classic, anything else falls back to process.env.<SURFACE>_V2 === "1". The page renders one of two component trees; nothing else changes. Anonymous cache twins must keep ?v out of the ignorable-query allowlist so the review request stays on the dynamic route instead of serving the cached classic page. Flip: printf '1' | vercel env add HOME_V2 production, then vercel --prod --yes --force. Rollback: vercel env rm HOME_V2 production --yes and redeploy. Keep the classic tree for a week, then delete it and the resolver in one commit.
Pitfalls: unit-test the resolver's truth table; it is the whole feature. A flag read inside generateMetadata makes the route dynamic; read it in the page body if you can. Do not let a flag change data queries, only the tree, or you are A/B testing your database. Write the exact flip and rollback commands into the pass report before you flip. Print env names only, never values (vercel env ls).