When should you migrate a vibe-coded app?
Learn when to migrate a vibe-coded app by comparing auth, database transfer, secrets, domain cutover, downtime, cleanup, and rollback.

Moving a generated app before launch is cheaper and cleaner. Moving it after traction is better informed but much less forgiving. The right time depends less on whether the project started in Lovable, Bolt, v0, or Replit than on whether you can name and rehearse every stateful boundary the current platform owns.
I treat launch as the point where identity, data, and a public domain become promises to users. Before that point, a broken migration costs developer time. After it, the same mistake can lock out customers, lose writes, invalidate sessions, or send traffic to two different versions of the product. Traction gives you evidence about what deserves to survive, but it also turns an ordinary code move into an operational change.
Do not decide from the size of the source tree. A small app with managed authentication and a live database can be harder to move than a large static site. Decide from ownership: who controls the repository, user identities, database, secrets, files, scheduled work, domain, deployment, and rollback path?
Before launch, migration buys freedom
Migrating before launch is usually the better choice when the current platform cannot meet a known requirement for ownership, deployment, data location, or maintainability. You still have room to change schemas, replace authentication, rename environment variables, and reset test data without negotiating with users.
This stage is especially attractive when the app has only seeded accounts and disposable records. You can export the code, build it in a clean environment, recreate the database from migrations, and discover which pieces were implicit in the original workspace. Every failure is useful because it exposes a dependency before that dependency carries customer data.
The cheap timing does not make the work optional. Generated projects often run because their original platform injects configuration, supplies a database URL, hosts functions, or understands a build convention. A source export proves that you possess files. It does not prove that another host can build and run the same system.
Before launch, I require a clean-room test. A teammate who did not create the project gets only the repository, a written secret list with safe development values, and the setup instructions. If that person cannot reach a working login, create a record, and run the main user journey, the project is not portable yet.
There are also good reasons to delay. An early prototype may change its data model every day, and migration work can be thrown away with the next product decision. If the current platform supports the intended launch, source export, deployment, custom domains, and a credible rollback path, learning from a small release can be worth more than polishing infrastructure for a product nobody wants.
The decision before launch is therefore not "Can we move?" It is "Does moving remove a known launch risk, or are we paying to preserve guesses?" Migrate for a concrete constraint. Do not migrate merely because conventional infrastructure feels more respectable.
After traction, evidence comes with obligations
Migrating after traction makes sense when real usage has exposed needs that the original setup cannot satisfy, but the plan must preserve every public promise already in use. You now know the hot paths, the actual data volume, the background jobs users trigger, and which integrations matter. That evidence can prevent an expensive move toward an imagined architecture.
The obligations are equally concrete. Existing passwords must still work or users need a controlled reset path. Database identifiers must remain stable if URLs, invoices, webhooks, or foreign keys expose them. Uploaded files need a transfer plan. Email links and OAuth callbacks must point to the right domain. Writes made during the copy must reach the new database or be deliberately paused.
Traction is not a single threshold. Ten active customers who use the app for payroll create more migration risk than ten thousand readers of a static catalog. Count state and consequences, not accounts. Ask how much data changes per minute, how costly a duplicate action is, how quickly support can reach every affected user, and whether the business can tolerate a maintenance window.
This is also the stage where teams confuse observed demand with architectural permission. More users do not automatically justify a rewrite. If the exported application is understandable and the current services can be separated one boundary at a time, an incremental migration is safer than replacing the whole stack.
I want a written ownership map before approving a post-traction move:
- Source repository and build process
- User directory and active sessions
- Primary database, files, and backups
- Secrets, scheduled jobs, and outbound webhooks
- Domain, email sender records, monitoring, and rollback authority
Any blank item is a blocker, not a detail for cutover night. The platform name matters only when it changes how you export or reconfigure one of these assets.
Authentication is an identity migration
Authentication should be treated as a transfer of identities and trust rules, not as a login screen that can be rebuilt later. The visible form is the easy part. Password hashes, provider subject IDs, verified email state, multifactor enrollment, recovery methods, sessions, and authorization roles carry the real continuity.
First determine whether the app owns a user table or delegates identity to a managed service. If you can export users, inspect which fields are available and whether password hashes can be imported into the destination. Hashes are not interchangeable just because both systems call them hashes. The destination must support the exact algorithm and parameters, or every password needs a reset.
Social login creates another identity seam. OAuth providers usually return a stable provider-specific subject identifier. If the new implementation matches accounts only by email, it can merge people incorrectly when addresses change or providers return different aliases. Preserve the tuple of issuer, provider subject, and local user ID. Re-register callback URLs before cutover, then test both a new login and an existing account.
OWASP's Session Management Cheat Sheet recommends renewing the session identifier after a privilege change. A migration is not itself a privilege change, but the advice exposes an important boundary: session state is security state. Trying to serialize opaque cookies from one authentication stack into another is usually a bad bargain. Keep the old verifier temporarily if you fully understand it, or expire sessions and tell users they must sign in again. Never silently accept a cookie the new service cannot validate.
Cookie scope can break an otherwise correct move. Check the cookie name, domain, path, Secure, HttpOnly, and SameSite attributes produced by the new host. MDN's Set-Cookie reference explains that a cookie with a Domain attribute is available to that domain and its subdomains, while an omitted domain restricts it to the host that set it. That distinction matters when the old app used one host for the web interface and another for its API. Test in a fresh browser profile so an old cookie cannot make the new flow appear healthy.
Authorization deserves a separate comparison. A user may authenticate successfully while losing an organization membership, admin role, subscription entitlement, or row-level policy. Export a sample of accounts with different roles and write expected-access tests before moving data. A login success page proves almost nothing.
For a pre-launch migration, I prefer replacing the identity system now and deleting the test users. For a post-traction migration, choose one explicit continuity strategy:
- Import compatible password hashes and preserve provider IDs.
- Keep the old identity service while the application moves.
- Require a reset with expiring, single-use tokens.
- Run a short dual-read bridge with one authority for writes.
Do not run two writable user directories. Conflicting email changes and account deletion requests will turn that convenience into an incident.
Database transfer must preserve meaning
A database migration succeeds only when the destination preserves constraints, identifiers, timestamps, relationships, and every write accepted during the move. Row counts are a weak check. Two databases can contain the same number of rows while disagreeing about money precision, time zones, uniqueness, null handling, or foreign keys.
Before launch, rebuild the database from versioned migrations rather than copying a development database. Seed only records the application needs. This test proves that schema history is complete and that the app does not depend on tables somebody created manually in a hosted console.
After traction, separate the schema move from the live-data move. Record the source engine and version, extensions, collations, generated columns, triggers, row-level policies, sequences, and large objects. If the destination uses a different database engine, treat it as an application migration as well. SQL syntax is the smallest part of that change; transaction behavior and type semantics cause the ugly surprises.
The PostgreSQL documentation describes pg_dump as a consistent export that does not block readers or writers. That is useful, but teams often overread the promise. A consistent snapshot does not include writes committed after the snapshot began. You still need a change capture method, a final write pause, or a maintenance window to close that gap.
Use a reconciliation query whose output can be saved with the cutover record. This fragment checks counts, identifier bounds, and update windows for three important tables:
SELECT 'users' AS table_name, count(*) AS rows,
min(id)::text AS min_id, max(id)::text AS max_id,
max(updated_at) AS newest_update
FROM users
UNION ALL
SELECT 'projects', count(*), min(id)::text, max(id)::text, max(updated_at)
FROM projects
UNION ALL
SELECT 'orders', count(*), min(id)::text, max(id)::text, max(updated_at)
FROM orders;
Run it on both sides and investigate every difference. Then test domain invariants that counts cannot see: no order points to a missing user, balances match their ledger, every file record has an object, and uniqueness rules reject the same duplicates.
Backups need a restore test. A successful export file is only evidence that a command finished. Restore it into an empty destination, run the application against it, and time the process. That measured restore time tells you whether rollback by restoration is realistic or merely comforting.
File storage often hides behind database rows. An exported uploads table may preserve object names while the actual objects remain in a platform-managed bucket. Copy the bytes, checksums, content types, access rules, and ownership metadata, then sample downloads through the application rather than the storage console. If URLs contain signed tokens or the old host name, regenerate them instead of copying stale URLs. Treat user uploads as state in the same cutover window, especially when users can replace a file while the database copy runs.
Environment variables reveal hidden architecture
Environment variables should be converted from an inherited bag of strings into a named contract for each environment. Missing variables cause obvious failures. More dangerous variables contain plausible but wrong production values, such as a test payment key, an old webhook secret, or a callback origin that sends users back to the former host.
Inventory variables from the code, platform settings, build configuration, serverless functions, scheduled jobs, and deployment system. Do not copy the whole old environment into the new host. Classify each value by owner, sensitivity, scope, rotation method, and whether it is read at build time or runtime.
A compact manifest makes the boundary reviewable:
DATABASE_URL runtime secret owner=backend rotate=yes
PUBLIC_APP_ORIGIN build public owner=web rotate=no
SESSION_SIGNING_KEY runtime secret owner=security rotate=yes
MAIL_SENDER runtime public owner=ops rotate=no
WEBHOOK_SECRET runtime secret owner=backend rotate=yes
The build/runtime distinction matters in React-style front ends. A value embedded during the build will not change when somebody edits a runtime setting. Rebuild the client and inspect the delivered bundle for public configuration. Never put a secret into a variable merely because its name starts with a framework's public prefix.
Rotate secrets during a post-traction migration when the destination can support an overlap period. For webhook verification or session signing, accept the old and new secret briefly while issuing only the new one. Remove the old value after the maximum delivery or session window. If a provider supports only one secret, coordinate the switch with the final cutover and make that dependency explicit in the runbook.
Pre-launch, delete unused variables and fail startup on missing required values. After traction, add observability before cleanup so you can see whether an apparently obsolete integration still receives calls. Guessing from variable names is how teams disable the quiet monthly job that finance actually needs.
Compare values by environment, but never paste secrets into the migration document. Record secret names and version labels, then keep the values in the destination's secret store. Give the application identity permission to read only what that deployment needs. When a variable changes, record who changed it and which release consumed it. This small discipline answers the familiar cutover-night question: "Which database URL did we actually deploy?"
Domain cutover is a traffic-control change
A domain cutover should be designed so both the old and new deployments can safely receive traffic during DNS propagation. DNS does not flip everywhere at once, and lowering time to live shortly before the change does not affect resolvers that already cached the older value.
Several days before a planned move, lower the relevant record's TTL and confirm the authoritative response. Keep the old deployment healthy for at least the previous TTL plus a conservative resolver margin. Provision the certificate on the new host before directing traffic there, and verify the apex domain, www host, API subdomain, redirects, and IPv6 records separately.
The domain is only the front door. Update authentication callbacks, allowed origins, cookie domains, canonical URLs, webhook endpoints, email links, and any mobile deep-link configuration. Search the repository and platform settings for the old hostname. A redirect helps browsers, but it does not repair a strict OAuth callback mismatch or a webhook signed for the wrong endpoint.
Zero downtime is possible only if both versions can operate against compatible state. If the new release changes the database in a way the old code cannot read, DNS overlap creates failures. Use expand-and-contract schema changes: add the new column or table first, deploy code that understands both forms, move the data, then remove the old form after all traffic has left the old release.
For low-volume products, a short maintenance window can be safer than a complicated live replication setup. Say when writes will pause, return a proper maintenance response, drain background work, take the final copy, reconcile, switch traffic, and reopen writes. Read-only access may remain available if it cannot enqueue hidden work.
Rollback must have a data rule. Pointing DNS back is easy when no writes reached the destination. Once users have written to both sides, a DNS reversal can discard or fork data. Define the last safe rollback moment, and after that moment roll forward or reconcile changes instead of pretending traffic reversal restores consistency.
Watch the application from outside the new hosting account. Resolve the domain through more than one public resolver, request the certificate chain, load a page without a warm cache, submit one reversible transaction, and confirm that the resulting background work finishes. Host dashboards can report a healthy deployment while users receive an old DNS answer or a regional edge returns an older build. Keep a synthetic check running against both the public domain and a destination-specific test host until the overlap ends.
Source cleanup determines whether the move lasts
Source cleanup should remove platform coupling without erasing useful generated structure or triggering an unrelated rewrite. Generated code can be repetitive or awkward, but aesthetic dislike is not a migration requirement. Change what blocks independent builds, testing, security review, or future maintenance.
Start with provenance. Export the complete repository and retain license files, asset attributions, generated migrations, lockfiles, and configuration. Check whether secrets or platform tokens entered the Git history. Removing them from the latest file does not revoke them, so rotate exposed credentials and decide whether history rewriting is warranted.
Next, find platform-specific imports, proxy paths, database clients, authentication helpers, storage adapters, deployment files, and generated API endpoints. Replace them behind narrow application interfaces where practical. A repository-wide search is useful, but running the user journeys tells you which references still matter.
Dependency cleanup comes after the independent build works. Remove packages one at a time, regenerate the lockfile with the existing package manager, and run tests after each group. Do not upgrade the framework, replace state management, rename every component, and migrate hosting in the same change. That creates too many explanations for one failure.
Generated server code deserves extra scrutiny at trust boundaries. Trace every request from route to authorization check to database query, and verify that the server does not rely on a client-side visibility rule. Review upload limits, outbound request targets, error messages, and administrative routes. This is not a call to rewrite every generated handler. It is a focused check that the code still enforces access rules after platform middleware and managed proxies disappear.
The generated project also needs ordinary operational files: an example environment manifest with fake values, database migration commands, build and start instructions, health checks, and a description of background workers. Keep these instructions executable. A README that says "configure the database" only records that a database exists.
Pre-launch cleanup can include schema resets and large refactors because there is no compatibility promise. Post-traction cleanup should preserve public API shapes, identifiers, and user-visible behavior until the infrastructure move settles. Give the new deployment a quiet period before changing product behavior. When migration and redesign arrive together, support cannot tell whether a complaint comes from the move or the new feature.
Rehearsal turns downtime into a decision
A migration rehearsal should reproduce the production sequence with a recent sanitized data copy and produce measured timings, reconciliation output, and a tested abort point. A checklist copied from another project cannot tell you how long your database takes to restore or which job keeps writing after maintenance mode begins.
Use one operator to execute and another to observe, record times, and challenge skipped checks. For a small team, the second person can be the founder, but they need enough context to recognize a changed result. The person typing commands should not also be the only person deciding whether those commands worked.
A practical runbook has a strict order:
- Freeze unrelated deployments and record current versions, DNS values, and secret versions.
- Put writes into maintenance mode, drain queues, stop scheduled jobs, and record the final source watermark.
- Copy the remaining data, reconcile tables and domain invariants, then run authentication and core-journey tests.
- Switch traffic, verify certificates and callbacks, watch errors and queue depth, then reopen writes.
- At the declared checkpoint, either continue on the new system or execute the documented rollback data rule.
Before launch, rehearse by destroying the destination and rebuilding it from the repository. The target is reproducibility, so an empty database and fresh environment are more revealing than a production-shaped copy.
After traction, rehearse scale and concurrency. Copy enough representative data to expose slow indexes and long migrations. Replay safe read traffic if you have it, create synthetic writes with known identifiers, and verify that background jobs are idempotent before allowing retries. An email job that sends twice is not harmless just because the database stayed consistent.
Measure the write pause separately from the whole maintenance window. You can often perform bulk copying while the source remains live, then pause only for the delta and validation. If rehearsal shows that the delta cannot finish inside the allowed window, add replication or change capture. Do not discover that requirement with customers waiting.
Keep evidence after the move: source and destination versions, timestamps, row checks, smoke-test results, DNS answers, operator decisions, and the time old services were disabled. That record speeds up debugging and prevents the next migration plan from depending on somebody's memory.
Choose the stage by reversibility
The best migration stage is the one where the failure you can realistically cause is still reversible. Before launch, the product has little evidence but almost unlimited freedom. After traction, the product has evidence but carries state that must remain coherent throughout the move.
I use six decision tests:
- Migrate before launch if a known compliance, ownership, export, hosting, or architecture constraint blocks the intended release.
- Stay and launch if the platform meets current needs and the team would otherwise migrate only from anxiety.
- Migrate after traction if measured usage exposes a constraint and you can rehearse identity, data, and traffic continuity.
- Delay if you cannot export a restorable database, control the domain, enumerate secrets, or define write ownership.
- Prefer incremental separation when authentication or data can remain temporarily while compute and hosting move.
Lovable, Bolt, v0, and Replit can all produce projects whose portability depends on the exact services chosen, the plan in use, and the code generated at that moment. Inspect the actual repository and account controls. A vendor category does not answer whether your specific password hashes, database extensions, files, or deployment settings can move.
If you choose a new chat-based development environment, planning and rollback controls reduce the cost of separating the move into reviewable changes. Koder.ai supports source export, deployment and hosting, custom domains, snapshots and rollback, so a team can keep those ownership checks inside the migration plan without making the article's advice depend on one platform.
Set a migration budget before launch even if you decide to stay. Keep source under your control, version the schema, document the environment contract, and rehearse a restore. Those actions cost far less while the app is small, and they preserve the option to move when traction supplies a reason instead of a crisis.
If the team cannot perform that restore today, portability remains an intention rather than a property of the application.
FAQ
Should I migrate my generated app before launching it?
Migrate before launch when the current setup fails a known requirement for ownership, hosting, data location, or maintenance. If the platform meets the release requirements and the product is still changing daily, launching a limited version may teach you more than an early infrastructure move.
Is it risky to migrate an app after it has users?
Yes, because user identities, writes, files, callbacks, and scheduled work must remain consistent during the move. The risk becomes manageable when you rehearse with representative data, define one authority for writes, and document the last safe rollback point.
Can I move password hashes to a new authentication provider?
Only if the destination accepts the exact hash algorithm and parameters used by the source. Otherwise keep the old identity service temporarily or run a controlled password reset; never convert hashes as if they were plain encrypted passwords.
Will users need to sign in again after migration?
Often they should, especially when the new authentication stack cannot verify old session cookies safely. A clear sign-in request is better than a fragile compatibility layer that accepts session state nobody can fully validate.
How do I migrate a live database without losing writes?
Use replication or change capture, or pause writes for a final delta copy and reconciliation. A consistent snapshot covers one point in time, so it does not remove the need to handle commits that arrive after the snapshot begins.
How long should migration downtime be?
Rehearsal should determine it. Measure queue draining, the final data delta, validation, DNS switching, and smoke tests separately, then publish a window with enough margin for the slowest measured run.
When should I lower DNS TTL before a cutover?
Lower it several days beforehand and confirm the authoritative DNS response, because resolvers may retain the previous value until its old TTL expires. Keep the former deployment healthy through the overlap instead of expecting an instant global switch.
Should I refactor generated code while migrating?
Change the code that blocks an independent build, testing, security review, or operation. Save broad framework upgrades and aesthetic rewrites for later, because combining them with the infrastructure move makes failures harder to isolate.
Can I roll back by pointing the domain to the old host?
Only before the destination accepts writes, or when you have a tested way to replay those writes into the source. Once both databases diverge, changing DNS alone can lose data and is not a complete rollback.
What must I export from Lovable, Bolt, v0, or Replit?
Export the complete source and identify the database, users, files, secrets, jobs, domain settings, and deployment configuration that live outside it. Exact controls vary by project and plan, so verify the assets in your own account instead of trusting a generic platform comparison.