DEPLOYMENT GUIDE

How to deploy a vibe-coded app.

Uploading is the short part. The rest is knowing what your app needs, getting the address and HTTPS right, and testing what happens when someone installs it, goes offline or comes back to an old tab.

For apps built with a coding agent. Platform facts link to primary documentation, checked 6 November 2026. Plan limits and prices change; read the current provider page before you commit.

What “live” actually means

Between a working folder and a link you can send, six things happen: the app is built, hosted, configured, routed, observed and reverted. A host covers the first four. Noticing errors and going back to an earlier version stay with you.

So the first decision is not which vendor. It is what your app needs at runtime. A build with no server is a different job from a server with a database.

First: what does your app need?

What you builtHow to tellWhat it needs
Frontend onlyVite, React or plain HTML. Data lives in the browser or comes from a public API you do not run.A static host. Build once, upload the output folder.
Server codeAPI routes, a long-running process, websockets, or a secret that must never reach the browser.A host that runs your server or serverless functions.
DatabaseA SQLite file, or a connection string to a database on the same machine.A persistent disk, or a managed database service.

Common deployment failures include: a hard-coded localhost address, an environment variable that was never set on the host, and a request blocked by CORS. All three look like “the deploy failed”. None of them are the deploy.

A SQLite file on a serverless host loses data, because requests can land on a fresh filesystem. Use a managed database, a host with a persistent disk, or keep the app local-only on purpose.

The shortest Vercel path that actually works

Vercel supports Vite as a framework preset. The build is npm run build, the output folder is dist, and the install command is npm ci so the deployment uses your lockfile rather than whatever resolves today.

  1. Build it locally first. Run the commands below. Resolve local build errors before deploying, so the hosted build has the same verified inputs.
  2. Check the output. dist/index.html must exist. Assets that were supposed to be imported and are missing will not appear later.
  3. Connect the project. Import the repository at vercel.com/new, or run npx vercel for a preview and npx vercel --prod for the public one. The CLI signs you in through the browser the first time.
  4. Confirm the three settings: install npm ci, build npm run build, output dist.
  5. Open a deep link, then refresh it. If /reports returns 404 on reload, your client-side router needs a rewrite that sends unknown paths to index.html.
npm ci
npm run build
npx vercel --prod

Environment variables

With Vite, only variables prefixed VITE_ are placed in the browser bundle, and they are baked in at build time. That means two things: a key in a VITE_ variable is public, and changing a variable requires a new deployment. Server-only secrets belong in serverless function variables, never in the client bundle.

No terminal needed

A Bolt project can be exported as a .zip and deployed through Vercel Drop, without Git or the CLI (Vercel: deploy a Bolt project). Netlify also accepts Git, CLI, zip folders and AI-agent deploys (Netlify deploy docs). If your builder already hosts, check what it actually publishes: Lovable deploys the current version to its own URL and treats custom domains as a paid-plan feature (hosting, custom domains).

HTTPS and the address

A Vercel project gets an HTTPS address on *.vercel.app without configuration. If you use a domain you already own, add it under Project → Settings → Domains and create exactly the DNS records Vercel shows. The certificate is issued once DNS resolves; nothing here requires registering a new domain.

Then look for old http:// links inside the app. Browsers block insecure requests from an HTTPS page, and a single blocked request can break sign-in or data loading while the rest of the page looks fine.

Installable, but not an app-store listing

Browsers set their own installation rules. Serve over HTTPS and provide a web app manifest with a name and icons to control the installed experience (web.dev: installation). Chrome no longer requires a service worker that handles fetch() for the menu install, but offline use still needs one (Chrome install criteria update).

{
  "name": "Your app",
  "short_name": "App",
  "start_url": "/",
  "scope": "/",
  "display": "standalone",
  "background_color": "#f8f7f2",
  "theme_color": "#242421",
  "icons": [
    { "src": "/icons/icon-192.png", "sizes": "192x192", "type": "image/png" },
    { "src": "/icons/icon-512.png", "sizes": "512x512", "type": "image/png" },
    { "src": "/icons/maskable-512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" }
  ]
}

The icon files must exist at those paths. A maskable icon is cropped to a circle or squircle, so it needs its own artwork with padding rather than a reused square.

  • Chrome and Edge: eligible apps may show an install option in the address bar or browser menu, and may receive beforeinstallprompt. That is the event a custom install button can wait for.
  • Android Chrome: either the browser’s Install app menu item or the prompted install flow, depending on the browser version.
  • iPhone and iPad Safari: Share → Add to Home Screen. There is no prompt API on iOS (Apple: add a website to the Home Screen).
  • macOS Safari 17 and later: File → Add to Dock. Safari fires no beforeinstallprompt on macOS or iOS, so a button that silently waits for it does nothing there; show written instructions instead.

