NEXT_PUBLIC_ plus a Sume API key: why it ships to the browser

A NEXT_PUBLIC_ prefix inlines the value into client JavaScript at build time. Keep the Sume API key server-side, proxy via a route handler, rotate if it leaked.

5 min readSume
All posts

Do not put a Sume API key in a NEXT_PUBLIC_ variable. Next.js replaces every process.env.NEXT_PUBLIC_* reference with the literal value at build time, so the key lands in JavaScript served to every visitor, and Sume has no browser-safe key variant. Keep SUME_API_KEY unprefixed, read it in a route handler, and let the browser call your endpoint instead.

If a key has already shipped with a NEXT_PUBLIC_ name, treat it as published and rotate it. The steps are at the end.

What does the NEXT_PUBLIC_ prefix actually do?

The Next.js environment variables guide says non-NEXT_PUBLIC_ variables are only available in the Node.js environment. To expose one to the browser, Next.js inlines its value at build time into the JS bundle, replacing references to process.env.[variable] with a hard-coded value, and the guide says it is inlined into any JavaScript sent to the browser.

The same page notes that after the build the app no longer responds to changes in these variables, so a rotated key does not fix an already-built bundle. You have to rebuild and redeploy as well as rotate.

Why is there no safe way to use the key client-side?

The SDK page is direct: a Sume API key spends your credits, there is no browser-safe variant, and it must never reach client JavaScript, a mobile bundle or a NEXT_PUBLIC_* variable. The authentication page says the same for frontend code, mobile apps, support tickets and screenshots, and tells browser and mobile clients to call your backend, which attaches the key.

That also means scopes are not a mitigation. They are fixed when a key is created, and a leaked key with formats:write can start paid runs until you revoke it.

What does the server-side version look like?

A route handler holds the key and forwards only what you choose. Validate input and enforce your own authorization before forwarding, as the authentication page advises. This sketch follows its proxy pattern and adds a field allowlist so the browser cannot set arbitrary request fields:

// app/api/avatar/route.ts
export async function POST(request: Request) {
  const { image_url, avatar_handle } = await request.json();
  // your own auth check goes here

  const response = await fetch("https://api.sume.com/v1/avatar-1.0/generate", {
    method: "POST",
    headers: {
      "x-api-key": process.env.SUME_API_KEY!, // no NEXT_PUBLIC_ prefix
      "Content-Type": "application/json",
      "Idempotency-Key": crypto.randomUUID(),
    },
    body: JSON.stringify({
      avatar_handle,
      input: { type: "photo", image_url },
    }),
  });
  return new Response(await response.text(), {
    status: response.status,
    headers: { "Content-Type": "application/json" },
  });
}

How do I check for a leak and rotate the key?

Search your built output, not your source. Run a production build and grep .next/static for the key prefix (sume_live_). Also search any .env file that a CI log or a Docker layer may have copied, since the Next.js guide warns that .env files should almost never be committed.

Dynamic lookups such as process.env[name] are not inlined, per the same guide, so a clean grep on one build does not prove another is clean. Check the build you actually deployed.

The authentication page gives the sequence: create a replacement key, deploy it to your server, verify GET /v1/me, then revoke the old key from the dashboard. Create the new key with the scopes you need from the start, because scopes cannot be added later.

Rename the variable at the same time so the new one has no NEXT_PUBLIC_ prefix, rebuild, and confirm the old key is gone from the new bundle before you revoke it. After revoking, calls with the old key return 401.

What about server components and runtime variables?

Unprefixed variables are safe to read in server code. The Next.js guide says environment variables are by default only available on the server, and that you can read them on the server during dynamic rendering, which is how a single built image can be promoted through several environments with different values. Route handlers and server actions run on the server, so process.env.SUME_API_KEY is available there without any prefix.

The failure mode to watch is a server-only module that gets imported from client code. Keep the Sume client in a server-only file, export a function that takes plain data, and call it from a route handler or server action. Return to the browser only what the user should see: a job id, a status and a result URL, never the request headers or the key.

Also keep the key out of places the Next.js guide does not control: error messages, logging middleware and analytics payloads. The authentication page lists support tickets and screenshots among the places a key must not go, and calls signed upload and download URLs temporary secrets.

Before you ship, run through this list once per project.

  • No Sume variable name starts with NEXT_PUBLIC_.
  • The key is read only in route handlers, server actions or server-only modules.
  • Your own route checks the signed-in user before forwarding anything to Sume.
  • The forwarded body is built from an allowlist, not spread from the request.
  • .env* files are ignored by git, as the default Next.js template does.
  • You know the rotation order: new key, deploy, GET /v1/me, then revoke.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume