If your code reads an API key through a variable starting with VITE_ , NEXT_PUBLIC_ or REACT_APP_ , that key is in your public JavaScript and anyone can copy it. An environment variable keeps it out of your Git repo, not out of the browser: the build tool pastes the value into the bundle.
Why the prefix matters
Vite (Lovable, Bolt): VITE_ variables are exposed in client code, and the Vite docs say they shouldn't hold secrets.
Next.js (v0, many Cursor projects): NEXT_PUBLIC_ variables are inlined into the browser JavaScript at build time.
Create React App : same for REACT_APP_ .
Hardcoding a key in a component is the same problem. If the browser runs the code, the browser has the key.
Safe in the browser vs secret
Usually fine in frontend code Never in frontend code
Supabase anon / sb_publishable_ key (with RLS) Supabase service_role / sb_secret_
Stripe pk_live_... Stripe sk_live_...…