Exit Cost Calculator
The real price of switching — in hours and dollars. We document 41 migration paths with step-by-step breakdowns, hidden gotchas, and honest difficulty ratings.
Know your exit cost before you sign up. The cheapest tool isn't cheap if leaving costs 80 hours.
Shopify → Medusa.js
E-commerce80h
$500
Steps
- 1.Export products, customers, orders as CSV
- 2.Set up Medusa backend + admin
- 3.Rebuild storefront (Liquid → React/Next.js)
- 4.Recreate checkout and payment integration
- 5.Migrate shipping and tax rules
- 6.Set up 301 redirects for all product/collection URLs
Gotchas
- !Shopify apps have no equivalent — rebuild or find alternatives for each
- !SEO redirects are critical — thousands of URLs must be mapped
- !Shopify Payments rates may actually be better than alternatives at low volume
Salesforce → Twenty CRM
CRM60h
$500
Steps
- 1.Export all Salesforce objects (Contacts, Accounts, Opportunities, Activities)
- 2.Map Salesforce custom objects to Twenty data model
- 3.Write migration scripts for data transformation
- 4.Recreate automation rules (Salesforce Flows → Twenty workflows)
- 5.Rebuild reports and dashboards
- 6.Migrate email templates and sequences
- 7.Retrain sales team
- 8.Run parallel systems for 30 days
Gotchas
- !Salesforce's custom objects, formulas, and validation rules are irreplaceable in most alternatives
- !AppExchange integrations have no Twenty equivalents
- !Years of accumulated automation and process logic must be manually recreated
- !Twenty CRM is early-stage — missing features you take for granted in Salesforce
Firebase → Supabase
BaaS40h
Free
Steps
- 1.Export Firestore to JSON
- 2.Transform data to relational schema
- 3.Migrate to PostgreSQL
- 4.Rewrite security rules to RLS
- 5.Replace Firebase Auth with Supabase Auth
- 6.Update client SDK calls
- 7.Migrate Cloud Functions to Edge Functions
Gotchas
- !Firestore's nested documents don't map cleanly to tables
- !Auth user passwords can't be exported
- !Realtime subscriptions have different API
Datadog → Grafana Cloud
Monitoring40h
$50
Steps
- 1.Set up Grafana Cloud stack
- 2.Replace Datadog agent with Prometheus exporters
- 3.Recreate dashboards in Grafana
- 4.Migrate alerting rules to Grafana Alerting
- 5.Set up log aggregation (Loki)
- 6.Configure APM (Tempo/Jaeger)
Gotchas
- !Dashboard recreation is manual and tedious
- !3-year contracts may have early termination fees
- !Custom metrics need re-instrumentation
WordPress → Astro
CMS/Site40h
$20
Steps
- 1.Export WordPress content via WP REST API or WXR export
- 2.Convert posts/pages to Markdown or MDX files
- 3.Design Astro site structure and layouts
- 4.Build Astro components matching WordPress theme
- 5.Set up Astro Content Collections for blog posts
- 6.Configure 301 redirects for all existing URLs (critical for SEO)
- 7.Migrate media files to static assets or CDN
- 8.Set up contact forms (replace WP plugins)
- 9.Deploy to Vercel/Netlify/Cloudflare
Gotchas
- !WordPress plugins (WooCommerce, Yoast, Contact Form 7) have no Astro equivalents — rebuild or replace
- !URL structure changes will destroy SEO without proper 301 redirects
- !Dynamic features (comments, search, forms) need external services
- !WordPress's WYSIWYG editing experience is lost — content editing is now Markdown in code editor or headless CMS
Stripe → Paddle
Payments35h
$250
Steps
- 1.Map Stripe products to Paddle catalog
- 2.Rebuild checkout (Paddle.js vs Stripe Elements)
- 3.Negotiate active subscription migration with Paddle
- 4.Update webhook endpoints and event schemas
- 5.Handle VAT/tax differences (Paddle is MoR)
- 6.Update invoice templates and customer portal
Gotchas
- !Active subscribers must be manually migrated — Paddle handles negotiation
- !Paddle's webhook schema is entirely different from Stripe's
- !Paddle as Merchant of Record affects your legal/accounting setup
Firebase → Convex
BaaS35h
Free
Steps
- 1.Design Convex schema from Firestore document model
- 2.Export Firestore data to JSON
- 3.Write Convex mutations for data import
- 4.Replace Firestore SDK with Convex React hooks
- 5.Rewrite Cloud Functions as Convex functions
- 6.Migrate Firebase Auth to Convex Auth or Clerk
- 7.Replace Firestore realtime listeners with Convex reactive queries
- 8.Update file storage from Firebase Storage to Convex file storage
Gotchas
- !Firestore's nested document model doesn't map to Convex's relational model
- !Firebase Auth passwords can't be exported — users need to reset
- !Convex's reactive query model is fundamentally different from Firestore snapshots
- !Convex functions run server-side only — no client-side offline support like Firestore
Auth0 → Better Auth
Auth35h
Free
Steps
- 1.Set up Better Auth with your database (Postgres/MySQL/SQLite)
- 2.Export Auth0 users via Management API
- 3.Write migration script for user records, metadata, and roles
- 4.Implement email/password + OAuth flows from scratch
- 5.Replace Auth0 SDK and Universal Login with custom UI
- 6.Rewrite middleware and route protection logic
- 7.Migrate Auth0 Actions/Rules to Better Auth hooks
- 8.Set up email verification and password reset flows
- 9.Test all auth flows end-to-end
Gotchas
- !Password hashes from Auth0 use bcrypt but format may need adaptation
- !Auth0's Universal Login and prebuilt UI have no equivalent — build everything custom
- !Machine-to-machine tokens need a different approach
- !Auth0 Organizations feature requires custom implementation in Better Auth
Stripe → Lemon Squeezy
Payments30h
$200
Steps
- 1.Map products/prices to LS catalog
- 2.Rebuild checkout flow
- 3.Migrate active subscriptions (customers re-subscribe)
- 4.Update webhook handlers
- 5.Handle tax configuration (LS is MoR)
- 6.Update receipts/invoices
Gotchas
- !Active subscriptions can't be migrated — customers must re-subscribe
- !Payment methods don't transfer
- !Lemon Squeezy is Merchant of Record — different tax model
Clerk → Better Auth
Auth30h
Free
Steps
- 1.Set up Better Auth with your database
- 2.Export users via Clerk Backend API
- 3.Write migration script for user records and metadata
- 4.Implement email/password + OAuth flows from scratch
- 5.Replace all Clerk React components with custom UI
- 6.Rewrite middleware auth checks
- 7.Migrate webhook handlers
- 8.Test all auth flows end-to-end
Gotchas
- !Clerk's prebuilt components (<SignIn/>, <UserButton/>) have no equivalent — build everything custom
- !Password hashes use bcrypt but Clerk's specific format may need adaptation
- !Organization/team features require custom implementation from zero
- !Session management strategy changes completely (JWT vs database sessions)
Supabase → Convex
BaaS30h
Free
Steps
- 1.Design Convex schema from Supabase table structure
- 2.Export Postgres data via pg_dump or Supabase API
- 3.Write Convex mutations for data import
- 4.Replace Supabase JS client with Convex React hooks
- 5.Rewrite RLS policies as Convex function-level auth
- 6.Replace Edge Functions with Convex actions
- 7.Migrate Supabase Auth to Convex Auth or Clerk
- 8.Replace Supabase Realtime subscriptions with Convex reactive queries
Gotchas
- !Postgres features (CTEs, window functions, full-text search) have no direct Convex equivalent
- !Convex has no raw SQL — all data access is via TypeScript functions
- !Supabase Storage → Convex file storage has different API
- !Convex is a closed platform — no self-hosting option, so you're trading one vendor lock-in for another
Contentful → Sanity
CMS28h
Free
Steps
- 1.Export Contentful content model as JSON
- 2.Recreate content schema in Sanity Studio
- 3.Write migration scripts for content export/import
- 4.Replace Contentful SDK with Sanity client
- 5.Rebuild editorial UI customizations in Sanity Studio
- 6.Update image asset references
Gotchas
- !Contentful's rich text format (document nodes) differs from Sanity's Portable Text
- !Asset URLs change — update all hardcoded references
- !Contentful's localization model maps differently to Sanity's i18n approach
Auth0 → Clerk
Auth24h
Free
Steps
- 1.Export users via Auth0 Management API
- 2.Import to Clerk via Backend API
- 3.Replace Auth0 SDK with Clerk SDK
- 4.Update login/signup components
- 5.Migrate social connections
- 6.Update webhook handlers
Gotchas
- !Password hashes may not transfer
- !Custom Actions need rewrite
- !Universal Login customizations lost
Supabase → PocketBase
BaaS24h
$5
Steps
- 1.Export Postgres data to JSON/CSV
- 2.Design PocketBase collections matching Supabase tables
- 3.Import data into PocketBase
- 4.Replace Supabase JS client with PocketBase SDK
- 5.Rewrite RLS policies as PocketBase API rules
- 6.Replace Edge Functions with PocketBase hooks or external API
- 7.Set up file storage migration from Supabase Storage
- 8.Self-host PocketBase on VPS
Gotchas
- !PocketBase is SQLite-based — no complex Postgres features (CTEs, window functions, full-text search)
- !Realtime implementation differs significantly
- !No equivalent to Supabase Edge Functions — need external service
- !Single-binary deployment is great but scaling is limited vs managed Postgres
Datadog → Axiom + Sentry
Monitoring24h
Free
Steps
- 1.Set up Axiom for logs and metrics ingestion
- 2.Replace Datadog agent with OpenTelemetry Collector exporting to Axiom
- 3.Set up Sentry for error tracking and performance monitoring
- 4.Recreate critical dashboards in Axiom using APL queries
- 5.Migrate alerting rules to Axiom monitors
- 6.Set up Sentry alerts for error spikes
- 7.Configure Slack/PagerDuty integrations
Gotchas
- !Datadog's unified APM + Logs + Metrics in one UI has no single replacement — you're now managing two tools
- !Dashboard recreation is manual and APL query syntax differs from Datadog queries
- !Datadog enterprise contracts may have early termination fees
- !Custom metrics need re-instrumentation with OTel semantics
MongoDB Atlas → PostgreSQL
Database20h
Free
Steps
- 1.Design relational schema from document model
- 2.Write migration scripts
- 3.Export data with mongodump
- 4.Transform and import to Postgres
- 5.Update all queries (Mongoose → Prisma/Drizzle)
- 6.Test thoroughly
Gotchas
- !Nested documents → JOIN tables adds complexity
- !Aggregation pipeline → SQL rewrites
- !Change streams → Postgres LISTEN/NOTIFY
Auth0 → Supabase Auth
Auth20h
Free
Steps
- 1.Export users via Auth0 Management API
- 2.Import users to Supabase Auth via admin API
- 3.Replace Auth0 SDK with Supabase Auth client
- 4.Recreate social OAuth connections in Supabase
- 5.Migrate Auth0 Actions/Rules to Supabase hooks
- 6.Update RLS policies to use auth.uid()
- 7.Test MFA flows if applicable
Gotchas
- !Auth0 password hashes may not be importable — users might need to reset passwords
- !Auth0's Universal Login customization is lost entirely
- !Machine-to-machine tokens need a different approach in Supabase
- !Rate limiting on auth endpoints differs significantly
Datadog → BetterStack
Monitoring20h
$30
Steps
- 1.Set up BetterStack uptime monitors for all endpoints
- 2.Replace Datadog agent with BetterStack logging agent
- 3.Recreate critical dashboards in BetterStack
- 4.Migrate alerting rules and on-call schedules
- 5.Set up status page in BetterStack
- 6.Update PagerDuty/Slack integrations
Gotchas
- !Datadog 3-year contracts may have early termination fees — check your agreement
- !APM tracing has no direct BetterStack equivalent — use Sentry for that
- !Custom metrics and dashboards need manual recreation
- !BetterStack is simpler by design — some Datadog power-user features don't exist
Stripe → Polar
Payments20h
$100
Steps
- 1.Map Stripe products/prices to Polar products
- 2.Rebuild checkout flow using Polar SDK
- 3.Migrate customer records (customers must re-subscribe)
- 4.Update webhook handlers for Polar events
- 5.Handle active subscription transitions
- 6.Update invoicing and receipt logic
Gotchas
- !Active subscriptions cannot be migrated — customers must re-enter payment info
- !Polar is focused on digital products/SaaS — not suitable for physical goods
- !Stripe's extensive payment method support (SEPA, iDEAL, etc.) may not be available
- !Polar's API is simpler but less flexible than Stripe's
Vercel → Kamal + Hetzner
Hosting20h
$5
Steps
- 1.Set up Hetzner VPS with Docker
- 2.Configure Kamal deploy.yml for your app
- 3.Containerize your Next.js app with Dockerfile
- 4.Set up Traefik as reverse proxy (Kamal handles this)
- 5.Configure SSL with Let's Encrypt
- 6.Set up environment variables via Kamal secrets
- 7.Configure custom domain and DNS
- 8.Set up monitoring and logging (BetterStack or Grafana)
- 9.Test zero-downtime deployments
Gotchas
- !Next.js Image Optimization needs sharp installed in container
- !Edge Middleware won't work — rewrite as server middleware
- !No preview deployments — set up manually with Kamal accessories or skip
- !You're now responsible for server security updates and backups
Clerk → WorkOS
Auth20h
Free
Steps
- 1.Set up WorkOS User Management
- 2.Export Clerk users via Backend API
- 3.Import users to WorkOS via Admin API
- 4.Replace Clerk SDK with WorkOS SDK
- 5.Rebuild login/signup UI (WorkOS AuthKit)
- 6.Migrate social OAuth providers
- 7.Update webhook handlers for WorkOS events
- 8.Configure SSO connections if using organizations
Gotchas
- !Clerk's prebuilt React components have no direct WorkOS equivalent — use AuthKit hosted UI or build custom
- !WorkOS is enterprise-focused — pricing may be higher at low user counts
- !Clerk's organization model maps well to WorkOS organizations but metadata may not transfer
- !WorkOS webhook event schemas differ from Clerk's
AWS Lambda → Cloudflare Workers
Compute16h
Free
Steps
- 1.Audit Lambda functions for Workers compatibility
- 2.Rewrite Node.js APIs to Web Standard APIs
- 3.Replace AWS SDK calls with fetch-based equivalents
- 4.Migrate secrets from Secrets Manager to Workers KV/Secrets
- 5.Set up wrangler.toml and deploy
- 6.Update API Gateway routes
Gotchas
- !Node.js built-ins (fs, child_process) not available in Workers
- !Lambda timeout limit is 15 min; Workers max is 30s (CPU time)
- !AWS SDK and IAM auth patterns don't exist in Workers
AWS Lambda → Fly.io
Compute16h
Free
Steps
- 1.Containerize Lambda functions
- 2.Create Dockerfiles per service
- 3.Deploy to Fly.io
- 4.Migrate API Gateway routes to Fly proxy
- 5.Update secrets from AWS Secrets Manager to Fly secrets
- 6.Set up auto-scaling config
Gotchas
- !Lambda event triggers (SQS, S3, EventBridge) have no direct Fly equivalent
- !Cold start behavior is different — Fly machines can be persistent
- !VPC/subnet isolation requires Fly private networking setup
Clerk → Supabase Auth
Auth16h
Free
Steps
- 1.Export users via Clerk API
- 2.Import to Supabase Auth
- 3.Replace Clerk components with custom UI
- 4.Update session handling
- 5.Migrate social OAuth providers
- 6.Update RLS policies to use Supabase auth.uid()
Gotchas
- !Clerk's prebuilt UI components have no Supabase equivalent — build custom
- !Organization/team model needs custom Postgres tables
- !Password hashes may require users to reset
PlanetScale → Turso
Database16h
Free
Steps
- 1.Export PlanetScale data via mysqldump
- 2.Transform MySQL schema to SQLite-compatible schema
- 3.Import data into Turso database
- 4.Replace mysql2/PlanetScale SDK with @libsql/client
- 5.Rewrite MySQL-specific queries for SQLite
- 6.Update connection strings across environments
- 7.Set up edge replicas if needed
Gotchas
- !MySQL → SQLite type mapping has edge cases (ENUM, SET, UNSIGNED)
- !Stored procedures and triggers don't exist in libSQL
- !PlanetScale's deploy request workflow has no Turso equivalent
- !Query syntax differences: GROUP_CONCAT, DATE functions, IFNULL vs COALESCE
Jira → Linear
Project Management16h
Free
Steps
- 1.Export Jira issues via CSV or API
- 2.Use Linear's Jira importer (built-in)
- 3.Map Jira workflows/statuses to Linear states
- 4.Recreate automation rules in Linear
- 5.Migrate custom fields to Linear labels/properties
- 6.Update CI/CD integrations (branch naming, PR linking)
- 7.Train team on Linear's different workflow model
Gotchas
- !Jira's complex permission schemes don't map to Linear's simpler model
- !Custom fields and workflow transitions are lost in translation
- !Jira's JQL has no Linear equivalent — Linear uses filters
- !Attachments and comments import but formatting may break
Prisma → Drizzle
ORM14h
Free
Steps
- 1.Audit all Prisma model definitions
- 2.Recreate schema in Drizzle TypeScript format
- 3.Rewrite all Prisma Client queries to Drizzle syntax
- 4.Replace Prisma Migrate with Drizzle Kit migrations
- 5.Update seed scripts
- 6.Test all queries with integration tests
Gotchas
- !Prisma's nested writes (create with connect) have no direct Drizzle equivalent — must be explicit
- !Prisma Middleware → Drizzle has no built-in equivalent, use wrappers
- !Prisma Studio replacement needs external tool (e.g., Drizzle Studio or TablePlus)
Vercel → Coolify
Hosting12h
$10
Steps
- 1.Set up VPS + Coolify
- 2.Connect GitHub repo
- 3.Configure environment variables
- 4.Set up custom domain + SSL
- 5.Test serverless functions (may need rewrites)
- 6.Set up monitoring
Gotchas
- !Edge middleware needs rewrite
- !Image optimization not included
- !No built-in analytics replacement
CircleCI → GitHub Actions
CI/CD12h
Free
Steps
- 1.Audit all CircleCI .circleci/config.yml files
- 2.Rewrite workflows to GitHub Actions YAML syntax
- 3.Replace CircleCI orbs with Actions marketplace equivalents
- 4.Migrate environment variables to GitHub Secrets
- 5.Test all pipelines on feature branch
- 6.Update status checks in branch protection rules
Gotchas
- !CircleCI orbs don't have direct GitHub Actions equivalents for all cases
- !Parallelism model differs — CircleCI uses parallel jobs, Actions uses matrix
- !Self-hosted runners need separate setup if you have custom hardware needs
Mixpanel → PostHog
Analytics12h
Free
Steps
- 1.Set up PostHog project and install SDK
- 2.Map Mixpanel events to PostHog events
- 3.Replace Mixpanel SDK calls with PostHog SDK
- 4.Recreate funnels and retention reports in PostHog
- 5.Export historical Mixpanel data via export API
- 6.Import historical events into PostHog (optional)
- 7.Migrate user identification and properties
- 8.Update server-side tracking
Gotchas
- !Historical data import to PostHog is possible but time-consuming for large datasets
- !Mixpanel's JQL has no direct PostHog equivalent — use HogQL
- !Cohort definitions need manual recreation
- !PostHog's autocapture may generate more events than expected — tune filters
Twilio → Vonage
Communications10h
Free
Steps
- 1.Audit all Twilio SDK usage (SMS, Voice, Verify)
- 2.Port phone numbers to Vonage (takes 5-10 business days)
- 3.Replace Twilio SDK with Vonage SDK
- 4.Update webhook URLs for inbound SMS/calls
- 5.Test 2FA flows with Vonage Verify API
Gotchas
- !Phone number porting can take 2+ weeks and may have fees
- !Twilio Verify → Vonage Verify has different API shape
- !Vonage pricing varies by country — recheck cost assumptions
Vercel → Cloudflare Pages
Hosting10h
Free
Steps
- 1.Create Cloudflare Pages project
- 2.Adapt Next.js for @cloudflare/next-on-pages or switch to Astro
- 3.Migrate environment variables to Cloudflare dashboard
- 4.Replace Vercel serverless functions with Cloudflare Workers
- 5.Update DNS to Cloudflare
- 6.Replace next/image with Cloudflare Images or custom solution
- 7.Migrate Edge Middleware to Cloudflare middleware
Gotchas
- !Next.js on Cloudflare Pages has limited feature support — ISR, middleware, and some API routes may not work
- !Vercel's image optimization is superior — Cloudflare Images is separate and costs extra
- !Preview deployments work differently
- !Serverless function cold starts are different — Workers have near-zero cold starts but 10ms CPU time limit on free tier
Algolia → Typesense
Search8h
Free
Steps
- 1.Export index data from Algolia
- 2.Create Typesense collection with schema
- 3.Import data
- 4.Replace Algolia SDK with Typesense SDK
- 5.Update search UI components
Gotchas
- !Algolia's ranking rules are more granular
- !InstantSearch widgets need Typesense adapter
- !Geo-search API differs slightly
Heroku → Fly.io
Hosting8h
Free
Steps
- 1.Write Dockerfile (Heroku buildpacks won't work)
- 2.Create fly.toml config
- 3.Migrate Postgres (pg_dump → fly postgres import)
- 4.Migrate Redis add-on to Upstash or Fly Redis
- 5.Set secrets with flyctl secrets set
- 6.Update DNS and review auto-scaling config
Gotchas
- !Heroku buildpacks are not supported — Dockerfile required
- !Heroku's dyno model (web/worker) maps to Fly machines, not 1:1
- !Review memory limits carefully — Fly free tier is 256MB per machine
Railway → Fly.io
Hosting8h
Free
Steps
- 1.Write Dockerfile (Railway auto-detects, Fly requires explicit)
- 2.Create fly.toml configuration
- 3.Migrate Postgres with pg_dump/restore
- 4.Migrate Redis to Fly Redis or Upstash
- 5.Set secrets with flyctl
- 6.Configure auto-scaling (Fly Machines)
- 7.Update DNS
Gotchas
- !Railway's Nixpacks auto-build doesn't work on Fly — Dockerfile required
- !Fly's machine-based model is different from Railway's container model
- !Fly Postgres is not managed — you handle backups and failover
- !Volume-based persistence works differently between platforms
Heroku → Railway
Hosting6h
Free
Steps
- 1.Create Railway project
- 2.Connect repo or push Docker image
- 3.Migrate Postgres (pg_dump/restore)
- 4.Set config vars
- 5.Update DNS
- 6.Set up logging
Gotchas
- !Heroku-specific buildpacks may not work
- !Add-ons need replacement (Redis, etc)
- !Review dynos → container resource limits
AWS S3 → Cloudflare R2
Storage6h
Free
Steps
- 1.Create R2 bucket with S3-compatible API
- 2.Use rclone to sync S3 → R2
- 3.Update application code (same S3 SDK works with R2 endpoint)
- 4.Update CORS and access policies
- 5.Switch CDN to R2's built-in public access or custom domain
- 6.Verify all file operations work
Gotchas
- !R2 has no egress fees — this is the main reason to switch
- !S3 event triggers (Lambda) have no R2 equivalent — use Workers
- !S3 Object Lock and Glacier equivalents don't exist in R2
- !Some S3 SDK features (presigned POST policies) work slightly differently
Vercel → Netlify
Hosting5h
Free
Steps
- 1.Connect GitHub repo to Netlify
- 2.Migrate environment variables
- 3.Adapt next.config.js for Netlify adapter
- 4.Configure custom domain and DNS
- 5.Test edge functions and redirects
Gotchas
- !Vercel's Edge Middleware needs rewrite as Netlify Edge Functions
- !next/image optimization uses different adapter on Netlify
- !Vercel Analytics → use Netlify Analytics or PostHog
Vercel → Railway
Hosting4h
Free
Steps
- 1.Connect GitHub repo to Railway
- 2.Set environment variables
- 3.Configure custom domain
- 4.Update DNS
Gotchas
- !Edge functions not supported
- !Preview deployments work differently
- !No built-in CDN — add Cloudflare
SendGrid → Resend
Email4h
Free
Steps
- 1.Set up domain authentication in Resend (DKIM, SPF)
- 2.Replace SendGrid SDK with Resend SDK
- 3.Update email templates (can use React Email)
- 4.Migrate webhook handlers for delivery events
- 5.Update environment variables
Gotchas
- !SendGrid's template system doesn't export cleanly — rebuild with React Email
- !Marketing email features (lists, campaigns) don't exist in Resend — use separate tool
- !DNS propagation for new DKIM records takes up to 48 hours
- !SendGrid's event webhook schema differs from Resend's
Netlify → Vercel
Hosting3h
Free
Steps
- 1.Connect repo to Vercel
- 2.Move environment variables
- 3.Update build command if needed
- 4.Point DNS to Vercel
- 5.Migrate Netlify Forms to alternative
Gotchas
- !Netlify Forms → use Formspree or custom endpoint
- !Netlify Identity → Clerk/Supabase Auth
- !Edge handler syntax differs between platforms