XSS in Next.js Applications
Next.js inherits React's built-in XSS protections through JSX escaping, but developers frequently bypass these safeguards with dangerouslySetInnerHTML, unescaped Server Component outputs, and misconfigured Content Security Policies. Learn exactly where XSS surfaces in Next.js and how to fix it.
Scan Your Next.js AppHow XSS Manifests in Next.js
In Next.js, XSS most commonly appears when developers use dangerouslySetInnerHTML to render user-generated content like blog posts or comments. Server Components introduce a new vector: data fetched on the server and rendered directly into HTML without sanitization bypasses React's client-side escaping. Another frequent pattern is reading query parameters via useSearchParams() or the params prop and injecting them into the DOM. Next.js API routes that return HTML responses are also vulnerable if they interpolate request data without encoding. The App Router's metadata API can also be exploited if user input flows into dynamic og:image URLs or meta tags without validation.
Real-World Impact
An attacker injects a script tag through a comment field on a Next.js blog. Every visitor who views the page executes the payload, which steals their session cookie and sends it to the attacker's server. The attacker hijacks authenticated sessions, accesses private dashboards, and exfiltrates user data. In e-commerce Next.js apps, XSS in product review sections has been used to redirect users to phishing pages that mimic the checkout flow, capturing payment card details.
Step-by-Step Fix
Sanitize HTML before rendering
Install DOMPurify and sanitize any user-generated HTML before passing it to dangerouslySetInnerHTML.
import DOMPurify from 'isomorphic-dompurify';
export default function BlogPost({ content }: { content: string }) {
const sanitized = DOMPurify.sanitize(content, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p', 'br'],
ALLOWED_ATTR: ['href', 'target', 'rel'],
});
return <div dangerouslySetInnerHTML={{ __html: sanitized }} />;
}Add Content Security Policy headers
Configure CSP in next.config.js to prevent inline script execution.
// next.config.js
const nextConfig = {
async headers() {
return [
{
source: '/(.*)',
headers: [
{
key: 'Content-Security-Policy',
value: "default-src 'self'; script-src 'self' 'nonce-{nonce}'; style-src 'self' 'unsafe-inline';",
},
],
},
];
},
};
module.exports = nextConfig;Validate query parameters
Never render URL parameters directly into the page. Validate and encode them first.
import { useSearchParams } from 'next/navigation';
export default function SearchResults() {
const searchParams = useSearchParams();
const query = searchParams.get('q') || '';
// Encode for display - React JSX handles this automatically
// but NEVER put this into dangerouslySetInnerHTML
return <h1>Results for: {query}</h1>;
}Secure API routes
Never interpolate user input directly into HTML responses from API routes.
// app/api/preview/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { escape } from 'html-escaper';
export async function GET(req: NextRequest) {
const title = req.nextUrl.searchParams.get('title') || '';
// Always escape user input in HTML responses
const safeTitle = escape(title);
return new NextResponse(
`<html><body><h1>${safeTitle}</h1></body></html>`,
{ headers: { 'Content-Type': 'text/html' } }
);
}Prevention Best Practices
1. Never use dangerouslySetInnerHTML with unsanitized input. If you must render HTML, use DOMPurify or sanitize-html. 2. Configure a strict Content Security Policy in next.config.js using the headers() function. 3. Validate and sanitize all user input on the server side in API routes and Server Actions. 4. Use Next.js middleware to add security headers like X-Content-Type-Options and X-Frame-Options. 5. Avoid string interpolation of user data into HTML templates in API routes.
How to Test
1. Enter <script>alert("XSS")</script> into every input field, search bar, and URL parameter in your Next.js app. 2. Check for reflected XSS by appending ?q=<img src=x onerror=alert(1)> to your URLs. 3. If your app renders markdown or HTML content, test with payloads like <img src=x onerror=alert(1)> in the content. 4. Use Vibe App Scanner to automatically detect XSS vulnerabilities in your Next.js application, including checks for missing CSP headers and unsafe HTML rendering patterns. 5. Review all uses of dangerouslySetInnerHTML in your codebase with: grep -r "dangerouslySetInnerHTML" --include="*.tsx" --include="*.jsx"
Frequently Asked Questions
Does React's JSX escaping protect Next.js from all XSS?
No. While JSX automatically escapes expressions in curly braces, developers can bypass this protection using dangerouslySetInnerHTML, server-side HTML generation in API routes, or by injecting into href attributes with javascript: URLs. Server Components also introduce new vectors where server-fetched data is rendered without client-side escaping.
Are Next.js Server Components more vulnerable to XSS?
Server Components render HTML on the server and stream it to the client. If unsanitized user data is included in the server-rendered output, it becomes part of the HTML before React's client-side protections apply. Always sanitize data in Server Components just as you would in API routes.
How do I set up CSP with Next.js App Router?
Use the headers() function in next.config.js or create middleware that adds a nonce-based CSP header. Next.js 13+ supports nonce-based CSP natively through the generateStaticParams and middleware patterns. Set script-src to 'self' with a nonce to block inline scripts.
Related Security Resources
Is Your Next.js App Vulnerable to XSS?
VAS automatically scans for xss vulnerabilities in Next.js applications and provides step-by-step remediation guidance with code examples.
Scans from $5, results in minutes. Get actionable fixes tailored to your Next.js stack.