XSS Attacks

Cross-Site Scripting (XSS) is what happens when an attacker gets their own JavaScript to run in another user's browser, in the security context of your site — able to read cookies not marked httpOnly, make requests as the logged-in user, or rewrite the page entirely. The classic vector is stored or reflected input: a comment, a profile field, a query parameter, anything user-controlled that ends up rendered back as live HTML instead of inert text.

Why JSX mitigates most of this by default

When you write {someValue} in JSX, React does not insert it into the DOM as HTML. It's set as text content, and any characters that would otherwise open a tag or attribute (<, >, &, quotes) are escaped automatically. So a comment containing <script>alert(1)</script> rendered via <p>{comment.body}</p> shows up on the page as the literal text of a script tag — it is never parsed as markup. This is true in both Server Components and Client Components, and it's why, in an ordinary Next.js app that only ever interpolates values through JSX, most user input is already safe by construction.



Where the risk still exists



Mitigations

Sanitizing untrusted HTML before rendering it with dangerouslySetInnerHTML, for something like a CMS-authored article body.
components,ArticleBody.tsx:
import DOMPurify from 'isomorphic-dompurify'

export function ArticleBody({ html }: { html: string }) {
  const clean = DOMPurify.sanitize(html)
  return <div dangerouslySetInnerHTML={{ __html: clean }} />
}
Adding a Content-Security-Policy header to every response via next.config.js, restricting scripts to same-origin sources.
next.config.ts:
import type { NextConfig } from 'next'

const nextConfig: NextConfig = {
  async headers() {
    return [
      {
        source: '/:path*',
        headers: [
          {
            key: 'Content-Security-Policy',
            value: "default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'self';",
          },
        ],
      },
    ]
  },
}

export default nextConfig