Application Development

How to Hide API Keys in a Vite App

J
James Eriksson
··8 min read
Hide API keys in Vite apps with 4 methods: environment variables, Vite proxy, backend server, edge functions. Compare security, cost, and complexity for production apps.
TL;DR
  • You cannot hide API keys in Vite frontend code; any bundled JavaScript is inspectable via DevTools
  • Four methods exist: environment variables (dev only), Vite proxy (local testing), backend server (production), or edge functions (serverless)
  • Backend servers cost $5-50/month; edge functions cost $0.15-0.50 per million requests
  • If your only purpose for a backend is proxying API keys, a managed platform eliminates deployment overhead
  • Choose based on traffic volume, operational complexity, and whether your API key is public or sensitive

No, you can't hide API keys in a Vite app that runs in the browser. Any code bundled for the frontend can be inspected via DevTools, and your key is visible in the Network tab the moment your code makes a request. But you have four ways to fix it: environment variables for dev, a Vite proxy for local testing, a backend server for production, or edge functions. Here's how to choose.

Why You Can't Hide API Keys in Frontend Code

Vite bundles all your JavaScript into files that ship to the browser. Anything in those files is inspectable. The VITE_ prefix in environment variables does expose them to import.meta.env at build time, but even without that prefix, HTTP requests your frontend makes--with the key in the Authorization header--are logged in the browser's Network tab. An attacker can read them there.

When you build your Vite app for production, the bundler includes every variable you reference. If you do:

const apiKey = import.meta.env.VITE_API_KEY;
fetch(`<your-api-endpoint>/data?key=${apiKey}`);

The bundler sees that you're using VITE_API_KEY, substitutes its value at build time, and ships both the request and the key in the same JavaScript file. Open DevTools on the deployed site, search the Network tab for your API endpoint, and the key is visible in the query string or request headers.

This is not a Vite-specific problem. It's a fundamental rule: never put secrets in code that runs in the browser. No JavaScript framework or build tool can change this.

Method 1: Environment Variables (.env Files) -- Development Only

Environment variables prefixed with VITE_ become available in your frontend code via import.meta.env. You set them in .env.local (gitignored) for development. This prevents accidental commits to GitHub. But in production, Vite builds these values into your JavaScript bundle. Your key is bundled into the output file and readable to anyone with DevTools. Use this method for development only.

To set this up:

  1. Create a .env.local file in your project root (add .env.local to .gitignore).
  2. Add your key: VITE_API_KEY=your_actual_key_here.
  3. Reference it in your code: const apiKey = import.meta.env.VITE_API_KEY;.
  4. In development, npm run dev reads .env.local and makes the key available.

This protects your key from being committed to GitHub. But when you run npm run build, Vite substitutes the value into your JavaScript. The production build contains the literal key. Do not use this method for production unless your API key has rate-limiting and you're comfortable with it being public.

Method 2: Vite Dev Server Proxy -- Local Testing Only

During local development, Vite's dev server can proxy requests from your frontend to a backend URL. This solves CORS issues and lets you test against a real API without hardcoding the full URL in your frontend code. Configure it in vite.config.js:

export default {
  server: {
    proxy: {
      '/api': {
        target: 'http://localhost:3000',
        changeOrigin: true,
      },
    },
  },
};

Now when you fetch from /api/endpoint, the dev server forwards it to your local backend. This is a development convenience only. It works because your local machine runs both the Vite app and your backend. In production, your built Vite app runs without a proxy server, so requests still fail. Use this method when testing locally against a backend you're also developing. Do not rely on it for production.

Method 3: Backend Proxy Server -- Production-Safe

The most reliable production approach: your API key lives on a backend server, never in your frontend code. Your Vite app calls an endpoint on your own backend; your backend calls the third-party API and returns the response. This requires running a server (Express, FastAPI, Node.js, etc.), but it's the standard pattern for most web apps. The API key is safe because it never leaves your infrastructure.

Here's a minimal Express example:

// backend/server.js
import express from 'express';
import fetch from 'node-fetch';

const app = express();
const API_KEY = process.env.API_KEY;

app.get('/api/data', async (req, res) => {
  const response = await fetch('<your-third-party-api-endpoint>/data', {
    headers: { Authorization: `Bearer ${API_KEY}` },
  });
  const data = await response.json();
  res.json(data);
});

app.listen(3000);

