Skip to content

Next.js security checklist

This checklist is a guide to help you secure your Next.js application. It is not exhaustive, but it covers the most important security best practices.

Checklist

  1. Are your dependencies up to date?
  2. Are you validating and sanitizing data?
  3. Are you using any environment variables prefixed with NEXT_PUBLIC_?
  4. Have you guarded against server-side code being exposed to the client?
  5. Have you added security headers?
  6. Is security-related functionality centralized?
  7. Is your code editor helping you write secure code?

0. Dependencies

This may seem simple, but quickly applying patches is one of the most important ways to keep your application secure. Use a tool like Dependabot to automatically get notified of new versions of your dependencies and open pull requests to update them.

Keeping up to date with the latest versions makes it much easier to upgrade if a security update is released. Otherwise, you may find yourself needing to work through multiple releases to get to the latest version in a stressful situation where a security vulnerability has been discovered.

Running Socket can help assess the security of new dependencies and identify potential supply chain risks when new updates are released. Running npm audit is an alternative.

Make sure you pin your dependencies to specific versions using package-lock.json or equivalent.

1. Data validation and sanitization

Once of the major advantages of using Next.js is that it inherits the built-in security escaping and sanitization features of React. However, it doesn’t do everything and you should still always validate data you receive from both users and APIs.

TypeScript helps with basic type checking, but that is not the same as validating data.

Use a package like zod or Valibot to validate data from users and APIs. Zod has a good comparison of the options.

If you are using an ORM like Drizzle then these can be integrated at the same time e.g. drizzle-zod. API frameworks like tRPC also support validation.

Watch out for

These features have their uses, but should warrant extra caution and review:

  • Any use of dangerouslySetInnerHTML in your application is a risk. This is a potential vector for cross-site scripting (XSS) attacks, which is why it is named “dangerous”.
  • Any use of innerHTML. A better option is innerText, which does not parse HTML.

In both cases, setting the HTML directly bypasses React’s built-in security. Try to create logic within the component which can use props to build the HTML instead rather than hard coding it.

2. Environment variables

Next.js automatically loads environment variables and only makes them available to server side code. To expose them to the client you must prefix them with NEXT_PUBLIC_.

This is a good default, and means you can pay extra attention to any variables prefixed with NEXT_PUBLIC_ to ensure they are safe to expose to the client.

If you are self-hosting Next.js or want to add an extra layer of safety to ensure no environment variables leak into the deployed version, you can use a tool like Trufflehog to scan the build artifacts.

3. Server only

Server components, client components, server side rendering, server actions…these can be confusing.

Next.js has a video on static vs dynamic rendering, docs on server actions and mutations and data fetching patterns, but it’s too easy to accidentally expose server-side code to the client. This can expose credentials or cause unexpected data leaks.

One way to guard against this is to tag modules with ‘server-only’ when you want to guarantee they will never be used on the client. If a client component tries to use a server-only module, the Next.js build will fail.

Just add this to the top of your module:

import "server-only";

This is also extra protection against accidentally exposing sensitive environment variables.

4. Security headers

There are a few security headers that you should consider adding to your application:

Setting security headers in Next.js

The next-safe package provides a simple way to configure and add these security headers to Next.js. For general JS apps and those using Express, the Helmet package is a popular choice.

The tldrsec/awesome-secure-defaults repo has a list of other useful secure-default libraries for various languages, including Node.js.

Next.js also provides an example of how to manually set just the CSP header using middleware.

Once you have a CSP policy, you can evaluate it with the CSP Evaluator tool from Google.

Other headers

  • X-XSS-Protection: This header was a feature of Internet Explorer, Chrome, and Safari that enabled the browser’s built-in XSS filter. It is deprecated and should not be used.

5. Centralize security functions

It’s a good idea to centralize security-related functionality in your application. This makes it easier to test, review, and audit. Some examples:

  • A single function for session management and authentication (AuthN). Ideally this is handled automatically without needing a developer to specifically call it - middleware is a good place for this. Clerk is a good option that supports authentication middleware. Another alternative is NextAuth, but their middleware doesn’t support edge functions so you may need to use the getServerSession function.
  • A single function for checking whether users have access to a particular resource (AuthZ). Similar to AuthN, this should be handled automatically without needing a developer to specifically call it. Oso is a framework which allows modeling authorization controls, but you could also have a simple function that runs the check based on passed in parameters.

6. Code editor

Your code editor can help you write secure code by showing linting and static analysis errors. Install Trunk to automatically bootstrap relevant linters and manage the configuration for you.

In particular, enable Trivy, Trufflehog, Gitleaks, and Semgrep to scan your code for potential security vulnerabilities. These can also be enabled in CI.

Resources

Other resources to help you secure your Next.js application: