Next.js is the most common framework behind AI-built apps. The model usually gets the rendering right and the boundaries wrong, and the boundaries are where the security lives.
Server and client are a security boundary
In the app router, a component runs on the server unless it says “use client”. Server components never reach the browser; client components are shipped to it. A model that puts a secret in a client component has published it, and the app still works, which is exactly why it goes unnoticed.
- Anything in a client component is public. Read every “use client” file with that in mind.
- Do privileged work in server components, route handlers or server actions.
- Pass only what the browser needs from a server component to a client component, not an entire database row.
What NEXT_PUBLIC_ really means
- NEXT_PUBLIC_ variables are inlined into the browser bundle at build time. Treat each one as published.
- A variable with no prefix is server-only. If it is read in a client component it will be undefined and the feature will silently fail.
- Never prefix a secret to make it “work” in the browser. Fix the design instead.
- Rebuild after changing a NEXT_PUBLIC_ value, because it is baked in, not read at runtime.
Route handlers and server actions need their own auth
A protected page does not protect the route handler or server action behind it. Each entry point can be called directly and must verify the caller itself. Middleware helps, but it is a convenience, not the only gate.
- Every route handler checks the session before doing work.
- Server actions re-check permission rather than trusting the page that rendered the form.
- Return 401 or 403 rather than a redirect, so an API caller gets a clear answer.
- Validate every field on the server, even if the form already validated it in the browser.
Metadata, sitemap and robots
- Every page has a unique title and a meta description.
- Canonical URLs are set once, consistently, so search engines do not guess.
- robots.txt allows the public pages and disallows the app and the API.
- A sitemap lists the pages you want indexed and nothing behind sign-in.
- Structured data describes the page accurately, not aspirationally.
Images, fonts and the render path
- Use the built-in image component so images are sized correctly and loaded lazily.
- Server-render or statically render anything that can be, so the first paint does not wait on the client.
- Keep “use client” at the interactive leaves of the tree, not at the top of a page.
- Check the real numbers with Lighthouse or PageSpeed Insights instead of guessing.
Caching and dynamic data
- Know which pages are static and which are dynamic, because a statically rendered page will happily show yesterday's data.
- Revalidate deliberately after a write instead of disabling caching everywhere.
- Never cache a page that shows one user's data to another.
- Check the deployed behaviour, because the dev server does not cache the way production does.
Deployment checks
- 01
Build for production
Run the real build, not the dev server. Type and page-level errors surface here.
- 02
Confirm the environment
Check that production has its own variables and that no development key made the trip.
- 03
Inspect the deployed bundle
Grep what the browser downloads for key prefixes and internal hostnames.
- 04
Check the headers on production
HTTPS, HSTS, a content security policy where you can, and sane cookie flags.
- 05
Probe the API directly
Call a protected route without a session and confirm it refuses.
- 06
Run an external review
A black-box pass over the deployed site confirms the runtime story the code cannot tell you.
The short version
In Next.js, most security mistakes are boundary mistakes. Know what runs where, and the rest follows.