Záchrana · 9 min čtení
Aplikace z Lovable nebo Bolt funguje v náhledu, ale v produkci se rozbije: příčiny a řešení
Většina aplikací postavených s AI, které fungují v náhledu a v produkci selžou, padá ze stejné hrstky důvodů: proměnné prostředí zapečené při buildu, přesměrování přihlášení stále mířící na náhled, chybějící autorizace v databázi, webhooky, které nejsou idempotentní, CORS a žádný monitoring chyb. Každý z nich má konkrétní a ověřitelné řešení.
Autor BacklooPublikováno
Hlavní body
- Náhled problémy skrývá: jeden uživatel, testovací klíče, požadavky ze stejného originu, benevolentní nastavení přihlašování a žádný skutečný provoz.
- Klientské proměnné prostředí (`VITE_*`, `NEXT_PUBLIC_*`) se při buildu zapečou do bundlu — jejich změna na hostingu vyžaduje nový build a cokoli v nich je veřejné.
- Autorizace patří do databáze nebo na server. Skryté tlačítko není řízení přístupu; Row Level Security nebo kontroly na serveru ano.
- Platební webhooky musí ověřovat podpis a být idempotentní, protože poskytovatelé opakují doručení a posílají události vícekrát a mimo pořadí.
- Před spuštěním potřebujete monitoring chyb, vyzkoušenou obnovu ze zálohy, rate limity na drahých endpointech a migrace ve verzovacím systému.
Proč aplikace postavená s AI funguje v náhledu, ale v produkci se rozbije?
Protože náhled je přívětivější prostředí než produkce. V náhledu jste obvykle jediný uživatel, běžíte na testovacích klíčích, požadavky přicházejí ze stejného originu, přesměrování přihlášení míří na URL náhledu a nic zatím nebylo opakováno, souběžně spuštěno ani zneužito. Produkce změní všechno najednou — a kód s tím nikdy nepočítal.
AI nástroje na programování optimalizují to, co je vidět: obrazovka funguje, když ji proklikáte. Mnohem slabší jsou v neviditelných částech — co se stane při druhém doručení webhooku, co vrátí vaše API na nepřihlášený požadavek, co obsahuje bundle po buildu. To není důvod je přestat používat. Je to důvod tyto části vědomě zkontrolovat dřív, než přijdou skuteční uživatelé a skutečné peníze.
45 %
Níže jsou typy selhání, které kontrolujeme jako první, když nám někdo předá projekt z Lovable, Bolt, v0 nebo Cursoru, který „včera ještě fungoval“. Jsou seřazené zhruba podle toho, jak často způsobí výpadek nebo únik dat, ne podle toho, jak jsou zajímavé.
Proměnné prostředí a tajné klíče: čas buildu vs. běh aplikace
Nejčastější produkční chybou je proměnná prostředí, která chybí, je špatně, nebo se čte ve špatnou chvíli. Frontendové frameworky vkládají klientské proměnné do JavaScriptového bundlu při buildu aplikace, ne při jejím běhu. Pokud VITE_SUPABASE_URL nebo NEXT_PUBLIC_API_URL nastavíte či změníte na hostingu až po buildu, nasazený kód obsahuje starou hodnotu, dokud znovu nenasadíte.
- Proměnné s prefixem jsou veřejné. Cokoli v
VITE_*,NEXT_PUBLIC_*neboEXPO_PUBLIC_*skončí v bundlu v prohlížeči, kde si to může kdokoli přečíst. Pro anon klíč Supabase nebo publishable klíč Stripe je to v pořádku. Proservice_roleklíč Supabase, tajný klíč Stripe nebo klíč OpenAI je to únik. - Proměnné bez prefixu existují jen na serveru. Když klientský kód čte
process.env.STRIPE_SECRET_KEY, dostane v produkciundefined— a „oprava“ od AI často spočívá v přidání veřejného prefixu, čímž tajný klíč unikne. - Náhled a produkce potřebují oddělené hodnoty. Testovací klíče v produkci znamenají, že platby potichu nic nedělají; ostré klíče v náhledu znamenají, že vaše testy strhávají peníze ze skutečných karet.
- Tajné klíče commitnuté do gitu zůstávají v historii gitu. Smazat řádek nestačí. Klíč nejdřív zrotujte, teprve potom ho odstraňte.
Rychlá kontrola: sestavte aplikaci lokálně a ve výstupu vyhledejte cokoli, co vypadá jako tajný klíč.
npm run build
# Vite generuje do dist/, Next.js do .next/static
grep -rEo "(sk_live|sk_test|service_role|sb_secret)[A-Za-z0-9_.-]{0,12}" dist .next/static 2>/dev/null
grep -rEo "eyJ[A-Za-z0-9_-]{20,}" dist .next/static 2>/dev/null | head
# Druhý řádek hledá JWT. Každý nález dekódujte a zkontrolujte claim "role":
# "anon" v bundlu prohlížeče očekáváme; "service_role" je únik.
# Tato slova se mohou objevit i v kódu knihoven, takže každý nález prověřte, nepanikařte.Okrajové případy přihlašování, které se objeví až na skutečné doméně
Přihlašování se v produkci obvykle rozbije, protože poskytovatel autentizace si pořád myslí, že vaše aplikace běží na localhost nebo na URL náhledu. Odkazy pro potvrzení e-mailu, reset hesla i OAuth callbacky se skládají z nastavené URL webu a povoleného seznamu adres pro přesměrování. Pokud na něm vaše produkční doména není, uživatelé skončí v náhledu, na localhostu nebo na chybové stránce.
- Site URL u poskytovatele autentizace (např. Supabase Auth → URL Configuration) nastavená na produkční doménu.
- Povolený seznam přesměrování obsahuje produkční doménu a variantu s
www/ bezwww, kterou skutečně používáte — a žádný wildcard širší, než potřebujete. - OAuth aplikace (Google, GitHub, Apple) mají zaregistrovanou produkční callback URL a jejich obrazovky souhlasu jsou publikované, ne v testovacím režimu.
- E-mailové šablony používají produkční doménu a skutečnou odesílací doménu s nastaveným SPF a DKIM, jinak e-maily pro reset hesla končí ve spamu.
- Obnova session otestovaná: nechte záložku otevřenou déle, než je platnost access tokenu, a ověřte, že další akce stále funguje.
- Odhlášení otestované na druhém zařízení a chráněné stránky ověřují session na serveru, ne jen v React efektu.
Poslední bod je důležitější, než se zdá. Mnoho vygenerovaných aplikací „chrání“ stránku tak, že v prohlížeči přesměruje, když není přihlášený uživatel. Požadavek na data stránky ale stejně odejde a kdokoli, kdo zavolá API napřímo, data dostane. Tím se dostáváme k většímu problému.
Autorizace: kdo smí číst a zapisovat které řádky
Autorizace selhává potichu, a proto je nejnebezpečnější položkou tohoto seznamu. U Supabase nebo Firebase komunikuje prohlížeč s databází napřímo pomocí veřejného klíče. Jediné, co uživateli A brání číst data uživatele B, jsou pravidla samotné databáze — Row Level Security v Supabase, Security Rules ve Firebase. Pokud chybí nebo jsou napsaná jako „povolit vše“, každý řádek si přečte kdokoli, kdo otevře záložku Network.
Nejde o teorii. Chybějící RLS odhalilo data ve více než 170 aplikacích postavených v Lovable a dostalo označení CVE-2025-48757 (analýza). Podrobný průvodce na úrovni SQL jsme sepsali zvlášť: Checklist Supabase Row Level Security pro aplikace postavené s AI. Ve zkratce:
- Zapněte RLS na každé tabulce ve vystaveném schématu a pak napište jednu policy pro každou operaci (select, insert, update, delete).
- Klíč
service_role/ secret nikdy neposílejte klientovi; RLS úplně obchází. - Role ukládejte do claimů, které řídí server (
app_metadata), ne do metadat, která může upravit uživatel. - Testujte jako anonymní uživatel a jako druhý uživatel, ne jen sami za sebe.
Platby: webhooky musí být ověřené a idempotentní
Platby se v produkci rozbijí, protože šťastná cesta z náhledu — kliknout na Zaplatit, nechat se přesměrovat na stránku úspěchu, označit objednávku jako zaplacenou — neodpovídá tomu, jak platební poskytovatelé fungují. Přesměrování na stránku úspěchu lze přeskočit, zavřít nebo podvrhnout. Zdrojem pravdy je webhook a webhooky se doručují alespoň jednou, mohou přijít mimo pořadí a opakují se, když je váš endpoint pomalý nebo vrátí chybu.
Z toho plynou tři pravidla. Ověřte podpis pomocí surového těla požadavku. Zpracování udělejte idempotentní, aby duplicitní doručení nic nezměnilo. Rychle vraťte 2xx a pomalou práci přesuňte do fronty. Tady je minimální route handler v Next.js pro Stripe, který dělá všechny tři:
// app/api/stripe/webhook/route.ts (runtime Node.js)
import Stripe from "stripe";
import { createClient } from "@supabase/supabase-js";
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
// klient jen pro server: tajný klíč nikdy neopustí server
const db = createClient(process.env.SUPABASE_URL!, process.env.SUPABASE_SECRET_KEY!);
export async function POST(req: Request) {
const body = await req.text(); // surové tělo, ne req.json(): potřebné pro podpis
const signature = req.headers.get("stripe-signature") ?? "";
let event: Stripe.Event;
try {
event = stripe.webhooks.constructEvent(body, signature, process.env.STRIPE_WEBHOOK_SECRET!);
} catch {
return new Response("invalid signature", { status: 400 });
}
// stripe_events.id je primární klíč, takže druhé doručení se nepodaří vložit
const { error } = await db.from("stripe_events").insert({ id: event.id, type: event.type });
if (error?.code === "23505") return new Response("already processed", { status: 200 });
if (error) return new Response("storage error", { status: 500 });
try {
if (event.type === "checkout.session.completed") {
const session = event.data.object as Stripe.Checkout.Session;
await db.from("orders").update({ status: "paid" }).eq("checkout_session_id", session.id);
}
} catch (err) {
// uvolníme záznam, aby opakované doručení od Stripe mohlo událost zpracovat znovu
await db.from("stripe_events").delete().eq("id", event.id);
throw err;
}
return new Response("ok", { status: 200 });
}Dvě další věci lidi často zaskočí. Podpisový secret webhooku je jiný pro každý endpoint a každý režim, takže produkční endpoint potřebuje vlastní STRIPE_WEBHOOK_SECRET. A protože události mohou přijít mimo pořadí, aktualizujte stav podle objektu, který přijmete (nebo si ho znovu načtěte z API), ne podle předpokladu, že created vždy přijde před updated.
Migrace databáze a zálohy
Pokud vaše schéma existuje jen jako kliky v administraci a SQL od AI vložené do editoru, nedokážete ho spolehlivě znovu vytvořit, zrevidovat ani vrátit zpět. Dejte schéma do migračních souborů ve verzovacím systému a aplikujte je stejným způsobem do každého prostředí.
- Aktuální produkční schéma jednou stáhněte do migrací (u Supabase:
supabase db pull), commitněte ho a každou další změnu dělejte jako nový migrační soubor. - Migrace aplikujte nejdřív na stagingový projekt, teprve potom do produkce. Nikdy nenechte AI nástroj spouštět DDL přímo proti produkční databázi.
- Upřednostňujte aditivní změny: přidejte sloupec, doplňte data, přepněte kód a starý sloupec smažte až v pozdějším vydání.
- Znejte svou politiku záloh: jak často, jak dlouho se uchovávají a zda máte ve svém tarifu zapnutou obnovu k určitému okamžiku (point-in-time recovery).
- Proveďte jednu obnovu do zkušebního projektu. Záloha, kterou jste nikdy neobnovili, je naděje, ne záloha.
Monitoring chyb: zjistěte to dřív, než vám to řeknou uživatelé
Bez monitoringu chyb se o produkčních chybách dozvídáte od zákazníků, obvykle o dny později a bez stack trace. Přidejte nástroj na sledování chyb (Sentry nebo podobný) do frontendu i do všech serverových a edge funkcí, nahrajte source mapy, aby stack trace ukazovaly na skutečné řádky, a posílejte upozornění tam, kde je čte člověk.
Kolem rizikových cest — registrace, platebních webhooků, nahrávání souborů, volání AI — přidejte strukturované logy s ID požadavku, podle kterého lze vyhledávat. A na domovskou stránku a jednu API routu vyžadující přihlášení dejte jednoduchou kontrolu dostupnosti, abyste si všimli, že databáze neběží, i když statická stránka ano.
Rate limity a CORS
Rate limity
Každý endpoint, který vás stojí peníze nebo se dotýká přihlašování, potřebuje před spuštěním rate limit. AI endpointy jsou nejzřejmější: nechráněná routa, která přeposílá požadavky na API modelu, dokáže přes noc nasekat vysoký účet. Omezte požadavky na uživatele i na IP adresu, omezte velikost požadavku a jako pojistku nastavte v administraci poskytovatele modelu pevný měsíční limit útraty. Totéž udělejte pro registraci, přihlášení, reset hesla a cokoli, co posílá e-maily nebo SMS.
CORS
Chyby CORS se často objeví až v produkci, protože náhled servíroval frontend i API ze stejného originu. Řešením je odpovídat na preflight požadavek OPTIONS prohlížeče a vracet explicitní seznam povolených originů — ne všude nastavit Access-Control-Allow-Origin: *. Pamatujte, že CORS je pravidlo prohlížeče, ne zabezpečení: nezabrání curl ani skriptu volat vaše API, takže autorizace musí i tak proběhnout na serveru.
// Supabase Edge Function (Deno): explicitní seznam povolených originů + preflight
const ALLOWED = new Set(["https://app.example.com", "https://www.example.com"]);
Deno.serve(async (req) => {
const origin = req.headers.get("origin") ?? "";
const cors: Record<string, string> = {
"Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type",
"Access-Control-Allow-Methods": "POST, OPTIONS",
Vary: "Origin",
};
// neznámé originy nedostanou hlavičku Allow-Origin, takže prohlížeč odpověď zablokuje
if (ALLOWED.has(origin)) cors["Access-Control-Allow-Origin"] = origin;
if (req.method === "OPTIONS") return new Response(null, { status: 204, headers: cors });
// ...ověřte JWT uživatele a proveďte práci...
return new Response(JSON.stringify({ ok: true }), {
headers: { ...cors, "Content-Type": "application/json" },
});
});Checklist před spuštěním aplikace z vibecodingu
Projděte tento seznam dřív, než na aplikaci postavenou s AI pustíte produkční provoz. Každou položku ověříte během několika minut až hodiny; žádná z nich nevyžaduje přepis.
- Produkční build neobsahuje žádné tajné klíče (prohledejte bundle; dekódujte všechny JWT).
- Oddělené hodnoty proměnných pro náhled a produkci; klientské proměnné po změně znovu sestavené.
- Všechny commitnuté tajné klíče zrotované, ne jen smazané.
- Site URL, povolený seznam přesměrování a OAuth callbacky míří na produkční doménu.
- Reset hesla, potvrzení e-mailu a odhlášení otestované od začátku do konce v produkci.
- RLS zapnuté na každé vystavené tabulce, s policy pro jednotlivé operace; otestované jako anon i jako druhý uživatel.
- Chráněná data se kontrolují na serveru nebo v databázi, ne jen skrývají v UI.
- Platební webhook ověřuje podpisy, je idempotentní a používá produkční podpisový secret.
- Schéma v migračních souborech; změny se aplikují nejdřív na staging.
- Zálohy zapnuté a jedna obnova skutečně provedená.
- Sledování chyb na frontendu i backendu, se source mapami a upozorněními.
- Rate limity na přihlašování, e-mail/SMS a AI endpointech; limit útraty nastavený u poskytovatele modelu.
- Seznam povolených originů pro CORS místo
*; preflight ošetřený. - Kontrola dostupnosti na stránce i na API routě vyžadující přihlášení.
Pokud chcete rychle zjistit, kde vaše aplikace stojí, 60sekundová kontrola připravenosti ohodnotí stejné oblasti a řekne vám, na kterou se podívat nejdřív.
Jak vám Backloo může pomoct
Pokud checklist procházíte sami, nechte si tuto stránku otevřenou a postupujte shora dolů — pořadí je záměrné. Pokud byste to radši svěřili inženýrovi, naše záchrana AI aplikací začíná auditem přesně těchto oblastí za pevnou cenu, po kterém následují sprinty s pevným rozsahem, v nichž zpevníme, co je křehké. Po spuštění pokrývá monitoring, aktualizace a reakční doby údržba a SLA. V obou případech vám zůstává kód, repozitář i cloudové účty. Nejste si jistí, jestli je oprava vůbec správný krok? Přečtěte si záchrana, refaktoring, nebo přepis.
Otázky
Proč moje aplikace z Lovable funguje v náhledu, ale po nasazení ne?
Obvykle kvůli rozdílům mezi prostředími: klientské proměnné zapečené při buildu, přesměrování přihlášení stále mířící na náhled, CORS na novém originu nebo testovací klíče v produkci. Tyto čtyři věci zkontrolujte dřív, než začnete ladit kód aplikace.
Je bezpečné, že je můj anon klíč Supabase vidět v prohlížeči?
Ano, anon (publishable) klíč je navržený jako veřejný — ale jen pokud je na každé vystavené tabulce zapnuté Row Level Security se správnými policy. Bez RLS dává anon klíč přístup ke čtení a často i zápisu vašich dat. Klíč service_role (secret) se do prohlížeče nesmí nikdy dostat.
Musím aplikaci vygenerovanou AI přepsat, aby byla připravená na produkci?
Zřídka. Většina produkčních problémů v aplikacích postavených s AI je v konfiguraci, autorizaci a chybějících pojistkách, a to se dá opravit na místě. Přepis dává smysl jen tehdy, když datový model nebo architektura brání každé změně; viz náš průvodce záchrana, refaktoring, nebo přepis.
Co je idempotence webhooků a proč na ní záleží?
Idempotence znamená, že dvojí zpracování stejné události má stejný účinek jako zpracování jednou. Platební poskytovatelé doručují webhooky alespoň jednou a při chybách je opakují, takže bez ní můžete přístup udělit dvakrát, poslat duplicitní e-maily nebo započítat tržby dvakrát.