The day I realized the frontend is not enough
Why frontend code is a public document, how API key exposure happens in production, and the four operations that require a backend.
It was a Tuesday night. I was testing ChainElect's voter registration — profile photo upload working perfectly, Supabase saving user data, MetaMask wallet binding all smooth.
Then I got a DM from a developer friend I'd sent the link to:
"Hey, nice project. Also... is this your Pinata API key? Found it in the JS bundle.
pk_...5f8a. You might want to rotate that."
I opened Chrome DevTools. Went to the Sources tab. Searched "pinata".
There it was. Baked directly into the compiled JavaScript. My production Pinata API key. Readable by anyone. No hacking required. Just Ctrl+F.
Within 20 minutes, I had rotated the key, revoked the old one, and started planning a backend.
1. The Root Cause: What Happens to Frontend Code
When you run npm run build, Vite (or Webpack) takes all your JavaScript source files and bundles them into a single compressed file that gets served to every browser that visits your site.
Here is the critical truth about frontend code: it is a public document.
Every line of JavaScript your browser runs was downloaded from a server. Anyone can:
- Open DevTools → Sources → read it
- Run
stringson the minified bundle - Use a beautifier to restore readable code
- Intercept network requests to see API calls in plain text
This is not a bug. It is the fundamental architecture of the web. Browsers need the code to run it.
The VITE_ prefix means "include this in the bundle." Minification renames the variable. The value stays.
2. The Visual: What Gets Exposed
[!TIP] VISUAL TRIGGER FOR FRONTEND: Animate the insecure flow as a chain where the API key visibly "travels" from the env file → bundle → browser → network request. Then show the secure flow where the key stays locked inside a server vault icon that only the backend can access. The frontend only ever receives the output (CID), never the secret.

3. Technical Explanation: What the Frontend Cannot Do
There are four operations that require a backend in any serious Web3 dApp:
1. API Secret Management Pinata, Alchemy paid keys, Supabase service role keys, email service API keys — anything that has financial cost or security implications must live on the server.
2. Server-Side Authentication MetaMask proves wallet ownership but cannot prove the user's email, their registration status, or their admin role in a traditional auth system. ChainElect uses Supabase sessions, managed through the Express backend:
3. Rate Limiting Without a backend, a bot can call your Pinata-powered upload endpoint 10,000 times per minute. With Express + rate-limit middleware, you control access.
4. Sensitive Computations Anything that would expose business logic, pricing formulas, or internal data structures should not run in browser JavaScript where it can be inspected.
Adding a backend does NOT undermine decentralization. The blockchain remains the trust layer — ownership, votes, and proofs are still fully on-chain and verifiable by anyone. The backend is the access control layer that manages authentication and API secrets. If the backend goes offline, the on-chain state remains intact and accessible through any RPC node.
The Next.js False Security Pattern:
Many developers think that using Next.js API routes makes their secrets safe because the code is in /api files. This is true only if those secrets are never imported into client-side components. If you import an API route's module from a page.tsx, Next.js may accidentally bundle the server-side code into the client bundle. Always use server-only package imports or ensure strict separation of client and server code.
Open any production dApp's compiled JavaScript bundle (e.g. load the site → DevTools → Sources → find the main.js bundle). Use Ctrl+F to search for common API key patterns: pk_, sk_, Bearer, apiKey, token. What do you find? This exercise reveals exactly how common the API key exposure mistake is in production Web3 apps.
Was this lesson helpful?
Let us know what you think of this specification. (submitting anonymously)