Installing from a browser does not create an App Store or Google Play listing. Store distribution has its own packaging and review process.

Write down exactly what works offline

Installing does not make an app work offline. Offline is a cache policy you choose. Decide the sentence now, because it becomes the claim you have to test: “after one online visit, the plan list and today’s timer work with no connection; sync and sign-in need a network.”

A service worker should precache the app shell under versioned cache names, delete stale caches on activation, and never cache authenticated responses or personal data in a cache shared with other users of the device. Then test it in two places: DevTools → Application → Service Workers → Offline with a reload, and a real phone in airplane mode after one online load.

Local data is a separate promise. Browser storage can be evicted, so ask for persistent storage where the platform offers it, and still give people an export they control.

Updates, without losing someone’s work

A deployment does not reach an open tab until its service worker updates. Wire that on purpose: detect a waiting worker, show “a new version is ready”, and activate only when every open tab says it is safe to reload. The SDK’s createUpdateController({ registration, canReload }) implements the page side of that acknowledgement protocol; the worker side stays yours.

Never call skipWaiting() unconditionally and never reload on page load. Both are how an unfinished form or a running timer disappears. Test by opening two tabs, starting an edit in one, deploying a new version, then applying the update: the refusal while work is unsaved is the feature, not a failure.

Recovery

Give people a file they can keep: export their data, then import it back into a cleared browser. If you use the SDK’s createSyncStore({ namespace, identity, localOnly: true }), local durability is handled for you; a real backend still needs its own authentication and revision checks. Test the restore, not just the export.

Check these before you share the link

  • Search the repository and the built output for keys and tokens, including history. Rotate anything that was ever committed.
  • Remember that everything in the browser bundle is public. A Supabase anon key can be there; the service_role key cannot. Turn on row-level security and test it while signed out.
  • Check every API route for authentication and server-side input validation. An endpoint that trusts the client is an open endpoint.
  • Name your own origin in CORS. Do not combine a wildcard with credentials.
  • Send the basic headers: a content security policy, X-Content-Type-Options: nosniff, a referrer policy, and frame-ancestors 'none' unless you embed the app.
  • Remove stack traces, debug panels and admin shortcuts from production error pages.

This is not theoretical. In February 2026 a researcher reported a Lovable-hosted app with 16 vulnerabilities, six of them critical, exposing data belonging to more than 18,000 people (The Register).

A second pair of eyes helps, and it is free to ask. The initial review covers the app page, pricing and data handling, and hosting basics during onboarding. It is best effort, with written findings, and it is not a security certification or a guarantee.

Request the free initial review

One real example

Wobble Goblin is a public static build of an app that started as a private household tool. It has bodyweight and Power programmes, 3D demos, plans, timers, history, settings and export/restore. There is no account, no cloud sync and no scheduled push: the records sit in the browser on the device.

That shape is why hosting it was simple: a Vite build, a static folder, a manifest and icons, and an install path per browser. Its publisher has confirmed offline use of the public app after the first online load. The catalogue marks it fully offline, with that first-load condition and on-device storage limits stated. To repeat the check, open it online once, turn the network off, open a plan and save a workout. Export a backup separately for device loss.

The whole thing, as a list

  1. Decide whether the app is static, needs a server, or needs a database.
  2. Get a clean build locally, with npm ci and npm run build.
  3. Deploy the output folder to a host that matches the answer above.
  4. Set only VITE_ values in the browser build; keep secrets server-side and redeploy after changes.
  5. Confirm HTTPS, the canonical address, and that a deep link survives a refresh.
  6. Add a real manifest, real icons at both sizes, and a maskable icon with padding.
  7. Decide the exact offline sentence, then test it online, offline and on a phone.
  8. Make updates explicit and test the refusal while work is unsaved.
  9. Export, clear, import. The restore is the part that matters.
  10. Run the security checklist before the link goes anywhere.

Next: the free SDK and skills handle install prompts, safe updates and local storage; the free initial review is for a second look. Agents can read /llms.txt and call /api/mcp after the app owner agrees.

Sources: Vercel deployment docs, Netlify and Lovable hosting docs, web.dev installation guidance, Chrome install-criteria notes and Apple’s Safari guidance. Checked 6 November 2026.