How to migrate from Emergent to your own hosting
Splitting the FastAPI + React (CRA + craco) + MongoDB stack across Vercel, Railway and MongoDB Atlas. mongodump/mongorestore, REACT_APP_BACKEND_URL, and how to handle the emergentintegrations LLM proxy lock-in.
Emergent builds full-stack apps from natural-language prompts. Emergent's own documentation describes the platform as supporting "full-stack FastAPI + React + MongoDB apps without configuring servers" (source). The generated code is real — a React frontend, a Python FastAPI backend, MongoDB for data — and Emergent syncs it to a GitHub repository the developer owns.
That makes Emergent more portable than tools that hide the backend behind a proprietary SDK. It also makes the migration shape different from a frontend-only build: there is a long-running backend process that has to live somewhere, and the database is MongoDB rather than Postgres.
Plan prerequisite. GitHub integration is gated behind the Standard plan ($20/mo or higher) — not available on the free tier (source). Standard is the minimum to get code out of Emergent.
What an Emergent project actually contains
The structure below is verified against a real exported Emergent project. Two distinctly separated service directories at the root, plus several agent-orchestration artifacts the platform ships:
your-app/
├── .emergent/
│ └── emergent.yml # base image name, job_id, created_at
├── frontend/
│ ├── package.json # React 19 + react-scripts 5 + craco + shadcn/radix
│ ├── craco.config.js # webpack overrides + @emergentbase/visual-edits plugin
│ ├── components.json # shadcn config
│ ├── tailwind.config.js
│ ├── jsconfig.json # JavaScript, not TypeScript
│ ├── plugins/health-check/
│ ├── public/
│ └── src/
│ ├── App.js, index.js, App.css, index.css
│ ├── components/, hooks/, lib/, pages/
│ ├── contexts/AuthContext.jsx
│ └── services/api.js # axios → REACT_APP_BACKEND_URL/api
├── backend/
│ ├── requirements.txt # fastapi, uvicorn, motor, pyjwt, bcrypt, emergentintegrations
│ ├── server.py # single-file FastAPI app
│ └── tests/
├── memory/PRD.md # product requirements doc (agent artifact)
├── design_guidelines.json # design system spec (agent artifact)
├── test_result.md # test outcomes (agent artifact)
├── auth_testing.md # docs
├── README.md
└── .gitignore
Notable specifics most articles get wrong:
- Frontend is Create React App + CRACO, not Vite or Next.js. Build with
npm run build, output directory isbuild/(notdist/). - Frontend env var is
REACT_APP_BACKEND_URL(CRA convention), notVITE_*orNEXT_PUBLIC_*. - Backend is a single-file FastAPI app using Motor (async MongoDB driver) + PyJWT + bcrypt. JWT-in-localStorage auth, fully portable.
emergentintegrationsPython package inrequirements.txtis Emergent's LLM proxy SDK — see the lock-in note below.@emergentbase/visual-editsnpm package infrontend/package.jsonis the in-Emergent visual editor. It loads conditionally via CRACO and only does anything inside Emergent.
What syncs to GitHub, what stays in Emergent
| Component | In the GitHub-synced repo? | Where it actually runs |
|---|---|---|
frontend/ source code | ✅ yes | In the browser (after CRA build) |
backend/server.py and backend/requirements.txt | ✅ yes | Anywhere Python runs |
.emergent/emergent.yml (base image ref, job id) | ✅ yes | Only meaningful inside Emergent |
Agent artifacts (memory/PRD.md, design_guidelines.json, test_result.md) | ✅ yes | Only meaningful inside Emergent |
| Actual MongoDB data | ❌ never | On Emergent's managed MongoDB |
| Auth users (rows) + password hashes (bcrypt) | ❌ never (lives in MongoDB) | On Emergent's MongoDB |
| Secret values (MONGO_URL, JWT_SECRET, EMERGENT_LLM_KEY) | ❌ never | Set in Emergent dashboard |
What you need to replace
Three platform-specific pieces leave with Emergent:
- Managed MongoDB. Emergent provisions the database during build. Outside Emergent, the most common destination is MongoDB Atlas's free tier (M0), but any MongoDB host works.
- One-click deployment. Emergent keeps the production environment alive for you. Self-hosted equivalents are Railway, Render or Fly for the FastAPI backend, and Vercel, Netlify or Railway for the CRA frontend.
- Emergent's LLM proxy. The backend imports
emergentintegrations.llm.chatand usesEMERGENT_LLM_KEYto call models through Emergent's proxy. Outside Emergent, you have two options:- Keep the proxy. Continue paying Emergent for LLM access via the
emergentintegrationspackage, with their key. Simplest, but you stay dependent on Emergent for inference. - Replace with direct provider SDKs. Open accounts directly with OpenAI / Anthropic / Google. Replace each
LlmChat/OpenAISpeechToText/OpenAITextToSpeechcall with the equivalent provider SDK. Each call site needs editing — countemergentintegrationsimports first to gauge scope.
- Keep the proxy. Continue paying Emergent for LLM access via the
Auth and storage are not bundled the same way Lovable bundles them. Emergent projects implement auth in the FastAPI backend with JWT-in-localStorage + bcrypt password hashes, so the auth code travels with the migration. There is no separate auth service to recreate — but the user rows and password hashes live in MongoDB and migrate as part of the database.
Step 1 — Get the code into GitHub
If GitHub integration is already connected, push any pending changes:
git status
git add .
git commit -m "Prepare for external deployment"
git push origin main
git tag pre-migration && git push --tags
If GitHub is not yet connected, enable it from the Emergent dashboard (Standard plan or above). The integration creates a repository with /frontend and /backend folders synced automatically.
Step 2 — Run locally first
Open the repo in Cursor or VS Code. Each service needs its own dependencies.
# Frontend
cd frontend
yarn install # Emergent ships yarn.lock
yarn start # craco start, serves on http://localhost:3000
# Backend (separate terminal)
cd backend
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
uvicorn server:app --reload --port 8000
The frontend calls the backend through REACT_APP_BACKEND_URL (CRA reads it at build time). Set it in frontend/.env.local:
REACT_APP_BACKEND_URL=http://localhost:8000
The backend needs MONGO_URL, DB_NAME, JWT_SECRET and EMERGENT_LLM_KEY (if you keep the LLM proxy). For local development, run MongoDB in Docker:
docker run -d -p 27017:27017 --name emergent-mongo mongo:7
Then in backend/.env:
MONGO_URL=mongodb://localhost:27017
DB_NAME=myapp
JWT_SECRET=<any-strong-random-string>
EMERGENT_LLM_KEY=<from-emergent-dashboard>
The default CORS config in backend/server.py is allow_origins=["*"], so local-host calls work without tightening it.
Step 3 — Choose the hosts
Emergent migrations split into three pieces that each need a home.
Frontend → Vercel
Import the repo at vercel.com/new and set:
- Root Directory:
frontend - Framework Preset: Create React App (Vercel auto-detects this)
- Build Command:
yarn build(ornpm run build) - Output Directory:
build - Environment Variable:
REACT_APP_BACKEND_URL=https://<your-backend-domain>
Because CRA reads REACT_APP_* at build time, changing the URL after deploy requires a redeploy.
Backend → Railway, Render or Fly
FastAPI is a long-running process — it needs a host that keeps a Python service alive, not serverless functions. The three common targets:
- Railway — fastest setup. Import the repo, set the Root Directory to
backend, set the start command touvicorn server:app --host 0.0.0.0 --port $PORT. Railway's Railpack readsrequirements.txtorpyproject.tomlautomatically. - Render — similar to Railway. Create a Web Service from the repo, set the same root directory and start command.
- Fly.io — fits if you need multi-region or websockets; requires a
fly.tomland a Dockerfile, both of which Fly generates withfly launch.
All three handle SSL automatically once a custom domain is added.
Database → MongoDB Atlas
The free tier on MongoDB Atlas (M0 cluster, 512MB storage) is enough for most early-stage projects. Create the cluster, create a database user, allow the backend's egress IP under Network Access, and copy the connection string into the backend's MONGO_URL.
For larger projects, Atlas's paid tiers, DigitalOcean Managed MongoDB or self-hosted MongoDB on a VM all work. The connection string is the only thing the application cares about.
Step 4 — Migrate the data
Working with a coding agent. Claude Code, Codex, or another agent with repo access can run the dump/restore for you. Open the cloned repo in the agent and ask it to migrate MongoDB from the Emergent connection string to your Atlas one. The agent runs
mongodump/mongorestorein your local terminal with your approval, then verifies counts. Useful if you got to Emergent without much CLI background.
MongoDB migration uses mongodump and mongorestore, not pg_dump. The flow is the same shape as a Postgres migration — dump from source, restore into target — but the tools differ.
# Dump from Emergent's MongoDB
# (MONGO_URL is the connection string Emergent exposed in your secrets)
mongodump --uri="$EMERGENT_MONGO_URL" --out=./mongo_dump
# Restore into Atlas
mongorestore --uri="$ATLAS_MONGO_URL" ./mongo_dump
If the database is small, mongoexport to JSON and mongoimport into the target is also valid — useful if the source and target are very different versions of MongoDB.
After restore, run a smoke test against the new database. Pick a collection with predictable counts and verify it matches:
mongosh "$ATLAS_MONGO_URL" --eval "db.users.countDocuments()"
Where Emergent stores secrets
Emergent manages environment variables and secrets through the deployment dashboard. The keys to expect, based on a real exported project:
MONGO_URL— connection string to Emergent's managed MongoDBDB_NAME— database name inside that MongoDB instanceJWT_SECRET— used by the FastAPI auth code to sign tokensEMERGENT_LLM_KEY— Emergent's LLM proxy key (if you keep the proxy)- Plus anything else the app uses (Stripe keys, third-party API tokens, etc.)
All of these need to land on the new backend host (Railway / Render / Fly). Frontend env vars are smaller — typically just REACT_APP_BACKEND_URL pointing at the production backend domain.
See the hub guide's secrets-moving principles — particularly the rule about rotating anything that has been logged or shared, since Emergent's AI agents touch your secrets during build.
Step 5 — Wire the services together
Once frontend, backend and database are deployed, the three pieces need to know about each other:
- Backend → Database. Set
MONGO_URLandDB_NAMEon the backend host (Railway / Render / Fly) to the Atlas connection string + database name. Redeploy — env changes only apply to new deployments. - Frontend → Backend. Set
REACT_APP_BACKEND_URLon Vercel to the backend's public URL. Redeploy (CRA reads this at build time). - Backend CORS. The default Emergent server uses
allow_origins=["*"]— wide open. Tighten it to the new frontend domain in production:
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=["https://your-frontend.vercel.app", "https://your-custom-domain.com"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
- LLM access. Either keep
EMERGENT_LLM_KEYset on the new backend host (proxy stays through Emergent) or rewrite theemergentintegrationscall sites against direct provider SDKs and add OpenAI / Anthropic / Google keys instead. Either way, the keys live in the backend env — never in the frontend.
Step 6 — Custom domain
The frontend domain goes on Vercel / Railway as usual. The backend domain typically takes a subdomain like api.example.com:
- On Railway: Service → Settings → Networking → Custom Domain.
- On Render: Service → Settings → Custom Domains.
- On Fly:
fly certs create api.example.com.
DNS records propagate in five to thirty minutes. SSL issues automatically once the records resolve.
Common gotchas
The app loads but the network tab is full of CORS errors. The backend's CORS middleware does not include the new frontend domain. Update allow_origins and redeploy.
MongoDB connection times out from the backend host. Atlas Network Access does not include the backend's IP. Either add the specific IP or allow 0.0.0.0/0 for development. The latter is fine if the database user has a strong password and the connection string is treated as a secret.
uvicorn exits immediately after start. The host expects the app to bind to 0.0.0.0 on $PORT. Default uvicorn server:app binds to 127.0.0.1:8000 — invisible to external traffic. Use uvicorn server:app --host 0.0.0.0 --port $PORT as the start command.
Frontend build succeeds, runtime says "API base URL is undefined." REACT_APP_BACKEND_URL was not set at build time, or it was set after deploy and the build was not re-run. CRA bakes REACT_APP_* into the bundle at build time — changing the var on the host requires a redeploy.
@emergentbase/visual-edits errors after deploy. The package only works inside Emergent's runtime. craco.config.js already wraps the import in a try/catch with a warning, so missing the package is fine — but if a hard error fires, the catch logic in craco.config.js may have been edited. Leave the try/catch wrapper alone or remove the dependency entirely.
MongoDB queries that worked on Emergent are slow on Atlas. The free M0 tier has no dedicated CPU and limited connections. For anything past prototype traffic, M10 or higher is the realistic minimum.
What stays portable, what does not
The FastAPI codebase is the most portable part of the migration. Python is Python, FastAPI is open source, and Atlas is the same MongoDB Emergent provisioned. The CRA frontend is a normal React 19 + craco build that Vercel auto-detects.
What does not come across cleanly:
- The Emergent dashboard, credit-based pricing, and one-click deploy button — pure platform features.
@emergentbase/visual-edits— only works inside Emergent's runtime; safe to leave inpackage.json(the craco wrapper handles its absence) or remove entirely.- The agent artifacts (
memory/PRD.md,design_guidelines.json,test_result.md,.emergent/emergent.yml) — useful as reference but inert outside Emergent. Keep them or delete them. - The LLM proxy. Either rewrite
emergentintegrationscall sites to use direct provider SDKs, or keep paying Emergent for the proxy by settingEMERGENT_LLM_KEYon the new backend host.
If continued AI-driven development matters, keep syncing with Emergent through GitHub even while production runs externally. The platform supports that pattern.
Where to go next
If the project is mostly frontend with a thin backend, the "Migrate from Lovable" guide covers the frontend-on-Vercel + Supabase flow in more detail. For the full deploy walkthrough from empty project to live URL, the next guide in this pillar — "Your first real deploy: Supabase + Vercel from scratch" — ends with a working URL on a domain you own.
Next in this pillar
How to migrate from Base44 to your own hostingGet the next guide when it lands
One email on Sunday with new /learn guides, tool updates, and a couple of links worth reading.