Guides

Is my v0 app secure?

v0 is good at a polished Next.js interface, and it will keep going into server code when you ask for data. The risky moment is when a secret is passed into a client component so the preview can render.

Free check

Check the live app

Paste the public v0 URL. The check reads what a visitor's browser can already see, then gives you a private report with a fix prompt for each issue.

Free, one time, and limited to what a stranger can already see. No account required.

What v0 actually ships

v0, from Vercel, generates React and Next.js, typically the App Router, with Tailwind and shadcn/ui. Backend logic belongs in Server Actions and route handlers. Publishing goes to Vercel. Databases such as Supabase, Neon, Upstash, and Vercel Blob are optional integrations, not something every project has. Auth is whatever integration you add. This page does not assume a database you never connected.

Mistakes worth checking

These are the ones that show up in apps built with v0. Each step is something you can do in the browser or in the product dashboard.

  1. Critical

    A secret inside a client component

    A file marked with "use client", and any value a Server Component passes into one, is part of what the browser downloads. OpenAI keys, Stripe secret keys, and database passwords belong in a Server Action or a route handler. They do not belong in the props of a client form.

    Check it yourself

    In the project, search client components for sk-, service_role, and database URLs. Then open the deployed page and search the HTML and scripts, because a server value can still be serialized into the page.

  2. Critical

    NEXT_PUBLIC_ on a secret in Vercel

    Vercel environment variables stay on the server unless the name starts with NEXT_PUBLIC_. v0 may add that prefix so the browser preview can read the value. From then on, the value is in the bundle.

    Check it yourself

    In the Vercel project, read the variable names. Anything secret should lack the NEXT_PUBLIC_ prefix. Search the live JavaScript for the value's prefix (sk-, sk_live, service_role) to confirm it did not ship.

  3. High

    Supabase unlocked, or a Neon URL in the browser

    When v0 connects Supabase, the anon key in the client is normal and row-level security is still required. A Neon (or any Postgres) connection string includes a password and has to stay in a server variable. There is no "public" Neon key that belongs in the page.

    Check it yourself

    If the live page contains a supabase.co address, confirm row-level security on every public table. Search the page for postgres:// and for a Neon host. A match with a password means rotate that database password after you remove the string.

  4. Medium

    Files in public/, and browser source maps

    Next.js serves the public folder as-is. An .env file dropped there becomes a URL. Browser source maps stay off unless productionBrowserSourceMaps is turned on. v0 can flip that while debugging a production error.

    Check it yourself

    Open /.env on the deployment URL. In next.config, confirm productionBrowserSourceMaps is not true. If a script references a .map file, open it and see whether your original source is inside.

  5. High

    Stripe's secret key used where the publishable key belongs

    Checkout examples need a publishable key in the browser and a secret key on the server. Mixing them up is a one-line mistake, and the secret key can move money.

    Check it yourself

    Search the deployed JavaScript. pk_live and pk_test may appear. sk_ and rk_ may not. Webhook secrets start with whsec_ and belong only in the server route that verifies Stripe.

A prompt you can paste

Paste this into v0, publish again, and run the check a second time. If a key was already on the live site, rotate it in the vendor dashboard. The prompt cannot do that part.

Review this v0 / Next.js app and fix issues a visitor can already see in the deployed site.

1. Remove from client components and from NEXT_PUBLIC_ variables: OpenAI and Anthropic keys, Stripe secret or restricted keys (sk_, rk_) and webhook secrets (whsec_), Supabase service_role keys, and any database URL with a password. A Stripe publishable key (pk_) and a Supabase anon key may stay in the browser.
2. Keep those calls in Server Actions or route handlers. Read the secrets from Vercel environment variables that do not start with NEXT_PUBLIC_. Do not pass the secret into a client component as a prop.
3. If Supabase is connected, enable row-level security on every public table and add policies for the signed-in user. Do not put a Neon or Postgres connection string in client code.
4. Do not set productionBrowserSourceMaps. Do not put .env or .git inside the public folder.
5. List exposed key names so I can rotate them in the vendor dashboards. Do not print the secret values.

Do not mark a server module as a client component to fix an import error if that would ship a secret.

What the check looks at

  • The public page and the JavaScript it loads, for common leaked secret keys: a Supabase service role key, OpenAI, Anthropic, Stripe secret keys, AWS, GitHub, Slack, private keys, and database URLs that include a password. A Supabase anon key is called out as expected in the browser.
  • A short list of public paths: /.env, /.env.local, /.env.production, /.git/config, /.git/HEAD, and /.npmrc.
  • A source map, only when a script on the page already points at one, and only when that map includes original source.
  • Supabase tables, only when the page already includes a supabase.co address and the public anon key. It asks for one row per table, then drops that row.
  • Firebase Realtime Database and Firestore, only when the public config is already in the page. It uses that public config and does not sign in.
  • A few browser safety headers on the main response. Missing headers are noted when two or more of the watched ones are absent. They are less urgent than a leaked key.

What stays outside it

  • It reads. It does not log in, submit forms, or change data.
  • It does not guess passwords, fuzz inputs, or hunt through hidden paths.
  • It does not use any secret it finds, including a service role key.
  • It does not open your builder's dashboard. A rule that lives only there can stay invisible.
  • A clear result describes this public look. It is not a certificate that the whole app is safe.
  • Server-only code stays out of view unless the page or a script actually includes the secret. A key that never reaches the browser will not show up here, which is what you want.

Questions

Free check

Run it on the published URL

Use the address a customer would type. The report stays private, and the shareable scorecard hides the details.

Free, one time, and limited to what a stranger can already see. No account required.

Other builders

Vibe-coded app security checklist · All guides