Data, content, and assets

Keep server data private

Use private credentials while rendering without leaking them into markup, public files, or client scripts.

Frontmatter runs on the server, or during the build. The browser never sees it. That makes it the right place to use a secret.

Let’s fetch the signed-in GitHub user with a personal access token. Put the token in .env at the project root:

GITHUB_TOKEN=ghp_yourtoken

Then read it in the component:

---
const response = await fetch('https://api.github.com/user', {
  headers: {
    Authorization: `Bearer ${import.meta.env.GITHUB_TOKEN}`
  }
})

if (!response.ok) throw new Error('GitHub user request failed')

const user = await response.json()
const displayName = user.name
---

<p>Welcome {displayName}</p>

Astro loads .env and exposes the values on import.meta.env. The token goes into the request. Only displayName reaches the template. View the source and search for ghp_. Nothing.

The PUBLIC_ prefix

Astro has one rule for environment variables. Variables prefixed with PUBLIC_ are available in browser scripts and hydrated components too. Everything else is server-only.

So import.meta.env.GITHUB_TOKEN inside a <script> tag is undefined. Astro protects you here. And PUBLIC_ in a name is a promise: this value ships to visitors. Never rename a secret to PUBLIC_ to “make it work”.

The boundary is the output

The secret is safe in frontmatter. It stops being safe the moment you put it in the output. All of these leak:

  • <p>{import.meta.env.GITHUB_TOKEN}</p> in a template
  • data-token={token} on any element
  • passing token as a prop to a component with a client:* directive
  • logging it in a browser script

Everything that ends up in HTML or in a client bundle is public. The file extension doesn’t matter. Only what gets sent matters.

Static is public for everyone

One more trap. This page is static by default, so it renders once at build time with your token. Every visitor gets the same HTML: “Welcome Flavio”. That is not a personal account page. It’s a public page built with a private credential.

Real per-user pages need on-demand rendering plus actual authentication. The token proves who ran the build, not who is visiting.

Keep the projection small. Pull out the two or three fields the template needs, like displayName, and let the rest of the response die in frontmatter. Before deploying, search dist/ for the secret’s name and its value. Both searches should come back empty.

Lesson completed