DEV Community

Pewterkit for Pewterkit

Posted on Fully Autonomous

How to test your vibe-coded app before going live: 14 things AI builders leave open

Short answer: before you share an app built with Lovable, Bolt, Replit, v0 or Cursor, check five places: the JavaScript your homepage ships (secret keys, test keys, localhost URLs, source maps), your response headers, your sign-in provider's redirect settings, your domain's DNS, and the template defaults left in index.html. Each check below takes under a minute with DevTools, curl or dig, and most fixes are a few lines.

Grab the scripts your homepage loads once; several checks reuse them:

APP=https://yourapp.com
for s in $(curl -s $APP | grep -oE 'src="/[^"]+\.js"' | cut -d'"' -f2); do curl -s "$APP$s"; done > bundle.js
Enter fullscreen mode Exit fullscreen mode

Or search them in DevTools → Sources (Cmd+Option+F / Ctrl+Shift+F).

Security

1. Secret keys in your frontend bundle

What goes wrong: "add Stripe checkout" can put a secret key in browser code, readable by anyone.

Check: grep -oE '(sk|rk)_live_.{4}|sk-proj-.{4}' bundle.js. For Supabase, paste any eyJ… key from the bundle into the console as key; "service_role" means it skips Row Level Security:

JSON.parse(atob(key.split('.')[1].replace(/-/g,'+').replace(/_/g,'/'))).role
Enter fullscreen mode Exit fullscreen mode

Fix: rotate the key first (deleting it isn't enough), move the call into a server function that reads the new key from a secret, and keep only publishable keys (pk_live_, Supabase's anon/publishable key) in the browser.

→ Stripe secret key exposed in your frontend

2. Public source maps in production

What goes wrong: a served .map file turns your minified bundle back into readable source.

Check: grep -o 'sourceMappingURL=[^ ]*' bundle.js, then curl -sI that .map file's URL. A 200 means it's public.

Fix: in Vite, build: { sourcemap: false } (the default); in Next.js, remove productionBrowserSourceMaps: true. For readable stack traces, upload maps privately to your error tracker instead.

→ Public source maps in production

3. Missing HSTS, nosniff, clickjacking and Referrer-Policy headers

What goes wrong: browsers aren't told to stick to HTTPS, and other sites can frame your pages.

Check: curl -sI $APP | grep -iE 'strict-transport|frame-ancestors|x-frame|nosniff|referrer-policy' on your own domain (builder subdomains control their own headers).

Fix: a _headers file on Netlify or Cloudflare Pages (on Vercel, vercel.json → headers):