Your frontend now calls your own /api/data endpoint. The third-party API key stays on the server. If someone reads your frontend code, they see only the URL to your backend, not the actual key. Hosting a backend costs money. A small VPS starts at $5/month (Hetzner, DigitalOcean), a managed platform at $7-20/month. Once you deploy your backend with GitHub, the infrastructure is set. Most production web apps have a backend for this reason alone.

Method 4: Edge Functions -- Serverless Alternative

Edge functions (Cloudflare Workers, Vercel Edge, AWS Lambda@Edge) run serverless code at the edge, closer to users. You deploy a small function that proxies requests: your frontend calls it, it calls the third-party API with your key, and returns the response. Billing is per-request, starting at $0.15 per million requests. For low-traffic apps, this can be cheaper than a 24/7 backend.

An example with Cloudflare Workers:

export default {
  async fetch(request, env) {
    const apiKey = env.API_KEY;
    const response = await fetch('<your-third-party-api-endpoint>/data', {
      headers: { Authorization: `Bearer ${apiKey}` },
    });
    return response;
  },
};

Deploy this function, and your frontend calls it instead of the third-party API. Cold start is milliseconds. Cost scales with usage. The trade-off: edge functions require learning a new runtime and deployment model, whereas a traditional backend reuses your existing Node.js or Python skills.

Comparing All Four Methods

MethodCostSetupSecurityCold StartBest For
Environment variablesFree5 minDev only; exposed in prodN/ADevelopment testing
Vite proxyFree5 minDev only; requires backendInstantLocal testing only
Backend server$5-50/mo30 minProduction-safe1-2 secMost production apps
Edge function$0-5/mo15 minProduction-safe<100 msLight-traffic apps

DIY backend with a cheap VPS wins on raw monthly cost. A managed platform costs more upfront but removes operational overhead: you don't manage uptime, backups, or deployment pipelines. The calculation changes when you account for your time.

When Your Backend Becomes Overkill

If your only reason to deploy a backend is to proxy API keys, you're paying for work you don't need to do. You have to monitor uptime, manage environment variables, rebuild and redeploy on code changes, and debug CORS errors. The complexity and mental load add up fast.

This is where managed platforms come in. Ship handles environment variable management and request proxying out of the box, letting you skip the backend entirely. You push code, Ship deploys it, and secrets are managed for you. It costs more per month than a cheap VPS, but your actual operational cost--in time and stress--is lower. If you're already running a backend for other reasons (authentication, databases, business logic), proxying API keys there is free. But if your backend exists only for this purpose, it's worth reconsidering.

Frequently Asked Questions

Can I use VITE_ environment variables in production?

Only if you don't mind the key being visible in DevTools. For public APIs (Google Maps, OpenWeather) with usage limits tied to the API key, exposure is an acceptable risk. For sensitive keys (payment processors, private data), production requires a backend or edge function proxy.

What's the difference between.env and.env.local?

.env is committed to Git and shared with your team..env.local is gitignored and holds local secrets. Use.env.local for development API keys; use.env for non-secret config.

How do I debug CORS errors when proxying?

CORS is a browser security feature. If your frontend calls an API directly and it lacks Access-Control-Allow-Origin headers, the browser blocks it. Use Vite's dev server proxy to test locally, or proxy through your backend in production.

Is an edge function faster than a backend server?

Edge functions have faster cold start (milliseconds vs. 1-2 seconds) because the infrastructure is globally distributed. For ongoing traffic, latency is similar. Choose edge functions if you want simplicity and minimal operational overhead.

Should I rotate API keys regularly?

Yes, if the key is exposed in production code. If the key lives on your backend only, rotation happens without redeploying your frontend. If it's bundled into your JavaScript, rotating means rebuilding and redeploying the entire app.

Can I read.env files in production Vite builds?

No. Vite processes.env files at build time. The values are baked into your JavaScript bundle. In production, your app cannot read from.env files.

The Bottom Line

The honest answer: you cannot hide API keys in frontend code. You have four clear options, each with tradeoffs. Environment variables work for development. Vite proxy helps with CORS in dev. Backend proxies are the standard for production apps. Edge functions are the lightweight alternative. Choose based on your traffic, tolerance for operational overhead, and risk profile. If running a backend just to proxy API keys feels like overkill, deploy to Ship and let it manage the infrastructure.

Deploy with zero DevOps friction
Ship manages environment secrets and request routing automatically, so you don't run a backend just for API proxying.
Get Started Free

Ready to self-host your own apps?

One server. Multiple apps. No per-app fees.

Get started →