---
title: "Running an exported Lovable app on your machine | Lovable Field Guide"
url: https://prerenderlovable.com/blog/run-exported-lovable-app-locally
description: "Blank white screen after export? Run Lovable app locally without the guesswork: pick the right Node, fix the missing env vars, and handle the backend that stayed behind."
lang: en
---

Skip to content

# Running an exported Lovable app on your machine

Blank white screen after export? Run Lovable app locally without the guesswork: pick the right Node, fix the missing env vars, and handle the backend that stayed behind.

Published September 11, 2026 9 min read

You exported the code, ran `npm install && npm run dev`, and got a white page. The build is usually fine. What is missing is almost always the environment: migration write-ups report that Lovable’s secret store is write-only and that it is not part of the code export, so nothing in `.env` came with you, and a client library constructed with `undefined` for its URL throws before React renders a single node.

This is the order the problems actually arrive in: Node version, install, dev command, environment variables, backend. Work through them in that order and you will spend ten minutes on this instead of an afternoon. If you are further back in the process and still getting the code out, start with downloading your Lovable code (https://prerenderlovable.com/blog/download-lovable-code) or the GitHub sync route (https://prerenderlovable.com/blog/lovable-github-sync), and read the migration overview (https://prerenderlovable.com/blog/lovable-migration) for the parts that are not code at all.

## Which stack are you on? Check before you type anything

Lovable changed its default stack (https://encited.com/blog/lovable-seo-update) on 13 May 2026. New projects since then are TanStack Start with SSR on Cloudflare Workers (new Enterprise projects from 22 June 2026); older projects are React + Vite, client-rendered. Existing projects were not force-migrated (Lovable’s blog post on the change says every project already built continues to run exactly as before) so the repo in front of you could be either.

The dev command, the directory layout and the ways it fails locally are all different, so settle it first:

```
grep -E '"(@tanstack/react-start|@tanstack/react-router|react-router-dom|vite)"' package.json
```

- `@tanstack/react-start` present: the newer stack. There is a server. Some of your errors will only print in the terminal.
- `react-router-dom` and `vite` with no TanStack Start: the older client-rendered stack. Everything runs in the browser, and every error lands in the console.

Do not trust Lovable’s docs on this point. As of September 2026 the deployment and hosting ownership page (https://docs.lovable.dev/tips-tricks/deployment-hosting-ownership) still describes projects as “standard Vite + React”, and the `llms.txt` index says the same, while the SEO and upgrade pages describe the TanStack Start default. Your `package.json` is the only source that is right about your project.

## Node, install, and the “vite: not found” trap

Read `engines` in `package.json` first. If it names a version, match it. If it names nothing, install the current LTS release and move on: this is not the interesting part of the job, and guessing at an old Node major to “be safe” causes more problems than it solves.

```
node -v            # what you have
nvm install --lts  # or fnm, volta, asdf: whatever you already use
npm install
```

Two install failures account for most of the noise:

**`sh: vite: not found` when you run the dev script.** The `vite` binary is a devDependency. If your shell has `NODE_ENV=production` exported, or you installed with `--omit=dev` or `--production`, npm skips devDependencies and the binary is never written to `node_modules/.bin`. Check with `echo $NODE_ENV`, clear it, then reinstall from clean:

```
unset NODE_ENV
rm -rf node_modules
npm ci        # or npm install, if there is no lockfile
```

**Install succeeds, the dev server crashes on a native or peer dependency.** That is usually a lockfile built against a different Node or npm major than the one you are running. This is the case where throwing the lockfile away is the fix: `rm -rf node_modules package-lock.json && npm install`. Regenerating it is fine here, because you are the one who owns the repo now.

Then run the dev script that actually exists rather than the one you assume:

```
node -e "console.log(require('./package.json').scripts)"
```

Vite projects print a `http://localhost:5173` style URL. TanStack Start projects start a server too, and a 500 there will show up as an empty page in the browser and a stack trace in your terminal: check both windows.

## The real cause: no environment variables came with the export

Here is the part that catches almost everyone. Lovable’s code export contains code. It does not contain the contents of the secret store, and it cannot: migration guides that have done this repeatedly report that Lovable secrets are write-only, with the secrets view showing a name and a creation date and never a value. Lovable’s own Stripe documentation (https://docs.lovable.dev/integrations/stripe) describes the same design from the other direction: your `STRIPE_SECRET_KEY` is held as a backend secret and is “never in app code or chat”. Code you export is app code. The secret was never in it.

So your repo has code that reads variables, and nothing that defines them.

### Work out which variables the app actually needs

Do not guess from a blog post’s list, including this one. Derive it from your own source:

```
# Browser-side variables (Vite exposes these through import.meta.env)
grep -rhoE 'import\.meta\.env\.[A-Za-z0-9_]+' src | sort -u

# Server-side variables (TanStack Start server functions, scripts, config)
grep -rhoE 'process\.env\.[A-Za-z0-9_]+' . --include='*.ts' --include='*.tsx' \
  --include='*.js' --include='*.mjs' --include='*.json' \
  --exclude-dir=node_modules | sort -u

# Edge function secrets, if the repo carries function source
grep -rhoE "Deno\.env\.get\(['\"][A-Za-z0-9_]+" supabase 2>/dev/null | sort -u
```

Those three commands give you most of the names. They only match dotted access, so also grep for `import.meta.env` and `process.env` on their own to catch destructuring (`const { VITE_SUPABASE_URL } = import.meta.env`) and bracket lookups, and widen the last one past `supabase/` if function source lives elsewhere. On older React + Vite Lovable projects, the frontend set that migration write-ups most often name is `VITE_SUPABASE_URL`, `VITE_SUPABASE_PUBLISHABLE_KEY` and `VITE_SUPABASE_PROJECT_ID`, and missing them is exactly the reported cause of a blank local run. Your grep is still the authority.

### The prefix rule that silently breaks things

Vite only exposes variables to browser code when they are prefixed `VITE_`. Vite silently leaves an unprefixed `.env` variable out of `import.meta.env`, so the value arrives as `undefined` and whatever consumed it fails somewhere unrelated. This is the same for a TanStack Start app, because that stack builds with Vite too; anything read through `import.meta.env` in a component needs the prefix, while anything read through `process.env` inside a server function does not.

The rule has a security edge worth stating plainly: a `VITE_`-prefixed value is compiled into the bundle and shipped to every visitor. A Supabase URL and publishable key are meant to be public and are fine there. A Stripe secret key, a Resend key, or a service-role key is not, and prefixing one to “make it work” hands it to anyone who opens devtools.

### A .env.example worth committing

Write the file once, commit it, and keep it as the contract for anyone who clones the repo next, including future you on a new laptop.

```
# .env.example: commit this file. It holds names, never values.
# Copy to .env.local (gitignored) and fill in. Vite loads .env.local
# automatically and it overrides .env for local development.

# ---- Browser-visible. Compiled into the bundle. Public by definition. ----
VITE_SUPABASE_URL=https://your-project-ref.supabase.co
VITE_SUPABASE_PUBLISHABLE_KEY=
VITE_SUPABASE_PROJECT_ID=

# ---- Server-only. Never add a VITE_ prefix to any of these. ----
# Read via process.env in server functions, or set as platform secrets.
STRIPE_SECRET_KEY=
RESEND_API_KEY=
```

Then confirm `.gitignore` contains `.env.local` and `.env*.local` before your first commit. Do not copy secret values into a file that Git can see: the migration guides that flag the write-only secret store also flag committing an `.env` to GitHub as the mistake that follows it.

## The app still points at Lovable’s backend

Getting the dev server up does not give you a local application. The exported frontend keeps the backend URL it was built with, so `npm run dev` on your laptop is a local UI talking to a hosted backend that is still Lovable’s.

Three consequences, in the order they bite:

1. **You are reading and writing production data.** The app looks like a sandbox and isn’t. Before you click through flows, point the env at a scratch project, or at minimum know which tables you are about to mutate.
2. **Auth will probably fail before anything else does.** OAuth and magic-link flows validate the redirect URL, and `http://localhost:5173` is not on that list until someone adds it. For a Lovable Cloud project those settings live in the Cloud area of the Lovable editor; for a project connected to your own Supabase they live in the Supabase dashboard. Migration write-ups also report that Lovable Cloud handles Google sign-in through its own libraries rather than through Supabase directly, so treat social login as something to re-create rather than something to point at localhost.
3. **Edge functions are not running.** Their source may be in the repo, but deployment and secrets live on the platform, so any client call to a function URL hits the deployed copy or nothing at all. Running them locally means running them yourself (the Supabase CLI for Supabase-shaped functions) and supplying the secrets again.

Watch what happens in the network tab and the diagnosis is quick: a CORS failure means the backend is rejecting your origin, a 401 or 403 means the key in your env file is wrong or absent, and a request that never fires at all means the client was never constructed because its config was `undefined`.

## Debugging the blank screen properly

A white page is not one bug, it is four, and they are distinguishable in about sixty seconds.

**Read the console before anything else.** An exception thrown at module scope (the classic being a client library constructed with a missing URL, which throws a message naming the argument it wanted) kills the entire render tree and leaves the page blank with no React error boundary in sight. This is the single most common cause after an export, and the console names the variable for you.

**Then the network tab.** Look at the document request and the first script request. A 404 on the JS bundle means a wrong `base` in the Vite config or a page opened over `file://`. A 200 document with an empty body from a TanStack Start dev server means the crash happened on the server: go read your terminal.

**Now check whether the router mounted.** On the React + Vite stack, run this in the console:

```
document.getElementById('root')?.childElementCount
```

`undefined` means there is no `#root` at all: you are probably on TanStack Start, which renders the whole document, so read the terminal instead. Zero means React never rendered and the fault is upstream of routing: an import that threw, or a provider that crashed. A number greater than zero with a visually blank page means React rendered but your route matched nothing: check that the path you are on exists, and check for a `basename` set for a subpath deploy that does not match localhost.

**On TanStack Start, suspect browser-only code.** Anything touching `window`, `document` or `localStorage` at module scope runs during server rendering and throws there. Lovable’s own upgrade documentation (https://docs.lovable.dev/features/upgrade-to-tanstack-start) warns about exactly this class of problem, noting that some libraries only work in the browser and can break server rendering in ways the upgrade’s checks do not catch. Move the access into an effect, or import the module lazily on the client.

## What to do next

Commit the `.env.example` you just derived, along with a five-line README section listing where each value comes from. That single file is what turns “it works on my machine” into a repo someone else can clone.

After that, decide how far you are going. If localhost is a staging post on the way to your own infrastructure, the next problems are deployment, the backend and the pieces that never lived in the repo: self-hosting your Lovable app (https://prerenderlovable.com/blog/self-host-lovable-app) covers the hosting side, and moving from Lovable Cloud to your own Supabase (https://prerenderlovable.com/blog/lovable-cloud-to-supabase) covers the data and secret layers. If you plan to keep editing in Lovable as well as locally, read up on how GitHub sync behaves (https://prerenderlovable.com/blog/lovable-github-sync) before two tools start writing to the same branch.

Found something wrong or out of date? Lovable changes fast and we'd rather fix a guide than let it rot.

Disclosure: the team behind this guide also builds Encited, mentioned above.

## Frequently asked questions

Why is my exported Lovable app a blank white screen on localhost?

Most often it is a missing environment variable. Migration guides that have done this repeatedly report that Lovable's secret store is write-only, and the code export does not include it, so no .env file comes with your repo. A client-side library that is constructed at module scope with an undefined URL or key throws before React ever renders, leaving an empty root element. Open the browser console first: the error is almost always sitting there.

Which environment variables does my Lovable app need?

There is no universal list, because it depends on what the app integrates with. Derive it from the code: grep the repo for import.meta.env and process.env references and collect every variable name they use. On older React + Vite Lovable projects, migration guides commonly report VITE_SUPABASE_URL, VITE_SUPABASE_PUBLISHABLE_KEY and VITE_SUPABASE_PROJECT_ID as the frontend set.

Why does npm run dev say 'vite: not found'?

The vite binary is a devDependency, so it is missing if the install skipped dev dependencies, which happens when NODE_ENV is set to production or you installed with an --omit=dev flag. Delete node_modules, unset NODE_ENV, and run npm install again. A lockfile built against a different Node or npm major causes a different failure: the install succeeds and the dev server then crashes on a native or peer dependency.

Can I copy my secrets out of Lovable into a local .env file?

Not for anything in the secret store. Third-party migration guides report that Lovable secrets are write-only: the view shows a name and a creation date, never the value. For server-side keys like a Stripe or Resend key, generate a fresh key at the provider and put that in your local env file instead of trying to recover the original.

Will my local app still talk to the Lovable backend?

Often yes, at least partly. The exported frontend keeps pointing at whatever backend URL it was built against, so a local dev server can end up reading and writing your live data. Treat that as a hazard, not a convenience: use a separate project or test data before you start clicking around, and expect auth to fail until localhost is on the allowed redirect list.

Read next

## Keep going

- ### How to download your code from Lovable: https://prerenderlovable.com/blog/download-lovable-code
  Two routes to download Lovable code (a ZIP from the code editor and Git sync) the plan gate on both, and the backend pieces neither one exports.
- ### Getting your code out of Lovable: the full guide: https://prerenderlovable.com/blog/lovable-migration
  A Lovable migration is really three migrations: frontend, backend, hosting. What each export path moves, which one-way doors break sync, and the safe order.
- ### Self-hosting a Lovable app, and what you lose: https://prerenderlovable.com/blog/self-host-lovable-app
  How to self host Lovable app code on Vercel, Cloudflare Pages or Netlify: build settings, SPA rewrites, and the crawler pre-rendering you leave behind.
- ### Lovable GitHub sync: how it really works: https://prerenderlovable.com/blog/lovable-github-sync
  Lovable GitHub sync is two-way but only one branch at a time, and it can never import an existing repo. Every rule, limit and failure mode.

## Structured data

```json
{
  "@context": "https://schema.org",
  "@graph": [
    {
      "@type": "WebSite",
      "@id": "https://prerenderlovable.com/#website",
      "url": "https://prerenderlovable.com",
      "name": "The Lovable Field Guide",
      "description": "A working journal on Lovable SEO: getting apps built with Lovable indexed, ranked and cited, plus the migration and integration work that comes with it.",
      "publisher": {
        "@id": "https://prerenderlovable.com/#organization"
      },
      "inLanguage": "en"
    },
    {
      "@type": "Organization",
      "@id": "https://prerenderlovable.com/#organization",
      "name": "The Lovable Field Guide",
      "url": "https://prerenderlovable.com"
    },
    {
      "@type": "BlogPosting",
      "@id": "https://prerenderlovable.com/blog/run-exported-lovable-app-locally#article",
      "headline": "Running an exported Lovable app on your machine",
      "description": "Blank white screen after export? Run Lovable app locally without the guesswork: pick the right Node, fix the missing env vars, and handle the backend that stayed behind.",
      "datePublished": "2026-09-11T00:00:00.000Z",
      "dateModified": "2026-09-11T00:00:00.000Z",
      "inLanguage": "en",
      "mainEntityOfPage": {
        "@type": "WebPage",
        "@id": "https://prerenderlovable.com/blog/run-exported-lovable-app-locally"
      },
      "author": {
        "@type": "Organization",
        "name": "The Lovable Field Guide",
        "url": "https://prerenderlovable.com"
      },
      "publisher": {
        "@id": "https://prerenderlovable.com/#organization"
      },
      "keywords": "run lovable app locally, lovable export blank page localhost, vite not found npm run dev lovable, lovable env variables missing, exported lovable code doesn't work",
      "articleSection": "Migration"
    },
    {
      "@type": "BreadcrumbList",
      "@id": "https://prerenderlovable.com/blog/run-exported-lovable-app-locally#breadcrumbs",
      "itemListElement": [
        {
          "@type": "ListItem",
          "position": 1,
          "name": "Home",
          "item": "https://prerenderlovable.com/"
        },
        {
          "@type": "ListItem",
          "position": 2,
          "name": "Migration",
          "item": "https://prerenderlovable.com/migration"
        },
        {
          "@type": "ListItem",
          "position": 3,
          "name": "Running an exported Lovable app on your machine",
          "item": "https://prerenderlovable.com/blog/run-exported-lovable-app-locally"
        }
      ]
    },
    {
      "@type": "FAQPage",
      "@id": "https://prerenderlovable.com/blog/run-exported-lovable-app-locally#faq",
      "mainEntity": [
        {
          "@type": "Question",
          "name": "Why is my exported Lovable app a blank white screen on localhost?",
          "acceptedAnswer": {
            "@type": "Answer",
            "text": "Most often it is a missing environment variable. Migration guides that have done this repeatedly report that Lovable's secret store is write-only, and the code export does not include it, so no .env file comes with your repo. A client-side library that is constructed at module scope with an undefined URL or key throws before React ever renders, leaving an empty root element. Open the browser console first: the error is almost always sitting there."
          }
        },
        {
          "@type": "Question",
          "name": "Which environment variables does my Lovable app need?",
          "acceptedAnswer": {
            "@type": "Answer",
            "text": "There is no universal list, because it depends on what the app integrates with. Derive it from the code: grep the repo for import.meta.env and process.env references and collect every variable name they use. On older React + Vite Lovable projects, migration guides commonly report VITE_SUPABASE_URL, VITE_SUPABASE_PUBLISHABLE_KEY and VITE_SUPABASE_PROJECT_ID as the frontend set."
          }
        },
        {
          "@type": "Question",
          "name": "Why does npm run dev say 'vite: not found'?",
          "acceptedAnswer": {
            "@type": "Answer",
            "text": "The vite binary is a devDependency, so it is missing if the install skipped dev dependencies, which happens when NODE_ENV is set to production or you installed with an --omit=dev flag. Delete node_modules, unset NODE_ENV, and run npm install again. A lockfile built against a different Node or npm major causes a different failure: the install succeeds and the dev server then crashes on a native or peer dependency."
          }
        },
        {
          "@type": "Question",
          "name": "Can I copy my secrets out of Lovable into a local .env file?",
          "acceptedAnswer": {
            "@type": "Answer",
            "text": "Not for anything in the secret store. Third-party migration guides report that Lovable secrets are write-only: the view shows a name and a creation date, never the value. For server-side keys like a Stripe or Resend key, generate a fresh key at the provider and put that in your local env file instead of trying to recover the original."
          }
        },
        {
          "@type": "Question",
          "name": "Will my local app still talk to the Lovable backend?",
          "acceptedAnswer": {
            "@type": "Answer",
            "text": "Often yes, at least partly. The exported frontend keeps pointing at whatever backend URL it was built against, so a local dev server can end up reading and writing your live data. Treat that as a hazard, not a convenience: use a separate project or test data before you start clicking around, and expect auth to fail until localhost is on the allowed redirect list."
          }
        }
      ]
    },
    {
      "@type": "HowTo",
      "@id": "https://prerenderlovable.com/blog/run-exported-lovable-app-locally#howto",
      "name": "Run an exported Lovable project on localhost",
      "step": [
        {
          "@type": "HowToStep",
          "position": 1,
          "name": "Identify the stack",
          "text": "Open package.json and look for @tanstack/react-start (the TanStack Start stack) or react-router-dom with a plain vite build (the older React + Vite stack). The dev command and the failure modes differ."
        },
        {
          "@type": "HowToStep",
          "position": 2,
          "name": "Install with the right Node",
          "text": "Use the Node version named in the engines field, or the current LTS if there is none. Make sure NODE_ENV is unset, then run npm install so devDependencies are installed."
        },
        {
          "@type": "HowToStep",
          "position": 3,
          "name": "Reconstruct the environment file",
          "text": "Grep the source for import.meta.env and process.env to collect every variable the code reads, write them into .env.example, then copy that to .env.local and fill in values."
        },
        {
          "@type": "HowToStep",
          "position": 4,
          "name": "Start the dev server",
          "text": "Run the dev script from package.json and open the URL it prints. Do not open the built index.html from the filesystem."
        },
        {
          "@type": "HowToStep",
          "position": 5,
          "name": "Debug from the browser console",
          "text": "On a blank screen, read the browser console, then the network tab, then check whether the root element has any children. Each points at a different cause."
        }
      ]
    }
  ]
}
```