The problem
Your Replit app works perfectly. Then you try to share it with real users and hit one of these:
- The app goes to sleep after 30 minutes of inactivity and takes 30 seconds to wake up
- Your SQLite database resets every time Replit restarts
- Environment variables don't survive the deployment
- The app crashes under more than 2-3 concurrent users
- You want a real domain, not
yourapp.username.repl.co
Replit is an excellent development environment and prototyping tool. It's not designed to host production apps with real users. This guide covers how to move off it properly.
Why Replit isn't a production host
Replit's free and Hacker tiers use ephemeral containers:
- Compute sleeps when there's no traffic — cold starts of 20-40 seconds kill real user experience
- Storage is ephemeral — any file written to disk (including SQLite databases) can disappear on restart
- No SLA — uptime is not guaranteed
- Limited concurrency — Replit containers have fixed CPU and RAM that don't scale
If you're on Replit Deployments (their paid hosting tier), some of these problems are reduced — but you're still better off on a platform designed for production if your app is growing.
Step 1: Export your code properly
Your Replit project is a git repo under the hood. Connect it to GitHub if you haven't already:
In Replit sidebar → Version Control → Create a GitHub repository
Or clone directly:
git clone https://github.com/your-username/your-replit-project.git
cd your-replit-projectBefore exporting, check these files exist:
package.jsonwith a"start"script (not just"dev").gitignorethat excludesnode_modules/,.env, and any data files
If there's no start script, add one:
{
"scripts": {
"dev": "tsx watch server.ts",
"build": "tsc",
"start": "node dist/server.js"
}
}Step 2: Replace your database
This is the most important step. If your app writes to a file (SQLite, JSON files, local storage), you need to move to a hosted database before anything else.
The right hosted database depends on your stack:
| If your app uses... | Move to... |
|---|---|
| SQLite | Supabase (Postgres) or Turso (SQLite-compatible) |
| JSON files for data | Supabase |
| Replit DB (built-in key-value) | Upstash Redis or Vercel KV |
| No database (just filesystem) | Supabase Storage for files |
Migrating from SQLite to Supabase:
- Export your SQLite schema:
sqlite3 your_database.db ".schema" > schema.sql-
Create a Supabase project at supabase.com (free tier is generous)
-
Convert your SQLite schema to Postgres (the main differences:
INTEGER PRIMARY KEY→SERIAL PRIMARY KEYorUUID,AUTOINCREMENT→DEFAULT gen_random_uuid()) -
Run the schema in Supabase's SQL editor
-
Export and import your data:
# Export from SQLite
sqlite3 your_database.db ".mode csv" ".output data.csv" "SELECT * FROM your_table;"
# Import to Supabase via dashboard → Table Editor → Import data- Update your app's database connection to use Supabase's Postgres connection string
Step 3: Move all secrets to environment variables
Replit has a Secrets tab in the sidebar. Every secret there needs to move to your new host's environment variable system.
List all your Replit secrets and make sure they're all in your deployment platform before switching.
What to look for:
# Grep for hardcoded values that should be env vars
grep -r "sk_test_\|sk_live_\|AIza\|xoxb-\|whsec_" --include="*.ts" --include="*.js" --include="*.py" .Any hit is a secret that's hardcoded and needs to move to environment variables.
In your code:
// ❌ Hardcoded
const apiKey = "sk_live_abc123"
// ✅ Environment variable
const apiKey = process.env.STRIPE_SECRET_KEY
if (!apiKey) throw new Error("STRIPE_SECRET_KEY is not set")Always fail loudly if a required env var is missing — silent undefined causes cryptic bugs later.
Step 4: Choose your deployment target
For most Replit apps, one of these three options is right:
Vercel — best for Next.js, React, and frontend-heavy apps
- Zero config for Next.js
- Free tier is generous
- Serverless — no persistent processes
- Not suitable for apps that need WebSockets or long-running processes
npm i -g vercel
vercelRailway — best for Express, FastAPI, Node servers, apps that need persistent processes
- Runs actual containers
- Supports WebSockets, background jobs, cron jobs
- Free trial available, then usage-based pricing
- Good for apps that Replit ran as a persistent server
Render — good middle ground, similar to Railway
- Free tier (with cold starts on free tier — same Replit problem, so use paid)
- Supports cron jobs, background workers, Postgres
If your app is a Next.js app: → Vercel
If your app is a Node/Express/FastAPI server: → Railway
If you're unsure: → Railway (handles more cases)
Step 5: Deploy to Vercel (Next.js apps)
# Install Vercel CLI
npm i -g vercel
# From your project directory
vercel
# Follow the prompts:
# - Link to existing project or create new
# - Framework: Next.js (auto-detected)
# - Root directory: ./
# Set environment variables
vercel env add SUPABASE_URL
vercel env add SUPABASE_ANON_KEY
vercel env add STRIPE_SECRET_KEY
# ... add all your secrets
# Deploy to production
vercel --prodStep 5 (alternative): Deploy to Railway (Node/Express apps)
# Install Railway CLI
npm install -g @railway/cli
# Log in
railway login
# In your project directory
railway init
railway up
# Add environment variables in Railway dashboard or via CLI:
railway variables set STRIPE_SECRET_KEY=sk_live_...
railway variables set DATABASE_URL=postgresql://...Railway will detect your start script and run it automatically.
Step 6: Set up a custom domain
On Vercel:
- Vercel Dashboard → your project → Settings → Domains
- Add your domain
- Copy the CNAME or A record values
- Add them in your domain registrar's DNS settings
- Wait 10-30 minutes for DNS to propagate
On Railway:
- Railway Dashboard → your service → Settings → Networking
- Generate a domain or add your custom domain
Step 7: Verify everything before announcing
- App loads on the new domain
- Authentication works (login/logout, OAuth if applicable)
- Database reads and writes work
- File uploads work (if applicable)
- Environment variables are set correctly (check your app's settings page)
- No hardcoded
localhost:references in your frontend code - Error monitoring is set up (Sentry free tier takes 5 minutes)
Common mistakes
Mistake 1: Forgetting to update API URLs
Replit apps often have hardcoded localhost:3000 or Replit URLs in frontend code. Search for them:
grep -r "localhost\|repl.co\|replit.app" --include="*.ts" --include="*.tsx" --include="*.js" .Replace with environment variables:
const API_URL = process.env.NEXT_PUBLIC_API_URL ?? 'http://localhost:3000'Mistake 2: SQLite database on the new host
Even if you copy your SQLite file to your new host, it won't work reliably on Vercel (serverless functions don't have persistent disk) and will lose data on Railway if the container restarts. Migrate to Postgres/Supabase before deploying.
Mistake 3: Not testing OAuth redirect URLs
If you use Google/GitHub login, your OAuth provider has a list of allowed redirect URLs. Add your new production domain to that list before switching traffic.
Mistake 4: Using Replit's built-in DB
Replit DB (the built-in key-value store) is not accessible outside of Replit. If you used it, you need to migrate that data before switching. Upstash Redis is the closest drop-in replacement.
Going further
- Set up CI/CD: connect your GitHub repo to Vercel/Railway so every push to
maindeploys automatically - Add a staging environment: deploy from a
stagingbranch to a separate URL for testing - Set up error monitoring with Sentry (free tier covers most small apps)
- See the AI Build Security Checklist — moving to production is the right time to audit security
Production migrations have a lot of moving parts. If you'd rather have a Smith handle the move end-to-end, post a Deploy Fix job on Smith.