AI-app werkt in de preview, maar niet na het deployen

De preview van je AI-builder regelde dingen die je eigen host niet weet: de omgevingsvariabelen, de toegestane adressen voor inloggen en CORS, en een regel die elke route naar je app stuurt. Zet de variabelen bij je host en bouw opnieuw, zet je domein in de redirect-lijst van je auth, en stel een rewrite naar index.html in. Faalt de build zelf, draai hem dan lokaal met dezelfde commando’s als je host.

Damian Madronal Munoz, oprichter

6 min leestijd

Een app die in de preview werkt en na het deployen niet, is bijna nooit kapot. Hij mist iets wat de builder voor je regelde zonder het te zeggen. Hieronder de symptomen, waar ze vandaan komen, en per oorzaak hoe je hem vindt en oplost, in de volgorde waarin je ze het snelst uitsluit.

Herken je dit?

  • Op je eigen domein zie je een witte pagina, terwijl in de preview alles werkt.
  • In de console van je browser staat supabaseUrl is required of een fout over een ontbrekende key.
  • Een bevestigingsmail of een link om je wachtwoord te herstellen opent localhost:3000 of het adres van de preview.
  • De homepage werkt, maar wie op /dashboard ververst of een link deelt, krijgt een 404.
  • In de console staat blocked by CORS policy zodra de app een functie of API aanroept.
  • Je host meldt een mislukte build, met een fout die de preview nooit liet zien.

De preview regelde wat je host niet weet

Een AI-builder draait je app in zijn eigen omgeving. Daar staan je Supabase-URL en je key al klaar, staat het preview-adres al in de lijsten van toegestane adressen, en krijgt elke route vanzelf je app terug. De preview is vaak ook een ontwikkelserver, die meer door de vingers ziet dan een productiebuild.

Je host begint leeg. Hij kent geen variabelen, staat in geen enkele lijst, en zoekt bij /dashboard naar een bestand met die naam. Elk symptoom hierboven komt uit een van die verschillen.

Zo vind en los je het op, per oorzaak

1. Omgevingsvariabelen die de builder invulde

Vite zet variabelen die met VITE_ beginnen tijdens de build letterlijk in je JavaScript; Next.js doet hetzelfde met NEXT_PUBLIC_. Ze moeten dus bij je host staan voordat hij bouwt, en na elke wijziging bouw je opnieuw. Een .env-bestand dat in .gitignore staat, komt nooit bij je host aan.

Een Lovable-app met een Supabase-backend leest er drie: VITE_SUPABASE_URL, VITE_SUPABASE_PUBLISHABLE_KEY en VITE_SUPABASE_PROJECT_ID. Andere builders gebruiken andere namen. Kijk in je eigen code welke het zijn.

grep -rhoE "import\.meta\.env\.[A-Z_]+|process\.env\.[A-Z_]+" src/ | sort -u
Toont elke variabele die je code leest. Elke naam in deze lijst moet bij je host staan.

Controleer daarna of de waarden in de build terechtkomen.

