Security Best Practices in Next.js
5 questions found
Why is it important to never expose sensitive information like API keys inside client components?
Beginner
Anything included in a client component becomes part of the JavaScript bundle sent to every visitor's browser, meaning anyone can view it by inspecting the page, so secrets like API keys or database credentials must stay in server only code, such as server components, route handlers, or server actions.
// Never do this in a client component
'use client';
const apiKey = 'sk_live_secret_key_12345'; // exposed to everyone
// Instead, use it only in server side code
const res = await fetch(url, { headers: { Authorization: `Bearer ${process.env.API_KEY}` } });
Real-world example
A team accidentally included a payment provider's secret key inside a client component, and after realizing anyone could view it in their browser's developer tools, they immediately rotated the key and moved the logic to a server action.
Common follow-ups: How do you check if a secret has accidentally been exposed to the browser?;What is the difference between a public and a secret environment variable?
Environment Variables & Configuration Management;Client Components & Hydration
How do you protect a Next.js application against cross site scripting attacks?
Intermediate
React automatically escapes content rendered inside JSX, which prevents most cross site scripting attacks by default, but you should still avoid using dangerouslySetInnerHTML with untrusted content, and always validate and sanitize any user submitted data before storing or displaying it.
// React automatically escapes this, protecting against XSS
function Comment({ text }) {
return <p>{text}</p>;
}
// Avoid this unless the content is trusted and sanitized
<div dangerouslySetInnerHTML={{ __html: userInput }} />
Real-world example
A comment section on a blog safely displays user submitted text because React automatically escapes it, preventing a malicious visitor from injecting harmful scripts into the page.
Common follow-ups: When is it safe to use dangerouslySetInnerHTML?;What sanitization libraries are commonly used for user generated content?
Form Handling & Validation in Next.js;Client Components & Hydration
How do you set security related HTTP headers, such as Content Security Policy, in a Next.js application?
Intermediate
You add a headers function inside your next.config.js file, defining custom response headers like Content-Security-Policy or X-Frame-Options that apply across your application, helping protect against attacks like clickjacking and restricting which sources of content the browser is allowed to load.
// next.config.js
module.exports = {
async headers() {
return [
{
source: '/(.*)',
headers: [
{ key: 'X-Frame-Options', value: 'DENY' },
{ key: 'Content-Security-Policy', value: "default-src 'self'" }
]
}
];
}
};
Real-world example
A banking application adds strict security headers across its entire site, preventing it from being embedded in a hidden frame on a malicious website that could trick users into clicking something unintended.
Common follow-ups: What does the X-Frame-Options header actually protect against?;How restrictive should a Content Security Policy be for a typical application?
Middleware;Third-Party Script Optimization
How would you protect server actions from being called with malicious or unauthorized data, since they can be invoked directly from the client?
Advanced
You always validate and sanitize the incoming data inside the server action itself using a schema validation library, never trust that data matches what your form actually intended to send, and check that the currently authenticated user is actually authorized to perform that specific action before making any changes.
'use server';
import { z } from 'zod';
const schema = z.object({ postId: z.string(), text: z.string().max(500) });
export async function addComment(formData) {
const session = await getSession();
if (!session) throw new Error('Not authenticated');
const result = schema.parse({ postId: formData.get('postId'), text: formData.get('text') });
await db.comment.create({ data: { ...result, userId: session.user.id } });
}
Real-world example
A comment feature validates both the shape of incoming data and confirms the user is properly authenticated inside its server action, preventing a malicious actor from bypassing the form and submitting harmful data directly.
Common follow-ups: Can a server action be called directly without going through the intended form?;What additional protections help prevent server actions from being abused?
Server Actions & Mutations;Form Handling & Validation in Next.js
What steps should you take to keep dependencies and the Next.js framework itself secure over time?
Advanced
Regularly update Next.js and your project's dependencies to receive security patches, use automated tools that scan for known vulnerabilities in your dependencies, remove packages you no longer actually use, and subscribe to security advisories for the frameworks and libraries your project relies on most heavily.
# Check for known vulnerabilities in dependencies
npm audit
# Update Next.js to the latest version
npm install next@latest
Real-world example
A development team runs automated dependency scanning as part of their deployment pipeline, catching and fixing a known vulnerability in a third party package before it ever reaches their production users.
Common follow-ups: How often should a team check for outdated or vulnerable dependencies?;What tools can automate dependency vulnerability scanning?
Testing Next.js Applications;Deployment