SECURITY
Web Development10 min read

Website Security Checklist: Protect Your Next.js Site in 2026

DS

De Studio

Web Development Studio

May 9, 2026
10 min read

Most websites ship with zero HTTP security headers, open API endpoints, and unpatched npm vulnerabilities. Here is the complete security checklist we run on every De Studio project before it goes live.

Why Most Websites Ship Insecure by Default

Here is a uncomfortable truth about modern web development: the default output of every major framework — Next.js, Nuxt, SvelteKit, Remix — is an application with no HTTP security headers whatsoever. Zero. You get a working website, but a browser security audit will flag it as incomplete from day one.

This is not a criticism of the frameworks. They are rightly focused on developer experience and performance. Security hardening is left to the team shipping the product. The problem is that most teams — especially small studios and solo developers — ship without ever running that audit.

The consequences are real. Clickjacking attacks embed your site in an invisible iframe and trick users into clicking things they cannot see. MIME-type confusion attacks cause browsers to execute files as JavaScript when they should not. Missing Content Security Policy means a single compromised third-party script can exfiltrate your users' data. Cross-Site Scripting through an unsanitised API input can take down your entire application.

None of these attacks require sophistication. Automated scanners probe millions of sites every hour looking for exactly these gaps. If your site is reachable, it is being scanned. This checklist covers every layer we secure before a De Studio project goes live.

Layer 1: HTTP Security Headers

HTTP security headers are instructions your server sends to the browser before any content loads. They tell the browser what it is and is not allowed to do on your page. They cost nothing to implement and protect against an entire class of attacks.

Here are the six headers every production website must have:

1. X-Frame-Options: DENY Prevents your site from being embedded in an iframe on any other domain. Without this, attackers can load your site invisibly behind a fake UI and trick users into clicking things — a technique called clickjacking. Set it to DENY unless you specifically need to embed your own pages (in which case use SAMEORIGIN).

2. X-Content-Type-Options: nosniff Tells the browser to trust the Content-Type header and never guess file types. Without it, a browser might execute a text file as JavaScript if the server sends the wrong MIME type — a MIME confusion attack. This one header closes that entire attack surface.

3. Strict-Transport-Security (HSTS) Forces the browser to always use HTTPS for your domain, even if a user types http:// in the address bar. Set max-age to at least 31536000 (one year) and include the includeSubDomains and preload flags. Once set, man-in-the-middle HTTP downgrade attacks become impossible.

4. Referrer-Policy: strict-origin-when-cross-origin Controls what URL information is sent in the Referer header when users click links from your site. Without this, the full URL — including any tokens, IDs, or sensitive query parameters — is leaked to every external resource your page loads.

5. Permissions-Policy Disables browser features your site does not use. Explicitly deny camera, microphone, geolocation, payment, and USB access. If a malicious script is ever injected into your page, it cannot access these hardware features even if it tries.

6. Content-Security-Policy (CSP) The most powerful header on this list — and the most complex. CSP is a whitelist: you tell the browser exactly which origins are allowed to load scripts, styles, images, fonts, and API connections. Anything not on the whitelist is blocked, even if it is injected by a compromised third-party script.

In Next.js, you add all six in next.config.ts under the headers() function. Apply them to all routes except your CMS studio path, which has its own requirements.

Layer 2: API Route Security

Every API route in your Next.js application is a public HTTP endpoint the moment it is deployed. It does not matter that your frontend is the only thing supposed to call it — anyone with a terminal can send a POST request to /api/contact with whatever payload they like.

Four things every API route must implement:

Rate Limiting Without rate limiting, a single automated script can hammer your contact form thousands of times per minute, filling your CRM with spam and burning through your email quota in seconds. A simple in-memory rate limiter — five requests per IP per fifteen minutes — is enough to stop casual abuse. For high-traffic routes, use a Redis-backed solution like Upstash.

Origin Validation Check the Origin header on every mutating request. If the request does not come from your own domain, return a 403 immediately. This does not stop determined attackers who can spoof headers, but it stops the vast majority of automated cross-site abuse and is a single line of code to add.

Input Validation with Max Lengths Validate every field on the server, not just on the client. Client-side validation is a UX feature, not a security feature — anyone can bypass it by sending a raw HTTP request. Validate type, format, minimum length, and — critically — maximum length. A message field without a max-length limit can accept a 50MB string. Do that to enough endpoints and you have a denial-of-service attack.

Honeypot Fields Add a hidden form field that real users will never fill in (because it is hidden with CSS). Bots filling forms programmatically will populate every field. If the honeypot field has any value, return a fake success response — never reveal to the bot that it was blocked.

Whitelisting Over Blacklisting For fields with a fixed set of valid values — checkboxes, dropdowns, category selectors — maintain a server-side whitelist and silently discard any value not on it. A services[] field that accepts arbitrary strings can be used to inject data into your CRM. A services[] field validated against a known set of values cannot.

Layer 3: Dependency Vulnerabilities

Every package in your node_modules is a potential attack surface. The npm registry has had multiple high-profile supply chain attacks where popular packages were compromised to exfiltrate environment variables, steal credentials, or plant backdoors in production builds.

Run npm audit regularly — before every deploy is a reasonable cadence for active projects, or at least once a month for stable ones. The output will show you each vulnerability, its severity, and whether an automatic fix is available.

Understanding the output:

Critical and High severities require immediate action. These often involve remote code execution, data exfiltration, or authentication bypass. Update the affected packages as a priority, even if it means testing breaking changes.

Moderate severities should be addressed but are usually not emergency-level. Many are denial-of-service vectors or edge-case XSS that require specific conditions to exploit.

Low severities are worth tracking but are rarely exploitable in real-world conditions.

