Safeguard
Security

Nuxt Security: Hardening Your Nuxt App Against Real Threats

A practical Nuxt security guide covering the nuxt-security module, Content Security Policy with SSR nonces, server-route risks, and dependency hygiene.

Marcus Chen
DevSecOps Engineer
6 min read

Nuxt security starts with recognizing that a Nuxt app is not just a frontend — it is a server-rendering Node application with API routes, which means it needs both browser-side protections like Content Security Policy and server-side discipline around its endpoints and dependencies. The good news is that most of the browser-side hardening is available through a maintained module; the part teams underestimate is the server surface that server-side rendering (SSR) and Nitro server routes introduce.

The two halves of Nuxt security

Because Nuxt renders on the server and can host its own API routes via Nitro, its attack surface spans two domains. On the client side you are defending against cross-site scripting, clickjacking, and injection of unwanted content into rendered pages. On the server side you are defending API routes, handling secrets, and managing the same dependency risk any Node service carries. A hardening effort that only sets response headers and ignores the server routes leaves half the application exposed.

The nuxt-security module

The most efficient way to cover the browser-side half is the nuxt-security module, a community module built around OWASP Top 10 guidance and the Helmet approach. It sets sensible security headers and adds middleware by default, so a large chunk of baseline hardening arrives from a single install.

npm install nuxt-security
// nuxt.config.ts
export default defineNuxtConfig({
  modules: ["nuxt-security"],
});

Out of the box it applies headers for things like content type options, frame options, referrer policy, and a default Content Security Policy, plus rate-limiting and other middleware. Start from its defaults and tighten rather than assembling headers by hand, which is error-prone.

Content Security Policy done right

CSP is the highest-value browser protection because it mitigates the impact of XSS: even if an attacker injects a script, a strict policy can stop it from executing or exfiltrating data. It also helps against clickjacking, formjacking, and unwanted trackers.

The hard part with a server-rendered app is that a naive CSP either breaks legitimate inline scripts or is so loose (unsafe-inline) that it provides little protection. The modern answer is a strict CSP using nonces for SSR. The nuxt-security module supports this directly: for SSR applications it generates a one-time cryptographically random nonce per request and injects it into script tags via a placeholder, pairing it with 'strict-dynamic'. For statically generated (SSG) pages, where there is no per-request server, it uses hashes instead.

Conceptually the SSR configuration enables nonce generation and uses the nonce placeholder in the policy so each rendered page gets a fresh value:

// nuxt.config.ts (illustrative)
export default defineNuxtConfig({
  modules: ["nuxt-security"],
  security: {
    nonce: true,
    headers: {
      contentSecurityPolicy: {
        "script-src": ["'self'", "'nonce-{{nonce}}'", "'strict-dynamic'"],
        "object-src": ["'none'"],
        "base-uri": ["'none'"],
      },
    },
  },
});

The exact keys should be confirmed against the module's current documentation, but the principle is stable: nonces for SSR, hashes for SSG, and never fall back to blanket unsafe-inline just to silence console warnings. A permissive CSP that "works" is worse than an honest audit of what your app actually loads.

Server routes are your real API

Nitro server routes under server/api/ are full backend endpoints, and they deserve backend-grade scrutiny. The common mistakes are treating them as trusted because they live in the frontend repo:

  • Validate and sanitize every input. Server routes receive untrusted data exactly like any API. Use a schema validator on request bodies and query parameters rather than trusting shape.
  • Enforce authentication and authorization in the route, not just in the UI. Hiding a button does not protect the endpoint behind it.
  • Keep secrets in runtime config's private section (runtimeConfig, not runtimeConfig.public) and never in code committed to the repo. Anything under the public key is shipped to the browser.
  • Beware SSRF and injection in routes that make outbound calls or touch a database, the same as you would in any Node service.

An SSR page that echoes user-controlled data into the rendered HTML without escaping is a server-side XSS vector, so treat data flowing into templates with the same caution as data flowing into an API response.

Dependency and supply-chain hygiene

A Nuxt app sits on a deep npm dependency tree — Nuxt itself, Nitro, Vue, and a long tail of modules and their transitive dependencies. That graph is where a large share of real-world risk lives, and it is invisible unless you scan it. Pin versions and commit a lockfile, run npm ci in CI, and keep an eye on install scripts in the dependency chain. An SCA tool such as Safeguard can flag a vulnerable transitive dependency of a Nuxt module even when your package.json only names the module. For the reasoning behind these habits across the npm ecosystem, see our npm security best practices.

A pragmatic hardening order

If you are hardening an existing Nuxt app, work in this sequence for the best risk reduction per hour:

  1. Install nuxt-security and adopt its defaults.
  2. Move to a strict, nonce-based CSP and remove unsafe-inline.
  3. Audit every server/api/ route for input validation and auth.
  4. Verify no secrets leak through public runtime config or the client bundle.
  5. Add dependency scanning to CI and fail on new high-severity findings.

Nuxt gives you a productive full-stack framework, but "full-stack" is the operative word for security: the server side is real, and the browser side is only handled well if your CSP is strict rather than merely present.

FAQ

What is the best way to secure a Nuxt app?

Cover both halves of the app. Use the nuxt-security module for browser-side headers and a strict Content Security Policy, then apply backend-grade validation, authentication, and secret handling to your Nitro server routes, and scan your dependency tree in CI.

Does the nuxt-security module handle everything?

No. It handles the browser-side layer well — security headers, a default CSP, and useful middleware based on OWASP guidance. It does not secure your server route logic, your authentication, or your dependency graph, which remain your responsibility.

How do I set up CSP in Nuxt without breaking my app?

Use a strict CSP with nonces for SSR (the nuxt-security module generates a per-request nonce and pairs it with 'strict-dynamic') and hashes for SSG. Avoid falling back to 'unsafe-inline', which weakens the policy to the point of little value.

Are Nuxt server routes a security risk?

They are full backend endpoints, so they carry the same risks as any API: injection, missing authorization, SSRF, and secret exposure. Validate all input, enforce auth in the route itself, and keep secrets in private runtime config rather than the client bundle.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.