Skip to content

Security · Next.js (App Router)

I found an API key in my page source in Next.js (App Router)

The value is served to everyone who views source. Treat it as compromised and rotate it at the provider before fixing how it got there. A build-time prefix such as `NEXT_PUBLIC_` or `VITE_` inlines whatever it is given, which is the usual way this happens. A live sandbox key is the quieter version: the payment form accepts test cards and declines real ones, and nothing is logged as a failure.

The fix for Next.js (App Router)

13x detects your framework from the response and hands you this version rather than the generic one — below 50% confidence it hedges and gives you the generic one instead.

Rotate the key first — it is already public. Revoke and reissue it in the provider's dashboard before changing any code.

In the App Router, NEXT_PUBLIC_ is the whole story: that prefix inlines the value into the client bundle at build time. Anything without it stays on the server.

bash code
# .env.local
STRIPE_SECRET_KEY=sk_live_REPLACE_WITH_YOUR_KEY          # server only
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_REPLACE_ME    # public by design

Read the secret from a server action or route handler, never from a component that carries "use client":

ts code
// app/api/checkout/route.ts — runs on the server
import Stripe from "stripe";

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);

export async function POST(request: Request) {
  const session = await stripe.checkout.sessions.create({ /* … */ });
  return Response.json({ url: session.url });
}

Add import "server-only"; at the top of any module that reads a secret. It turns "this accidentally got imported into a client component" from a silent production leak into a build error.

Then verify the bundle is clean:

bash code
npm run build && grep -rE "sk_(live|test)_|AKIA[0-9A-Z]{16}" .next/static/

On a different stack? The general version of this fix explains what 13x checks and why it matters, without assuming a framework.

Check your Next.js (App Router) site

113 deterministic checks against your live URL, in about 30 seconds. Framework detected from the response, so every fix comes back in the form your stack actually uses.

No signup. Results in 30 seconds.

The same fix, other stacks