# security headers in sveltekit ## About this content This is a blog post from quang.design. **Title:** security headers in sveltekit **Description:** Getting from a D to an A+ on securityheaders.com, learning about CSP, nonces, and why sometimes you need both hooks.server.ts AND vercel.json to make things work. **Published:** 2025-10-12 **URL:** https://www.quang.design/blog/posts/security-headers-sveltekit ## Content ![security headers in sveltekit](/blog/posts/security-headers-sveltekit/security-headers-sveltekit.webp) ## why bother? So I'm learning full-stack at Codecademy and hit the security-related HTTP headers module. They mentioned something interesting: > "Having security headers configured well also increases a website's trustworthiness, which in turn makes it rank higher in web searches (SEO)." If you care about SEO or in the future AEO (Answer Engine Optimization), this could become a good practice. You can use https://securityheaders.com/ to check which headers are active on your web address. ## the reality check Let's try it. Tested quang.design and got a D. Not bad? I guess. ![my initial score - a solid D](/blog/posts/security-headers-sveltekit/initial-score.png) Here's the distribution of total scores - most sites are F anyway, so D feels kinda okay. ![distribution of security header scores](/blog/posts/security-headers-sveltekit/score-distribution.png) But what's Apple's score? Good benchmark to see how far we are from the top. ![apple's security headers - learn from the best](/blog/posts/security-headers-sveltekit/apple-headers.png) You can learn a lot from how they write their security headers. Time to fix mine. ## attempt #1: hooks.server.ts So let's fix it. It's pretty simple. I'm using SvelteKit so I found this short and sweet article, [Adding security headers to your SvelteKit application](https://edoverflow.com/2023/sveltekit-security-headers/), to follow. Tried to follow it and then added it to _hooks.server.ts_: ```typescript // hooks.server.ts import { randomBytes } from 'node:crypto'; import type { Handle } from '@sveltejs/kit'; const SITE_URL = 'https://quang.design'; const STATIC_SECURITY_HEADERS = { 'X-Frame-Options': 'SAMEORIGIN', 'X-Content-Type-Options': 'nosniff', 'Referrer-Policy': 'no-referrer', 'Permissions-Policy': 'accelerometer=(), \ camera=(), \ geolocation=(), \ gyroscope=(), \ magnetometer=(), \ microphone=(), \ payment=(), \ usb=()', 'Cross-Origin-Embedder-Policy': 'require-corp', 'Cross-Origin-Opener-Policy': 'same-origin', 'Cross-Origin-Resource-Policy': 'same-origin', 'Origin-Agent-Cluster': '?1', 'X-DNS-Prefetch-Control': 'off', 'X-Download-Options': 'noopen', 'X-Permitted-Cross-Domain-Policies': 'none', 'X-XSS-Protection': '0' }; function buildCSP(nonce: string): string { return [ "default-src 'self'", `script-src 'self' 'nonce-${nonce}'`, "style-src 'self' 'unsafe-inline'", "img-src 'self' data: https:", "font-src 'self' data:", "connect-src 'self'", "frame-ancestors 'self'", "base-uri 'self'", "form-action 'self'" ].join('; '); } function addNonceToScripts(html: string, nonce: string): string { return html.replaceAll(/]*nonce=)/g, `