npm run build
grep -o "https://[a-z0-9]*\.supabase\.co" dist/assets/*.js | sort -u
Bouwt de app en zoekt de Supabase-URL in het resultaat. Geen uitkomst betekent dat de URL leeg was tijdens de build.

Geheime keys, zoals die van OpenAI of Stripe, horen niet in deze lijst. Die zet je als secret bij de server of de functie die ze gebruikt, zonder voorvoegsel. Staat er toch een met VITE_ ervoor, lees dan eerst API-key gelekt.

2. Inloglinks die naar de preview of localhost wijzen

Supabase stuurt bevestigingsmails, magic links en wachtwoordlinks naar de Site URL, tenzij je app een ander adres meegeeft dat in de lijst met Redirect URLs staat. De Site URL staat standaard op http://localhost:3000. Beide vind je in het dashboard bij Authentication, onder URL Configuration.

Site URL:
  https://www.jouwdomein.nl

Redirect URLs:
  https://www.jouwdomein.nl/**
  https://jouwdomein.nl/**
  http://localhost:5173/**
Je live adres als Site URL, en elk adres waar de app draait in de lijst. De laatste regel is voor lokaal werken met Vite.

Zoek daarna in je code naar adressen die de builder er vast in zette.

grep -rn "lovable.app\|localhost:\|redirectTo" src/
Vindt vaste preview-adressen en elke plek waar je app een redirect meegeeft.

Staat er bij redirectTo een vast adres, vervang het dan door window.location.origin met het pad erachter. Log je in met Google of een andere provider via je eigen Supabase-project, dan blijft de callback bij die provider het adres van Supabase. Daar hoeft je domein niet bij.

3. Een 404 bij verversen of een gedeelde link

Een app uit een Vite-builder is één pagina, index.html, en de router in de browser bepaalt wat je ziet. Klik je in de app naar /dashboard, dan gaat het goed. Ververs je daar, dan vraagt de browser je host om een bestand dat /dashboard heet, en dat bestaat niet. De oplossing is een rewrite die elk onbekend pad naar index.html stuurt, met status 200.

# Vercel: vercel.json in de hoofdmap van je project
{ "rewrites": [{ "source": "/(.*)", "destination": "/index.html" }] }

# Netlify: public/_redirects
/*    /index.html   200

# Apache: .htaccess naast index.html
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.html [L]
Drie hosts, dezelfde regel: bestaat het bestand niet, geef dan index.html terug.

Lovable-projecten die vanaf 13 mei 2026 zijn aangemaakt, gebruiken TanStack Start met een eigen server. Daar hoort deze regel niet bij: zo’n app moet op een host die een server draait. Hoe je ziet welke je hebt, staat in Lovable-app exporteren en zelf hosten.

4. CORS-fouten bij functies en API’s

CORS is de afspraak waarmee een server de browser vertelt welke websites hem mogen aanroepen. In de preview kwam het verzoek van een adres dat al was toegestaan; jouw domein staat er nog niet bij. Een Supabase Edge Function moet het voorverzoek van de browser (OPTIONS) zelf beantwoorden, en elk antwoord moet de headers meekrijgen.

const corsHeaders = {
  'Access-Control-Allow-Origin': 'https://www.jouwdomein.nl',
  'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
  'Access-Control-Allow-Methods': 'POST, OPTIONS',
}

Deno.serve(async (req) => {
  if (req.method === 'OPTIONS') {
    return new Response('ok', { headers: corsHeaders })
  }

  // ... wat je functie doet

  return new Response(JSON.stringify({ ok: true }), {
    headers: { ...corsHeaders, 'Content-Type': 'application/json' },
  })
})
Een Edge Function die het OPTIONS-verzoek beantwoordt en verzoeken van jouw domein toestaat.

Een CORS-melding is vaak een andere fout in vermomming. Een functie die niet gedeployd is, of die crasht omdat een secret ontbreekt, antwoordt zonder deze headers, en dan meldt de browser CORS. Kijk daarom eerst in de logs van de functie. Werk je met de GitHub-koppeling van Lovable: gewijzigde Edge Functions die via GitHub binnenkomen, deployt Lovable niet vanzelf.

5. Een build die faalt bij je host

Je host draait npm run build, controleert soms de types, en werkt meestal op Linux. Daar zijn Header.tsx en header.tsx twee verschillende bestanden, dus een import met de verkeerde hoofdletter werkt op je Mac en faalt op de host. Draai lokaal precies wat je host draait.

npm ci            # installeert exact wat in package-lock.json staat
npm run build     # dezelfde build als je host
npm run preview   # toont de build zoals hij live komt
Dezelfde stappen als je host. Faalt de build, lees dan de eerste fout, niet de laatste.

Zet ook de Node-versie vast in package.json, met een regel als "engines": { "node": "22.x" }, zodat je host dezelfde versie gebruikt als jij.

Waar het daarna nog misgaat

  1. Een variabele aanpassen zonder opnieuw te bouwen. De oude waarde staat nog in de JavaScript die live staat. Start bij je host een nieuwe deploy; verversen in de browser doet niets.
  2. De redirect-lijst te ruim maken. Met een patroon dat elk domein toestaat, werkt inloggen overal, en mag een inloglink ook naar een adres dat niet van jou is. Noem elk adres dat je gebruikt, en zet ** alleen achter je eigen domein.
  3. De 404 wegwerken met een foutpagina. Sommige hosts laten je index.html als 404-pagina instellen. Bezoekers zien dan je app, maar je host stuurt status 404 mee, en zoekmachines slaan die pagina’s over. Waarom dat voor Google telt, staat bij AI-website niet gevonden in Google.
  4. De host laten bouwen wat de builder schrijft. Met de GitHub-koppeling van Lovable of Bolt wordt elke wijziging in de builder een commit. Bouwt je host die branch automatisch, dan gaat elke prompt meteen live. Laat de host een aparte branch bouwen, en zet daar alleen op wat je getest hebt.

Wanneer je hulp nodig hebt

Werkt de app na deze stappen nog steeds niet, of draait hij wel maar weet je niet of hij dichtzit, laat dan iemand meekijken. Bij je AI-app laten afmaken zetten we hem live op je eigen domein en hosting, samen met de onderdelen die nog ontbreken. Wil je eerst weten wat er openstaat voordat er echte gebruikers in komen, begin dan met de securitycheck.

Kies je nieuwe hosting, neem dan meteen een Europese regio: wat dat vraagt, staat bij je app naar Europese hosting en in wat AI-tools niet voor je regelen. Je database test je met Supabase RLS controleren; wat RLS is, lees je in Row Level Security uitgelegd. De problemen die we in AI-sites verder vaak zien, staan in je website is met AI gebouwd, en alle gidsen over apps op AI-apps afmaken en beveiligen.

Veelgestelde vragen⁠

Waarom werkt inloggen wel op het adres van de builder en niet op mijn domein?

Omdat het adres van de builder in de lijst met toegestane redirect-URL’s staat en jouw domein niet. Zet je domein erbij, en zet de Site URL op je live adres in plaats van op localhost of de preview.

De build is gelukt, maar ik zie een witte pagina.

Open de console van je browser. Staat er dat de Supabase-URL ontbreekt, dan was een variabele leeg tijdens de build. Laden de JavaScript-bestanden niet, kijk dan of je app in een submap staat: dan moet de instelling base in je Vite-configuratie naar die map wijzen.

Werkt dit ook voor een app uit Cursor of Claude Code?

Ja. Die draaien eerst op je eigen laptop, met een .env-bestand dat niet meegaat naar je host. De oorzaken en de oplossingen zijn dezelfde.

Kan de builder dit niet zelf oplossen?

Soms. Geef hem dan de exacte foutmelding uit de console of de buildlog, en de naam van je host. Zonder die informatie raadt hij, en een geraden oplossing voor een CORS-fout zet vaak meer open dan nodig.

Verder lezen⁠

Een app die je met Lovable, Bolt, v0, Cursor of Claude bouwde: dichtzetten, afmaken en online houden.

Wij zetten hem live op je eigen domein⁠

De kennismaking is gratis. We reageren binnen één werkdag.