Next.js Security Guide: Securing Your AI-Generated Application
Security guide for Next.js apps built with AI. Fix server component leaks, secure API routes, protect middleware, and lock down your deployment.
By Gabriel CA · Kraftwire Software
· 9 min readKey Takeaway
Next.js is a powerful full-stack framework, but its mix of server and client components creates unique security challenges. This guide covers the most common vulnerabilities in Next.js apps and how to fix them before they become problems.
Why Next.js Security Is Different
Next.js blurs the line between frontend and backend. You have server components, API routes, middleware, and client components all living in the same codebase. That flexibility is great for development speed, but it also means security mistakes can happen in more places than a traditional React app.
The biggest risk? Accidentally exposing server-side logic or secrets to the client. Next.js makes it easy to fetch data on the server, but if you are not careful about what gets serialized and sent to the browser, you could leak database credentials, API keys, or internal business logic.
Server Components and Data Exposure
Server components run exclusively on the server, which sounds safe. But the data they return gets serialized and sent to the client as part of the page payload. If your server component fetches a user record that includes a hashed password or internal ID, that data ends up in the browser.
How to Fix It
Always shape your data before returning it from server components. Create explicit return types that only include the fields the client needs.
// Bad: returns everything from the database
async function UserProfile({ id }: { id: string }) {
const user = await db.users.findUnique({ where: { id } });
return <div>{user.name}</div>; // user object is serialized
}
// Good: only return what the client needs
async function UserProfile({ id }: { id: string }) {
const user = await db.users.findUnique({
where: { id },
select: { name: true, avatar: true }
});
return <div>{user.name}</div>;
}
API Route Security
Next.js API routes (in the app/api directory) are full server-side endpoints. They handle incoming requests and can interact with databases, external APIs, and file systems. But they are also publicly accessible by default, which means anyone can call them.
Common Mistakes
No authentication checks on sensitive endpoints
Missing input validation
Returning detailed error messages that reveal internal structure
Not rate limiting endpoints
How to Fix It
Add authentication middleware to every API route that handles sensitive data. Validate all inputs before processing them.
import { NextRequest, NextResponse } from "next/server";
import { getServerSession } from "next-auth";
export async function POST(req: NextRequest) {
const session = await getServerSession();
if (!session) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const body = await req.json();
const parsed = schema.safeParse(body);
if (!parsed.success) {
return NextResponse.json({ error: "Invalid input" }, { status: 400 });
}
}
Environment Variables and Secret Management
Next.js has a specific convention for environment variables. Variables prefixed with NEXT_PUBLIC_ are bundled into the client-side JavaScript. Everything else stays on the server. This is a critical distinction that many developers get wrong.
If you put your database connection string in a variable called NEXT_PUBLIC_DATABASE_URL, it will be visible in the browser. Always double-check your .env files to make sure sensitive values do not have the NEXT_PUBLIC_ prefix.
Best Practices
Use NEXT_PUBLIC_ only for values that are safe to expose (like a publishable Stripe key)
Keep database URLs, API secrets, and encryption keys without the prefix
Audit your .env files regularly
Use different values for development and production environments
Middleware Security
Next.js middleware runs before every request and is a powerful tool for authentication, redirects, and header management. But middleware runs at the edge, which means it has limited access to Node.js APIs and cannot directly query databases in all deployment environments.
Common Middleware Patterns
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export function middleware(request: NextRequest) {
const response = NextResponse.next();
response.headers.set("X-Frame-Options", "DENY");
response.headers.set("X-Content-Type-Options", "nosniff");
response.headers.set(
"Strict-Transport-Security",
"max-age=31536000; includeSubDomains"
);
return response;
}
Cross-Site Request Forgery (CSRF) Protection
Next.js does not include built-in CSRF protection. If your app handles form submissions or state-changing API calls, you need to implement CSRF tokens yourself or use a library.
Server Actions in Next.js 14+ automatically include some CSRF protection through origin checking, but API routes do not. Always verify the origin of requests to your API endpoints.
Image and File Upload Security
If your Next.js app handles file uploads, validate file types, sizes, and contents on the server. Never trust the file extension or MIME type sent by the client since these can be spoofed.
const MAX_SIZE = 5 * 1024 * 1024; // 5MB
const ALLOWED_TYPES = ["image/jpeg", "image/png", "image/webp"];
export async function POST(req: NextRequest) {
const formData = await req.formData();
const file = formData.get("file") as File;
if (!file || file.size > MAX_SIZE) {
return NextResponse.json({ error: "File too large" }, { status: 400 });
}
const buffer = await file.arrayBuffer();
const header = new Uint8Array(buffer.slice(0, 4));
// Verify magic bytes match expected file type
}
Server Actions Security
Server Actions are functions that run on the server but can be called directly from client components. They are convenient, but they are also attack surfaces. Every server action is essentially a public API endpoint.
How to Secure Server Actions
Always validate inputs inside the action
Check authentication before processing
Do not pass sensitive data as arguments since they are visible in network requests
Use rate limiting for actions that modify data
Content Security Policy
A strong Content Security Policy (CSP) prevents cross-site scripting attacks by controlling which resources the browser can load. Next.js makes it easy to set CSP headers through middleware or next.config.js.
const securityHeaders = [
{
key: "Content-Security-Policy",
value: "default-src self; script-src self unsafe-eval; style-src self unsafe-inline;"
}
];
module.exports = {
async headers() {
return [{ source: "/(.*)", headers: securityHeaders }];
}
};
Your Next.js Security Checklist
Audit all NEXT_PUBLIC_ environment variables
Add authentication checks to every API route
Validate all inputs on the server
Shape data in server components to avoid leaking sensitive fields
Set security headers via middleware
Implement CSRF protection for state-changing operations
Validate file uploads on the server
Secure server actions with auth checks and input validation
Configure a Content Security Policy
Review middleware for proper access control
What to Do Next
Run a security scan on your Next.js app to identify vulnerabilities before they reach production. Automated scanning catches the common issues like exposed secrets, missing headers, and unprotected endpoints. Pair that with manual code review for business logic flaws, and you will have a solid security posture.