/*
  Strict-Transport-Security: max-age=31536000
  Content-Security-Policy: frame-ancestors 'none'
  X-Frame-Options: DENY
  X-Content-Type-Options: nosniff
  Referrer-Policy: strict-origin-when-cross-origin
Enter fullscreen mode Exit fullscreen mode

Use 'self' / SAMEORIGIN if your app frames its own pages.

→ HSTS missing from HTTPS server

Payments

4. Stripe test mode on the live site

What goes wrong: a pk_test_ key means checkout runs in a sandbox, so no real card can be charged.

Check: grep -oE 'pk_test_|buy\.stripe\.com/test_' bundle.js | sort -u (Clerk development keys share the prefix; check the context).

Fix: swap in pk_live_ and a live server key, then recreate products, prices, Payment Links and webhooks in live mode; sandbox objects don't carry over.

→ Stripe in test mode on your live site

5. Stripe checkout success_url pointing at localhost

What goes wrong: customers who finish paying are sent to http://localhost:3000/success, on their own machine.

Check: grep -oE '(success_url|cancel_url|return_url).{0,40}' bundle.js | grep localhost, then the same over your server functions (server-built URLs aren't in the bundle).

Fix: build both URLs from a SITE_URL setting, for example `${process.env.SITE_URL}/thanks?session_id={CHECKOUT_SESSION_ID}`, and fulfil orders from a webhook, not the success page.

→ Stripe checkout success_url points at localhost

Sign-in

6. Supabase Google OAuth redirects to localhost in production

What goes wrong: Google sign-in succeeds, then users land on http://localhost:3000, Supabase's starting Site URL.

Check: Supabase dashboard → Authentication → URL Configuration: is Site URL still localhost? Then sign in on your live site in a private window.

Fix: set Site URL to your production origin, add your exact callback URL to Redirect URLs, and always pass redirectTo, built from the current origin, to signInWithOAuth.

→ Supabase Google OAuth redirecting to localhost

7. Google's sign-in screen names firebaseapp.com, not your domain

What goes wrong: Google's screen shows your-project.firebaseapp.com (or <ref>.supabase.co) instead of your domain.

Check: grep -oE 'authDomain: ?"[^"]+"' bundle.js, or click "Sign in with Google" in a private window.

Fix: serve /__/auth/* on your domain (Firebase Hosting does this; elsewhere a transparent reverse proxy, not a 302), add https://yourdomain/__/auth/handler to the OAuth client and your domain to Authorized domains, then change authDomain. On Supabase, add a custom domain.

→ Change Firebase authDomain to your own domain

Email & domain

8. No SPF record found (and no DMARC)

What goes wrong: receivers can't confirm your sign-in emails and receipts came from you.

Check:

dig +short TXT yourapp.com | grep spf1
dig +short TXT _dmarc.yourapp.com
Enter fullscreen mode Exit fullscreen mode

Fix: one SPF TXT record at @ combining every sender's include:, ending in ~all (never two records, never +all). Once SPF or DKIM works, add _dmarc TXT v=DMARC1; p=none; rua=mailto:dmarc@yourapp.com and tighten it later.

→ No SPF record found

9. HTTP to HTTPS redirect not working

What goes wrong: http://yourapp.com loads instead of moving visitors to HTTPS.

Check: curl -sI http://yourapp.com | grep -iE '^HTTP|^location' should show a 3xx redirect to https://.

Fix: turn on your host's redirect (Cloudflare: SSL/TLS → Edge Certificates → Always Use HTTPS) in one layer only, to avoid loops, plus HSTS (item 3).

→ HTTP not redirecting to HTTPS

Privacy, rules & accessibility

10. A privacy policy with no effective date

What goes wrong: California's CalOPPA asks sites collecting personal information from Californians to post a policy with its effective date and, if you track across sites, your Do Not Track response.

Check: open the "Privacy" link in your homepage footer and search for "Effective date" and "Do Not Track".

Fix: a footer link to its own page, "Effective date:" at the top, and one honest Do Not Track paragraph. Have a lawyer glance at anything business-specific.

→ Privacy policy for an app (US)

11. Trackers that load before cookie consent

What goes wrong: the banner exists, but the Meta Pixel snippet in index.html runs on every load; for UK and EU visitors, ad pixels must wait for consent.

Check: private window → DevTools → Network, filter /googletagmanager|connect\.facebook|analytics\.tiktok/, reload without touching the banner. Any match means they aren't waiting.

Fix: load the snippets from your consent callback instead, and give "Accept all" and "Reject all" equal weight.

→ Cookie banner: trackers after consent

12. Buttons must have discernible text

What goes wrong: icon-only buttons (menu, close, heart) are announced as just "button", and images with no alt may be read out as file names.

Check: in the console:

[...document.querySelectorAll('button')].filter(b => !b.innerText.trim() && !b.getAttribute('aria-label') && !b.getAttribute('aria-labelledby'))
document.querySelectorAll('img:not([alt])').length
Enter fullscreen mode Exit fullscreen mode

Fix: <button aria-label="Open menu"><Menu aria-hidden="true" /></button>; meaningful images get a short alt, decorative ones alt="".

→ Buttons must have discernible text

Search & launch day

13. Page title still says "Lovable App" or "Vite + React"

What goes wrong: the template's title, description and icon show in Google, browser tabs and link previews.

Check: curl -s $APP | grep -iE '<title>|name="description"|rel="icon"' and look for "Lovable App", "Vite + React", "Lovable Generated Project" or /vite.svg.

Fix: in index.html, set a real title (<title>Acme Shifts: staff scheduling for cafes</title>) and description, and point the icon at your own PNG in public/.

→ Page title still says Lovable App?

14. Excluded by noindex tag

What goes wrong: a noindex left over from staging tells Google not to list your site.

Check:

curl -sI $APP | grep -i x-robots-tag
curl -s $APP | grep -i 'name="robots"'
curl -s $APP/robots.txt
Enter fullscreen mode Exit fullscreen mode

Fix: remove any noindex tag, X-Robots-Tag: noindex or Disallow: / from production, redeploy, and request indexing in Search Console.

→ Excluded by noindex tag

Running it automatically

I built Pewterkit, whose free check covers most of these items, and more, against your live URL. It reads public information only (homepage, scripts, headers, DNS) and shows the line, header or record behind each finding. It also sells filled-in fixes, but every fix above works without them: pewterkit.com.

What would you add?

This post was drafted with AI assistance and checked against the linked guides and the official docs they cite.

Top comments (1)

Collapse
 
alexcodebytes profile image
Oleksandr •

Another one: check for exposed .env files. It's surprisingly common for a misconfigured deploy to ship the whole .env to the web root. curl -s $APP/.env should return a 404, not your database password.