[{"data":1,"prerenderedAt":607},["ShallowReactive",2],{"blog-how-to/vercel-headers":3},{"id":4,"title":5,"body":6,"category":577,"date":578,"dateModified":578,"description":579,"draft":580,"extension":581,"faq":582,"featured":580,"headerVariant":593,"image":594,"keywords":594,"meta":595,"navigation":596,"ogDescription":597,"ogTitle":594,"path":598,"readTime":594,"schemaOrg":599,"schemaType":600,"seo":601,"sitemap":602,"stem":603,"tags":604,"twitterCard":605,"__hash__":606},"blog/blog/how-to/vercel-headers.md","How to Configure Security Headers on Vercel",{"type":7,"value":8,"toc":543},"minimark",[9,13,17,21,39,44,60,64,117,121,124,143,159,172,176,179,191,203,207,210,222,234,246,272,276,280,295,299,319,323,329,333,344,348,352,379,383,397,401,428,432,455,464,468,507,519],[10,11],"category-badge",{"category":12},"How-To Guide",[14,15,5],"h1",{"id":16},"how-to-configure-security-headers-on-vercel",[18,19,20],"p",{},"Protect your Vercel-deployed app with essential HTTP headers",[22,23,24,27],"tldr",{},[18,25,26],{},"TL;DR (15 minutes)",[18,28,29,30,34,35,38],{},"Add security headers on Vercel using ",[31,32,33],"code",{},"vercel.json"," in your project root. For Next.js apps, you can also use ",[31,36,37],{},"next.config.js"," or middleware for dynamic headers. Configure X-Content-Type-Options, X-Frame-Options, HSTS, Referrer-Policy, Permissions-Policy, and Content-Security-Policy.",[40,41,43],"h2",{"id":42},"prerequisites","Prerequisites",[45,46,47,51,54,57],"ul",{},[48,49,50],"li",{},"A project deployed on Vercel (or ready to deploy)",[48,52,53],{},"Access to your project's Git repository",[48,55,56],{},"Your site served over HTTPS (automatic on Vercel)",[48,58,59],{},"Basic knowledge of JSON configuration",[40,61,63],{"id":62},"three-ways-to-add-headers-on-vercel","Three Ways to Add Headers on Vercel",[65,66,67,83],"table",{},[68,69,70],"thead",{},[71,72,73,77,80],"tr",{},[74,75,76],"th",{},"Method",[74,78,79],{},"Best For",[74,81,82],{},"Dynamic Headers",[84,85,86,97,106],"tbody",{},[71,87,88,91,94],{},[89,90,33],"td",{},[89,92,93],{},"All frameworks, static sites",[89,95,96],{},"No",[71,98,99,101,104],{},[89,100,37],{},[89,102,103],{},"Next.js projects",[89,105,96],{},[71,107,108,111,114],{},[89,109,110],{},"Middleware",[89,112,113],{},"Dynamic CSP, nonces",[89,115,116],{},"Yes",[40,118,120],{"id":119},"method-1-using-verceljson","Method 1: Using vercel.json",[18,122,123],{},"The simplest approach that works with any framework.",[125,126,128,133],"step",{"number":127},"1",[129,130,132],"h3",{"id":131},"create-verceljson-in-your-project-root","Create vercel.json in your project root",[134,135,140],"pre",{"className":136,"code":138,"language":139},[137],"language-text","{\n  \"headers\": [\n    {\n      \"source\": \"/(.*)\",\n      \"headers\": [\n        {\n          \"key\": \"X-Content-Type-Options\",\n          \"value\": \"nosniff\"\n        },\n        {\n          \"key\": \"X-Frame-Options\",\n          \"value\": \"DENY\"\n        },\n        {\n          \"key\": \"X-XSS-Protection\",\n          \"value\": \"1; mode=block\"\n        },\n        {\n          \"key\": \"Referrer-Policy\",\n          \"value\": \"strict-origin-when-cross-origin\"\n        },\n        {\n          \"key\": \"Permissions-Policy\",\n          \"value\": \"camera=(), microphone=(), geolocation=(), interest-cohort=()\"\n        },\n        {\n          \"key\": \"Strict-Transport-Security\",\n          \"value\": \"max-age=31536000; includeSubDomains\"\n        },\n        {\n          \"key\": \"Content-Security-Policy\",\n          \"value\": \"default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' https://fonts.gstatic.com; connect-src 'self' https:; frame-ancestors 'none'; base-uri 'self'; form-action 'self';\"\n        }\n      ]\n    }\n  ]\n}\n","text",[31,141,138],{"__ignoreMap":142},"",[125,144,146,150,153],{"number":145},"2",[129,147,149],{"id":148},"add-route-specific-headers-optional","Add route-specific headers (optional)",[18,151,152],{},"Apply different headers to different routes:",[134,154,157],{"className":155,"code":156,"language":139},[137],"{\n  \"headers\": [\n    {\n      \"source\": \"/(.*)\",\n      \"headers\": [\n        { \"key\": \"X-Content-Type-Options\", \"value\": \"nosniff\" },\n        { \"key\": \"X-Frame-Options\", \"value\": \"DENY\" },\n        { \"key\": \"Referrer-Policy\", \"value\": \"strict-origin-when-cross-origin\" }\n      ]\n    },\n    {\n      \"source\": \"/api/(.*)\",\n      \"headers\": [\n        { \"key\": \"Cache-Control\", \"value\": \"no-store, max-age=0\" },\n        { \"key\": \"X-Content-Type-Options\", \"value\": \"nosniff\" }\n      ]\n    },\n    {\n      \"source\": \"/embed\",\n      \"headers\": [\n        { \"key\": \"X-Frame-Options\", \"value\": \"SAMEORIGIN\" }\n      ]\n    }\n  ]\n}\n",[31,158,156],{"__ignoreMap":142},[125,160,162,166],{"number":161},"3",[129,163,165],{"id":164},"deploy-to-vercel","Deploy to Vercel",[134,167,170],{"className":168,"code":169,"language":139},[137],"# Commit and push your changes\ngit add vercel.json\ngit commit -m \"Add security headers\"\ngit push\n\n# Or deploy directly with Vercel CLI\nvercel --prod\n",[31,171,169],{"__ignoreMap":142},[40,173,175],{"id":174},"method-2-using-nextconfigjs-nextjs","Method 2: Using next.config.js (Next.js)",[18,177,178],{},"For Next.js projects, configure headers directly in your Next.js config.",[125,180,181,185],{"number":127},[129,182,184],{"id":183},"update-nextconfigjs","Update next.config.js",[134,186,189],{"className":187,"code":188,"language":139},[137],"/** @type {import('next').NextConfig} */\nconst securityHeaders = [\n  {\n    key: 'X-DNS-Prefetch-Control',\n    value: 'on'\n  },\n  {\n    key: 'Strict-Transport-Security',\n    value: 'max-age=63072000; includeSubDomains; preload'\n  },\n  {\n    key: 'X-Content-Type-Options',\n    value: 'nosniff'\n  },\n  {\n    key: 'X-Frame-Options',\n    value: 'DENY'\n  },\n  {\n    key: 'X-XSS-Protection',\n    value: '1; mode=block'\n  },\n  {\n    key: 'Referrer-Policy',\n    value: 'strict-origin-when-cross-origin'\n  },\n  {\n    key: 'Permissions-Policy',\n    value: 'camera=(), microphone=(), geolocation=()'\n  },\n  {\n    key: 'Content-Security-Policy',\n    value: `\n      default-src 'self';\n      script-src 'self' 'unsafe-eval' 'unsafe-inline';\n      style-src 'self' 'unsafe-inline';\n      img-src 'self' blob: data: https:;\n      font-src 'self';\n      object-src 'none';\n      base-uri 'self';\n      form-action 'self';\n      frame-ancestors 'none';\n      upgrade-insecure-requests;\n    `.replace(/\\s{2,}/g, ' ').trim()\n  }\n];\n\nconst nextConfig = {\n  async headers() {\n    return [\n      {\n        // Apply to all routes\n        source: '/:path*',\n        headers: securityHeaders,\n      },\n    ];\n  },\n};\n\nmodule.exports = nextConfig;\n",[31,190,188],{"__ignoreMap":142},[125,192,193,197],{"number":145},[129,194,196],{"id":195},"different-headers-for-different-routes","Different headers for different routes",[134,198,201],{"className":199,"code":200,"language":139},[137],"const nextConfig = {\n  async headers() {\n    return [\n      {\n        source: '/:path*',\n        headers: securityHeaders,\n      },\n      {\n        // Stricter CSP for admin routes\n        source: '/admin/:path*',\n        headers: [\n          ...securityHeaders,\n          {\n            key: 'Content-Security-Policy',\n            value: \"default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self'; frame-ancestors 'none';\"\n          }\n        ],\n      },\n      {\n        // Allow embedding for widget route\n        source: '/widget',\n        headers: [\n          ...securityHeaders.filter(h => h.key !== 'X-Frame-Options'),\n          {\n            key: 'Content-Security-Policy',\n            value: \"frame-ancestors 'self' https://trusted-domain.com;\"\n          }\n        ],\n      },\n    ];\n  },\n};\n\nmodule.exports = nextConfig;\n",[31,202,200],{"__ignoreMap":142},[40,204,206],{"id":205},"method-3-using-middleware-dynamic-headers","Method 3: Using Middleware (Dynamic Headers)",[18,208,209],{},"Use middleware when you need dynamic headers like CSP nonces.",[125,211,212,216],{"number":127},[129,213,215],{"id":214},"create-middlewarets-in-your-project-root","Create middleware.ts in your project root",[134,217,220],{"className":218,"code":219,"language":139},[137],"import { NextResponse } from 'next/server';\nimport type { NextRequest } from 'next/server';\n\nexport function middleware(request: NextRequest) {\n  // Generate a unique nonce for this request\n  const nonce = Buffer.from(crypto.randomUUID()).toString('base64');\n\n  // Build CSP with nonce\n  const cspHeader = `\n    default-src 'self';\n    script-src 'self' 'nonce-${nonce}' 'strict-dynamic';\n    style-src 'self' 'nonce-${nonce}';\n    img-src 'self' blob: data: https:;\n    font-src 'self';\n    object-src 'none';\n    base-uri 'self';\n    form-action 'self';\n    frame-ancestors 'none';\n    upgrade-insecure-requests;\n  `.replace(/\\s{2,}/g, ' ').trim();\n\n  // Clone the request headers\n  const requestHeaders = new Headers(request.headers);\n  requestHeaders.set('x-nonce', nonce);\n\n  // Create the response\n  const response = NextResponse.next({\n    request: {\n      headers: requestHeaders,\n    },\n  });\n\n  // Set security headers\n  response.headers.set('Content-Security-Policy', cspHeader);\n  response.headers.set('X-Content-Type-Options', 'nosniff');\n  response.headers.set('X-Frame-Options', 'DENY');\n  response.headers.set('X-XSS-Protection', '1; mode=block');\n  response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');\n  response.headers.set('Permissions-Policy', 'camera=(), microphone=(), geolocation=()');\n  response.headers.set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');\n\n  return response;\n}\n\nexport const config = {\n  matcher: [\n    /*\n     * Match all request paths except:\n     * - api (API routes)\n     * - _next/static (static files)\n     * - _next/image (image optimization files)\n     * - favicon.ico (favicon file)\n     */\n    '/((?!api|_next/static|_next/image|favicon.ico).*)',\n  ],\n};\n",[31,221,219],{"__ignoreMap":142},[125,223,224,228],{"number":145},[129,225,227],{"id":226},"use-the-nonce-in-your-components","Use the nonce in your components",[134,229,232],{"className":230,"code":231,"language":139},[137],"// app/layout.tsx\nimport { headers } from 'next/headers';\n\nexport default function RootLayout({\n  children,\n}: {\n  children: React.ReactNode;\n}) {\n  const nonce = headers().get('x-nonce') || '';\n\n  return (\n    \u003Chtml lang=\"en\">\n      \u003Chead>\n        \u003Cscript\n          nonce={nonce}\n          dangerouslySetInnerHTML={{\n            __html: `console.log('Inline script with nonce');`,\n          }}\n        />\n      \u003C/head>\n      \u003Cbody>{children}\u003C/body>\n    \u003C/html>\n  );\n}\n",[31,233,231],{"__ignoreMap":142},[125,235,236,240],{"number":161},[129,237,239],{"id":238},"configure-script-component-with-nonce-nextjs-13","Configure Script component with nonce (Next.js 13+)",[134,241,244],{"className":242,"code":243,"language":139},[137],"// components/Analytics.tsx\nimport Script from 'next/script';\nimport { headers } from 'next/headers';\n\nexport function Analytics() {\n  const nonce = headers().get('x-nonce') || '';\n\n  return (\n    \u003CScript\n      nonce={nonce}\n      strategy=\"afterInteractive\"\n      dangerouslySetInnerHTML={{\n        __html: `\n          // Your analytics code here\n          console.log('Analytics loaded');\n        `,\n      }}\n    />\n  );\n}\n",[31,245,243],{"__ignoreMap":142},[247,248,249,252],"warning-box",{},[18,250,251],{},"Security Checklist for Vercel Deployments",[45,253,254,257,260,263,266,269],{},[48,255,256],{},"Verify vercel.json is in the project root (not in a subdirectory)",[48,258,259],{},"Test CSP in report-only mode before enforcing",[48,261,262],{},"Check that all third-party scripts are allowed in CSP",[48,264,265],{},"Ensure HSTS max-age is appropriate (start with 300, increase to 31536000)",[48,267,268],{},"Verify headers don't conflict between vercel.json and next.config.js",[48,270,271],{},"Test on preview deployments before production",[40,273,275],{"id":274},"how-to-verify-it-worked","How to Verify It Worked",[129,277,279],{"id":278},"method-1-vercel-dashboard","Method 1: Vercel Dashboard",[281,282,283,286,289,292],"ol",{},[48,284,285],{},"Go to your project in the Vercel dashboard",[48,287,288],{},"Click on a deployment",[48,290,291],{},"Go to \"Functions\" or \"Edge Functions\" tab",[48,293,294],{},"Check the logs for any header-related errors",[129,296,298],{"id":297},"method-2-browser-devtools","Method 2: Browser DevTools",[281,300,301,304,307,310,313,316],{},[48,302,303],{},"Visit your deployed site",[48,305,306],{},"Open DevTools (F12)",[48,308,309],{},"Go to Network tab",[48,311,312],{},"Reload the page",[48,314,315],{},"Click on the document request",[48,317,318],{},"Check Response Headers section",[129,320,322],{"id":321},"method-3-command-line","Method 3: Command Line",[134,324,327],{"className":325,"code":326,"language":139},[137],"# Check headers with curl\ncurl -I https://your-app.vercel.app\n\n# Expected output:\n# HTTP/2 200\n# x-content-type-options: nosniff\n# x-frame-options: DENY\n# strict-transport-security: max-age=31536000; includeSubDomains\n# referrer-policy: strict-origin-when-cross-origin\n# content-security-policy: default-src 'self'; ...\n",[31,328,326],{"__ignoreMap":142},[129,330,332],{"id":331},"method-4-online-scanner","Method 4: Online Scanner",[18,334,335,336,343],{},"Use ",[337,338,342],"a",{"href":339,"rel":340},"https://securityheaders.com",[341],"nofollow","securityheaders.com"," to get a comprehensive report and grade.",[40,345,347],{"id":346},"common-errors-and-troubleshooting","Common Errors and Troubleshooting",[129,349,351],{"id":350},"headers-not-appearing-after-deployment","Headers not appearing after deployment",[45,353,354,361,367,373],{},[48,355,356,360],{},[357,358,359],"strong",{},"vercel.json location",": Must be in project root, not in a subdirectory",[48,362,363,366],{},[357,364,365],{},"JSON syntax error",": Validate your JSON at jsonlint.com",[48,368,369,372],{},[357,370,371],{},"Caching",": Wait a few minutes or try an incognito window",[48,374,375,378],{},[357,376,377],{},"Build errors",": Check Vercel dashboard for deployment errors",[129,380,382],{"id":381},"conflicting-headers","Conflicting headers",[45,384,385,391],{},[48,386,387,390],{},[357,388,389],{},"vercel.json vs next.config.js",": vercel.json takes precedence. Choose one method.",[48,392,393,396],{},[357,394,395],{},"Multiple source patterns",": More specific patterns override general ones",[129,398,400],{"id":399},"csp-blocking-resources","CSP blocking resources",[45,402,403,409,422],{},[48,404,405,408],{},[357,406,407],{},"Check console errors",": Browser console shows CSP violations",[48,410,411,414,415,418,419],{},[357,412,413],{},"Use report-only first",": Replace ",[31,416,417],{},"Content-Security-Policy"," with ",[31,420,421],{},"Content-Security-Policy-Report-Only",[48,423,424,427],{},[357,425,426],{},"Missing domains",": Add third-party domains to appropriate directives",[129,429,431],{"id":430},"middleware-not-running","Middleware not running",[45,433,434,440,446],{},[48,435,436,439],{},[357,437,438],{},"File location",": middleware.ts must be in project root or src/ directory",[48,441,442,445],{},[357,443,444],{},"Matcher config",": Ensure your routes aren't excluded by the matcher",[48,447,448,451,452],{},[357,449,450],{},"Export",": Must export a function named ",[31,453,454],{},"middleware",[456,457,458,461],"tip-box",{},[18,459,460],{},"Pro Tip",[18,462,463],{},"Use Vercel's preview deployments to test security header changes before merging to production. Each pull request gets a unique URL where you can verify headers work correctly.",[40,465,467],{"id":466},"frequently-asked-questions","Frequently Asked Questions",[469,470,471,478,489,495,501],"faq-section",{},[472,473,475],"faq-item",{"question":474},"Does vercel.json work with Next.js projects?",[18,476,477],{},"Yes, vercel.json works alongside next.config.js. However, for Next.js projects, it's often cleaner to configure headers in next.config.js. Headers from both files are merged, with vercel.json taking precedence for conflicts.",[472,479,481],{"question":480},"Can I use different headers for different routes on Vercel?",[18,482,483,484,488],{},"Yes. In vercel.json, use the 'source' property with glob patterns. For example, '/api/",[485,486,487],"em",{},"' for API routes or '/admin/","' for admin pages. More specific patterns take precedence.",[472,490,492],{"question":491},"Why aren't my Vercel headers showing up?",[18,493,494],{},"Common causes: vercel.json not in project root, JSON syntax errors, deployment not complete, or CDN caching. Try a fresh deployment and check the Vercel dashboard for build errors.",[472,496,498],{"question":497},"Does Vercel add any security headers by default?",[18,499,500],{},"Vercel adds X-Vercel-Id for request tracking but does not add security headers by default. You must configure all security headers yourself via vercel.json, next.config.js, or middleware.",[472,502,504],{"question":503},"Should I use vercel.json or middleware for security headers?",[18,505,506],{},"Use vercel.json for static headers that don't change. Use middleware for dynamic headers like CSP nonces or request-specific logic. You can combine both approaches.",[508,509,512,516],"cta-box",{"href":510,"label":511},"/","Start Free Scan",[40,513,515],{"id":514},"scan-your-vercel-deployment","Scan Your Vercel Deployment",[18,517,518],{},"Check if your Vercel-hosted site has all the security headers configured correctly.",[520,521,522,528,533,538],"related-articles",{},[523,524],"related-card",{"description":525,"href":526,"title":527},"Complete guide to all essential security headers.","/blog/how-to/add-security-headers","Security Headers Overview",[523,529],{"description":530,"href":531,"title":532},"Configure headers with _headers and netlify.toml.","/blog/how-to/netlify-headers","Security Headers on Netlify",[523,534],{"description":535,"href":536,"title":537},"Deep dive into Content Security Policy setup.","/blog/how-to/csp-setup","CSP Configuration Guide",[523,539],{"description":540,"href":541,"title":542},"Complete security guide for Vercel deployments.","/blog/best-practices/vercel","Vercel Security Best Practices",{"title":142,"searchDepth":544,"depth":544,"links":545},2,[546,547,548,554,558,563,569,575,576],{"id":42,"depth":544,"text":43},{"id":62,"depth":544,"text":63},{"id":119,"depth":544,"text":120,"children":549},[550,552,553],{"id":131,"depth":551,"text":132},3,{"id":148,"depth":551,"text":149},{"id":164,"depth":551,"text":165},{"id":174,"depth":544,"text":175,"children":555},[556,557],{"id":183,"depth":551,"text":184},{"id":195,"depth":551,"text":196},{"id":205,"depth":544,"text":206,"children":559},[560,561,562],{"id":214,"depth":551,"text":215},{"id":226,"depth":551,"text":227},{"id":238,"depth":551,"text":239},{"id":274,"depth":544,"text":275,"children":564},[565,566,567,568],{"id":278,"depth":551,"text":279},{"id":297,"depth":551,"text":298},{"id":321,"depth":551,"text":322},{"id":331,"depth":551,"text":332},{"id":346,"depth":544,"text":347,"children":570},[571,572,573,574],{"id":350,"depth":551,"text":351},{"id":381,"depth":551,"text":382},{"id":399,"depth":551,"text":400},{"id":430,"depth":551,"text":431},{"id":466,"depth":544,"text":467},{"id":514,"depth":544,"text":515},"how-to","2026-01-28","Step-by-step guide to adding security headers on Vercel. Configure via vercel.json, Next.js middleware, and edge functions. Includes CSP, HSTS, and all essential headers.",false,"md",[583,585,587,589,591],{"question":474,"answer":584},"Yes, vercel.json works alongside next.config.js. However, for Next.js projects, it's often cleaner to configure headers in next.config.js using the headers() function. Headers from vercel.json and next.config.js are merged, with vercel.json taking precedence for conflicts.",{"question":480,"answer":586},"Yes. In vercel.json, use the 'source' property with glob patterns to apply headers to specific routes. For example, use '/api/*' to only add headers to API routes, or '/admin/*' to add stricter headers for admin pages.",{"question":491,"answer":588},"Common causes: 1) vercel.json is not in the project root, 2) JSON syntax errors in the file, 3) Deployment hasn't completed yet, 4) CDN caching old responses. Try a fresh deployment and check the Vercel dashboard for build errors.",{"question":497,"answer":590},"Vercel adds X-Vercel-Id for request tracking but does not add security headers by default. You must configure all security headers (CSP, HSTS, X-Frame-Options, etc.) yourself via vercel.json, next.config.js, or middleware.",{"question":503,"answer":592},"Use vercel.json for static headers that don't change between requests. Use middleware when you need dynamic headers like CSP nonces, request-specific values, or conditional logic based on the request path or user.","yellow",null,{},true,"Complete guide to setting up security headers on Vercel deployments with vercel.json and middleware.","/blog/how-to/vercel-headers","[object Object]","HowTo",{"title":5,"description":579},{"loc":598},"blog/how-to/vercel-headers",[],"summary_large_image","bkNJmi55BGNTWPfUKzM1cXjsihPEUThBF4ktP3Kh2Ac",1775843927125]