HTTP Security Headers: The Practical Guide for Web Developers
HTTP security headers are defense-in-depth measures that protect your users from attacks even when your application code has vulnerabilities. They instruct the browser to: restrict where scripts can load from, prevent your site from being embedded in iframes, enforce HTTPS connections, and control what information is shared with other origins.
Most developers know they should add security headers but are not sure which ones matter or how to configure them without breaking their application. Here is the practical guide.
Content-Security-Policy (CSP): The Most Important Header
CSP tells the browser: "only execute scripts from these sources, only load images from these sources, only connect to these APIs." If an attacker injects a malicious script (XSS), CSP prevents it from executing because it did not come from an allowed source.
A basic CSP: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; connect-src 'self' https://api.yourservice.com;
What this means: scripts only load from your own domain ('self'), styles load from your domain plus inline styles (needed for many CSS-in-JS approaches), images load from your domain, data URIs, and any HTTPS source, and fetch/XHR only connects to your domain and your API.
The CSP Challenge with Next.js
Next.js uses inline scripts for hydration data. A strict CSP that blocks inline scripts breaks Next.js. Solutions:
Nonce-based CSP: generate a random nonce for each request, add it to the CSP header (script-src 'nonce-ABC123'), and add the same nonce to Next.js script tags. This allows only specifically-approved inline scripts.
In Next.js 14+: the experimental serverActions API supports nonce generation in middleware. Add the nonce to all script tags via the Script component with nonce prop.
Alternative: use 'unsafe-inline' for scripts during development and strict nonce-based CSP in production. Not ideal but practical when implementing CSP incrementally.
X-Frame-Options / frame-ancestors
Prevents clickjacking: an attacker embeds your site in a hidden iframe and tricks users into clicking on your UI elements thinking they are interacting with something else.
Configuration: X-Frame-Options: DENY (never allow iframing) or frame-ancestors 'none' in CSP (equivalent, more modern).
If you need embedding (widgets, embedded content): frame-ancestors https://trusted-partner.com — allow only specific trusted domains.
For AIDToolStack: we use frame-ancestors 'none' because our site should never be iframed. Our embeddable tool widgets are served from a separate domain that allows embedding.
Strict-Transport-Security (HSTS)
Tells the browser: "always use HTTPS for this domain. Even if the user types http://, upgrade to https:// automatically."
Configuration: Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
What this does: for one year (31536000 seconds), the browser will refuse to connect via HTTP. includeSubDomains applies the rule to all subdomains. preload submits your domain to browser HSTS preload lists (hardcoded in Chrome, Firefox, Safari).
Important: only enable HSTS after confirming HTTPS works perfectly on your domain and all subdomains. Once HSTS is set with a long max-age, you cannot easily go back to HTTP. If HTTPS breaks, your site becomes inaccessible until the max-age expires.
X-Content-Type-Options
Prevents MIME type sniffing: without this header, browsers might interpret a plain text file as HTML (and execute embedded scripts) if the content looks like HTML.
Configuration: X-Content-Type-Options: nosniff
This is a simple, zero-risk header. Always include it. There is no legitimate reason for MIME sniffing.
Referrer-Policy
Controls how much URL information is shared when users click links to other sites.
Aggressive: Referrer-Policy: no-referrer — never share any URL information. Other sites cannot see where traffic came from.
Balanced: Referrer-Policy: strict-origin-when-cross-origin — share only the origin (https://aidtoolstack.com, not the full path) when navigating to other sites. Share the full URL for same-origin navigation.
We use strict-origin-when-cross-origin. This gives us: analytics data for internal navigation (full referrer), privacy for users when they leave our site (only origin shared), and compatibility with services that need the origin (payment processors, OAuth).
Permissions-Policy
Controls which browser features your site can use. Disabling unused features prevents: malicious scripts from accessing the camera/microphone, third-party scripts from accessing geolocation, and fingerprinting through unused APIs.
Configuration: Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=(), usb=()
Empty parentheses () means "disabled for all origins." If you need a feature: camera=(self) allows camera access only from your origin.
For a developer tool directory: we disable everything because we do not use any device APIs.
Implementation in Next.js
Add security headers in next.config.mjs using the headers() function. This applies headers to all routes:
Define an array of header objects (key-value pairs for each security header) and return them from the headers config function with a source pattern that matches all routes.
Alternatively: add headers in middleware.ts for more dynamic control (e.g., different CSP nonces per request).
Testing Your Headers
Use securityheaders.com to scan your site. It grades your security header implementation and identifies missing headers.
Use the browser's DevTools Network tab to inspect response headers and verify they are present on all pages.
Test CSP specifically: the browser console shows CSP violations. If a legitimate resource is blocked, you will see: "Refused to load the script... because it violates the following Content Security Policy directive." Adjust your CSP to allow the blocked resource.
Deployment Considerations
Headers must be set at the server/CDN level. Client-side JavaScript cannot set response headers.
Vercel: configure in next.config.mjs (as described above) or in vercel.json.
Cloudflare: set headers in Workers, Page Rules, or Transform Rules.
Self-hosted (Caddy/Nginx): configure in the server configuration file.
The Security Headers Checklist
Minimum headers for every web application:
This takes 30 minutes to implement and dramatically improves your security posture. These headers protect users even when your application code has vulnerabilities — they are the safety net that prevents exploitation.
Written by AIDToolStack Editorial
The AIDToolStack editorial team reviews developer tools, AI products, and MCP servers with hands-on testing and honest assessments. We use the tools we review every day in production.
References & Further Reading
Related Posts
Sponsor Our Newsletter
Reach thousands of developers who are actively evaluating AI tools, MCP servers, and dev infrastructure. Our weekly newsletter goes to engaged technical decision-makers.
All sponsored content is clearly labeled per our editorial policy.