Replit Agent is the only AI builder that gives you the whole thing in one place: the editor, the runtime, the database, the secrets, the deployment. For a first version that is a real advantage — there is nothing to wire up and nothing to configure.
It is also why "Replit to production" is a different question from the same question about Lovable or Bolt. Those tools hand you a repo and leave; Replit is also your host, and the decision about whether it should stay your host is one you have to make deliberately. This post is about making it well.
What Replit Agent actually built
Look before deciding anything. The typical shape:
| Layer | What Agent generated | What to check |
|---|---|---|
| Server | Node/Express or Python Flask/FastAPI, often in one file | Auth middleware, input validation, CORS |
| Client | React with Vite, served by the same process | Nothing unusual — usually fine |
| Data | Replit's Postgres (Neon-backed) or Replit DB key-value | Which one, and whether the shape fits |
| Schema | Pushed with drizzle-kit push or created by hand | No migration history |
| Secrets | Replit Secrets | Fine — but see the fallback pattern below |
| Hosting | Replit Deployments: Autoscale or Reserved VM | Cold starts, cost curve, region |
The rows that need fixing whether or not you move are the server and the schema. The rows where you have a decision to make are data and hosting.
Fix these regardless of where it runs
Every route trusts the caller
Generated Express routes look like this:
app.delete("/api/projects/:id", async (req, res) => {
await db.delete(projects).where(eq(projects.id, req.params.id));
res.json({ ok: true });
});Nothing checks who is asking. Anyone who can reach the URL can delete any project. Add an auth middleware that verifies the session and attaches the user, then scope every query to that user:
app.delete("/api/projects/:id", requireUser, async (req, res) => {
const { rowCount } = await db
.delete(projects)
.where(and(eq(projects.id, req.params.id), eq(projects.ownerId, req.user.id)));
if (!rowCount) return res.status(404).end();
res.json({ ok: true });
});Do the same for every route that reads or writes user data. Agent-generated apps routinely have twenty or thirty routes and it is rare for any to be protected before someone goes through them.
CORS is *
app.use(cors());That allows every origin. Restrict it to your own domain:
app.use(cors({ origin: ["https://yourapp.com"], credentials: true }));The session secret has a fallback
Search the server for this shape:
secret: process.env.SESSION_SECRET || "dev-secret"If the variable is ever missing in production — a redeploy to a new environment, a renamed secret — every session is signed with a string that is in your repo. Remove the fallback and let the app refuse to start without it.
The schema has no history
drizzle-kit push applies your schema file straight to the database. It is
fast, and it means there is no record of what changed when and no way to roll a
schema change back. Switch to generated migrations:
npx drizzle-kit generate # writes a versioned SQL file
npx drizzle-kit migrate # applies pending onesCommit the migrations directory. From now on a schema change is a file in a pull request that someone can read.
Decision one: the data store
If Agent used Replit's Postgres, you have a real database and the question
is only whether to keep it where it is. It is Neon underneath, so moving to
your own Neon or Supabase project is a pg_dump and pg_restore with a new
connection string — worth doing if you want billing, backups and access control
under your own account, and not urgent otherwise.
If Agent used Replit DB — the key-value store you access with
db.get() and db.set() — you have a decision to make sooner. Replit DB is a
convenient dictionary. It has no relations, no indexes, no transactions and no
way to query by anything except the key. Agent reaches for it because it needs
zero setup, and it works fine right up until you have users whose records relate
to other users' records.
Signs it is time to move:
- You are storing JSON arrays under a key and filtering them in application code.
- Two requests can race and one overwrites the other's change.
- A "list all" operation is getting visibly slower.
Moving is a one-off script: read every key, insert into a Postgres schema that models the relations properly, verify counts, switch the connection. Nothing is lost, and the app gets indexes, constraints and transactions for free. Our database and query optimisation work is mostly this, and the shape of the resulting schema matters more than which Postgres host it lands on.
Decision two: the hosting
Replit Deployments come in two shapes, and Agent picks one for you:
| Autoscale | Reserved VM | |
|---|---|---|
| Idle behaviour | Scales to zero; the next request waits for a cold start | Always on |
| Cost | Per compute unit — grows with traffic | Fixed monthly |
| Good for | Low, bursty traffic; internal tools | Steady traffic; anything latency-sensitive |
| Watch for | Cold starts hurting real users; bill climbing with success | Paying for idle |
Neither is wrong. What is wrong is not knowing which you are on and why.
Stay on Replit when traffic is modest, cold starts are not hurting anyone you charge, and the bill is a rounding error. Many apps live here happily and moving them would be work for its own sake.
Move when one of three things is true: a cold start is the first thing a paying user experiences, the compute bill is growing faster than revenue, or you need something the platform does not offer — a specific region, a long-running background worker, a compliance requirement about where data lives.
If you move, the destination for an Express or Flask app with a Postgres database is usually Fly.io, Railway or Render, all of which take a Dockerfile or detect the framework directly. The move is:
- Export the repl to GitHub if it is not already there.
- Add a
Dockerfileor let the host detect the runtime. - Recreate every secret from Replit Secrets on the new host.
- Point the app at the database (moved or not — see decision one).
- Deploy to a preview URL, test with a real account, then switch DNS.
The app does not change. What changes is that you now have a deploy pipeline, preview environments and a rollback, which Replit's one-click deploy never gave you — deployment and CI/CD is the longer version of what that buys.
Whichever you choose: watch it
Replit's console shows logs while you are looking at it. A production app needs someone told when it breaks at 3am. Add error tracking and an uptime check on the paths that matter — sign-in, checkout, whatever earns money — and route the alert somewhere you will see it. This applies identically on Replit and off it.
The order to do it in
- Fix the routes, the CORS and the session secret. This is a day's work and it is the part that can hurt you tomorrow.
- Introduce migrations, so the next change is recorded.
- Measure: cold-start latency as a real user sees it, and the monthly bill against revenue.
- Decide on data and hosting from the measurements, not from a feeling.
- Add monitoring wherever you end up.
If you want the measuring and the ranked list done for you, the free launch audit is a senior engineer reading your repl and coming back within 48 hours with each finding reproduced and a fixed price against each fix. What we fix in Replit apps covers what else we typically find.


