We almost shipped a major security mistake last year. The kind where a private API key ends up in the browser's network tab, visible to anyone who opens DevTools. It wasn't malicious — it was just a developer who didn't know the difference between server-side and client-side in Astro. The fix took 10 minutes. The realization that it had been exposed for 3 weeks was uncomfortable.

This is our guide to not making that mistake.

The Core Problem: Static Sites Have No Server Secrets

When you build a fully static site (Astro with output: 'static', Next.js exported, Gatsby), everything gets bundled at build time. Any API key you include in client-side JavaScript is visible to anyone who looks at your source code. End of story.

The rule is simple: any secret that should not be public must never touch client-side code.

Mistake 1: Using Private Keys in Astro Client-Side Components

In Astro, components with client:load or client:visible are React/Vue/Svelte components that run in the browser. If you pass a secret key as a prop or import it inside these components, it ships to the browser.

// WRONG - this key will be visible in the browser bundle
const MyWidget = () => {
  const data = await fetch('https://api.service.com', {
    headers: { 'Authorization': 'Bearer ' + import.meta.env.MY_SECRET_KEY }
  });
}
// RIGHT - do this in the .astro file frontmatter (server-side only)
---
const response = await fetch('https://api.service.com', {
  headers: { 'Authorization': 'Bearer ' + import.meta.env.MY_SECRET_KEY }
});
const data = await response.json();
---
<MyWidget data={data} />

Mistake 2: Exposing Keys via Client-Side Fetch in Static Sites

If you need real-time data (search, user-specific content), you cannot handle it in a static build. Your options:

  • Serverless functions (Vercel Functions, Netlify Functions) — proxy the API call server-side, return only what the client needs
  • Astro hybrid rendering — mark specific routes as prerender: false to enable SSR for those pages while keeping the rest static
  • Third-party managed services — some APIs (Algolia, Typesense) provide client-safe keys with restricted permissions

Securing Environment Variables

In Astro, prefix your secrets with nothing (or SECRET_) — they'll only be available server-side. Variables prefixed with PUBLIC_ are intentionally exposed to the browser. This is a useful convention, but developers need to understand the distinction.

On Vercel/Netlify: use the environment variable UI, never commit secrets to your repository. Even in a private repo, secrets in version control are a liability — repo access gets shared, forked, logged.

A Quick Audit Checklist

  • Open DevTools Network tab — filter for API calls. Check the request headers. Are any sensitive tokens visible?
  • Search your built dist/ folder for your secret key strings. If you find them, something is wrong.
  • Check your .env files — is .env.local in your .gitignore?
  • Review any client-side components that make fetch calls. Where do the credentials come from?

Security in static sites is mostly about understanding the boundary between build-time (server) and runtime (browser). Once that's clear, most mistakes are preventable.