The harder problem: vendor-locked vulnerabilities Sometimes npm audit reports a vulnerability in a package you do not control — like a dependency of Next.js itself. You cannot fix it by running npm audit fix without accepting a breaking change (like downgrading your entire framework). In these cases, the right move is: 1. Note the vulnerability and its CVE number. 2. Check the upstream issue tracker to see if a fix is in progress. 3. Assess whether your specific usage pattern actually triggers the vulnerability. 4. Update as soon as the framework ships a patched version.

Do not run npm audit fix --force blindly. That flag allows breaking changes and can swap your Next.js 15 for Next.js 9 to resolve a dependency conflict — a cure far worse than the disease.

Layer 4: Environment Variables and Secrets

The most common way secrets leak from Next.js applications is the accidental NEXT_PUBLIC_ prefix. Any environment variable prefixed with NEXT_PUBLIC_ is embedded into the client-side JavaScript bundle and is visible to anyone who inspects your page source. It is designed for public configuration — your Sanity project ID, your Mapbox public token — not for secrets.

Never prefix with NEXT_PUBLIC_: - API write tokens - Database connection strings - Email SMTP passwords - Stripe secret keys - Any value you would not be comfortable posting on Twitter

Only prefix with NEXT_PUBLIC_: - Sanity project ID and dataset name (public by design) - Analytics write keys (Posthog, Mixpanel — designed to be public) - Map provider public tokens - Feature flag keys for non-sensitive features

Two other common secret leaks:

1. Committing .env.local to git. Add it to .gitignore immediately. If you have already committed it, rotate every secret in the file and add .env.local to .gitignore — the history still contains the old values, but rotating the credentials renders them useless.

2. Logging secrets in server-side code. A console.log(process.env) statement in a server component or API route will print every environment variable to Vercel's function log. Anyone with access to your Vercel project can read them. Log specific values you need, never the entire env object.

Layer 5: CMS and Admin Panel Exposure

If you use Sanity CMS, your Studio is mounted at /studio by default and is reachable by anyone who knows the URL. This is by design — Sanity handles authentication internally. But there are still things you should verify.

First, confirm that only invited members can access the studio. In your Sanity project settings at sanity.io/manage, check that CORS origins only include your actual domains, that the dataset is not set to public write access, and that API tokens have the minimum permissions they need — read-only tokens for read-only operations, write tokens only for the API routes that need them.

Second, block the studio path from search engines. Your robots.ts should include /studio/ in the disallow list — you do not want Google indexing your CMS login page or exposing its existence to automated scanners looking for common admin paths.

Third, apply different security headers to the studio path. Sanity Studio loads external resources and uses iframes internally, so a DENY frame policy would break it. Apply SAMEORIGIN instead, and relax the CSP for that path specifically. The studio is a trusted internal tool — it does not need the same lockdown as your public-facing pages.

Finally, protect your write API token. The token that allows your contact form to save leads to Sanity is extremely sensitive. It should only live in server-side environment variables, never in client components, and should be scoped to the minimum permissions required — ideally a custom role that can only create lead documents, not read or delete existing content.

The Pre-Launch Security Checklist

Before every De Studio project goes live, we run through this checklist. Copy it and make it your own.

HTTP Headers [ ] X-Frame-Options set to DENY or SAMEORIGIN [ ] X-Content-Type-Options: nosniff applied [ ] Referrer-Policy configured [ ] Permissions-Policy disabling unused browser APIs [ ] Strict-Transport-Security with at least one year max-age [ ] Content-Security-Policy tested and not blocking legitimate resources

API Routes [ ] Rate limiting implemented on all mutating endpoints [ ] Origin header validated on POST/PUT/DELETE routes [ ] Input validation on server — not just on client [ ] Max-length limits on all string fields [ ] Honeypot field on contact and sign-up forms [ ] Whitelist validation on enum/select fields

Dependencies [ ] npm audit run with no critical or high severity issues [ ] Moderate issues reviewed and tracked [ ] Automatic updates configured (Dependabot or Renovate)

Secrets [ ] No secrets in NEXT_PUBLIC_ variables [ ] .env.local in .gitignore [ ] All secrets rotated if ever committed to git [ ] No console.log(process.env) in production code

CMS and Admin [ ] CORS origins limited to production domain only [ ] API token scoped to minimum required permissions [ ] Admin paths in robots.txt disallow list [ ] Studio path has relaxed but still present security headers

Security is not a feature you add at the end. It is a series of small, deliberate decisions made throughout the build. The checklist above takes less than an hour to complete on a typical Next.js project. The cost of skipping it can be measured in data breaches, spam floods, reputational damage, and hours of emergency incident response. Run the checklist. Ship secure.

TagsWeb DevelopmentDesignDe Studio
Keep Reading

Related Posts

A11Y
A11y

May 15, 2026

Accessibility & Inclusive Design: Building Websites for Everyone

Over 1.3 billion people live with some form of disability. When your website fails accessibility standards, you are not just losing users — you are excluding them entirely. Here is everything you need to build truly inclusive digital experiences.

Read Post
UX
UX Research

May 13, 2026

Design Thinking & UX Methodology: The Complete Guide for 2026

Design thinking is not a buzzword — it is the structured process that separates products users love from products they abandon. Here is the complete methodology we follow at De Studio, from empathy mapping to high-fidelity prototyping.

Read Post
AI
AI Development

May 6, 2026

No More Subscriptions: Building Websites with Claude Only

How we ditched over $600/month in SaaS tools and now build entire production websites using only Claude — no Webflow, no Figma, no page builders required.

Read Post
Let's Work Together

Ready To Transform Your Digital Presence

Let's build something remarkable together. Book a free discovery call and find out how we can help you design and develop a product your users will love.