An audit of my portfolio turned up two failures with the same shape: declared but not delivered. Neither threw an error. Both had been live for months.
The security headers that were never sent
My next.config.ts had a proper headers() block. CSP, frame protection, the
lot.
But this site is a static export, output: 'export'. Next.js builds plain HTML
files and my own nginx serves them. In that mode headers() and redirects() do
nothing at all, because there is no Next server at runtime to apply them. Next
does not warn you. The config parses fine and is silently ignored.
nginx has its own trap waiting behind that one: it drops every inherited
add_header directive the moment a location block adds any header of its own.
So even after moving the headers to nginx, the locations that set their own
caching headers quietly shed the entire security set.
The manifest that promised phantom files
The PWA manifest referenced eight icon sizes, screenshots and shortcut icons.
None of those files existed. The layout also linked /icon.svg and
/apple-touch-icon.png, also missing.
| What was declared | What was actually delivered |
|---|---|
CSP and frame protection in next.config.ts | Nothing. A static export never runs a Next server to apply it |
| Full header set on every nginx location | Only the top-level headers. Overridden blocks dropped the rest |
| Eight manifest icon sizes plus screenshots | 404 on every request. The files never existed |
/icon.svg, /apple-touch-icon.png | 404. Linked in the layout, never generated |
The fix
Make the promises true, and make them smaller.
Repeat the full header set in every nginx location that sets any header, verified
with nginx -t and then by curling the live responses. Draw a real icon.svg,
generate every referenced PNG from it, and rewrite the manifest to list only
assets that exist.
The second half of that matters as much as the first. A shorter manifest that is entirely true is worth more than a comprehensive one that 404s.