Cursor + Next.js: Environment Variable Security
Updated 2026-10-17
When you build a Next.js app with Cursor, the AI often reaches for whichever env variable makes the error go away. Prefixing a secret with NEXT_PUBLIC_ silences "undefined" errors in client components — and inlines the secret into your public JavaScript.
The rule
Next.js inlines NEXT_PUBLIC_* values into client bundles at build time. Non-prefixed variables are only available in server code (route handlers, server components, server actions, middleware).
If a client component needs data that depends on a secret, fetch it through a route handler or server action instead of reading the secret in the browser.
Enforce the boundary with server-only
Put secret-reading config in a module that imports server-only. Any accidental import from a client component fails the build.
// lib/server-env.ts
import "server-only";
export const serverEnv = {
stripeSecret: process.env.STRIPE_SECRET_KEY!,
supabaseServiceRole: process.env.SUPABASE_SERVICE_ROLE_KEY!,
};Add a Cursor rule
Project rules make the AI respect the boundary in future edits.
# .cursor/rules/secrets.mdc
- Never prefix secrets with NEXT_PUBLIC_.
- Only read STRIPE_SECRET_KEY, SUPABASE_SERVICE_ROLE_KEY, OPENAI_API_KEY, ANTHROPIC_API_KEY, RESEND_API_KEY in files that import "server-only".
- Client components call route handlers or server actions for privileged work.Checklist
- Only publishable values use NEXT_PUBLIC_
- Secret config lives behind
import "server-only" .env*files are gitignored- Production secrets are set in the host's env settings, not committed
- Built
.next/staticcontains no secret prefixes
Copy-paste fix prompt
Scan this Next.js project for any NEXT_PUBLIC_ variable that holds a secret and for any client component that imports server config. Create lib/server-env.ts with `import "server-only"`, move secret reads there, and replace client usage with route handlers or server actions.FAQ
Are server actions safe for secrets?
Server actions run on the server, so they can read secrets. Treat them like public API endpoints though: validate input and check authorization inside every action.
How do I check my production build?
Run `next build` and search `.next/static` for key prefixes such as sk_live_, sk-proj-, sk-ant- and service_role.
Check your deployed app in ~25 seconds
Free passive scan: leaked keys, Supabase exposure, public config files and headers.
Run a free scan