Fix: Setting Response Headers Not Supported in Browser in Nuxt

Error message:
Setting response headers is not supported in the browser.
ssr 2025-01-25

What Causes This Warning?

This warning appears when you try to use server-side response utilities (like setResponseHeaders, setHeader, or setCookie) in client-side code. These functions only work during server-side rendering.

The Problem

<script setup>
// ❌ Wrong - these don't work on client
setResponseHeader('X-Custom-Header', 'value')
setResponseStatus(404)
setCookie(event, 'name', 'value')  // Wrong context
</script>

The Fix

Check for Server Context

<script setup>
// ✅ Correct - only run on server
if (process.server) {
  const event = useRequestEvent()
  if (event) {
    setResponseHeader(event, 'X-Custom-Header', 'value')
  }
}
</script>

Use Server Middleware

// server/middleware/headers.ts
export default defineEventHandler((event) => {
  // ✅ Always server-side
  setResponseHeader(event, 'X-Custom-Header', 'value')
  setResponseHeader(event, 'X-Frame-Options', 'DENY')
})

Common Scenarios

Setting Cache Headers

<script setup>
// ❌ Wrong - won't work on client navigation
setResponseHeader('Cache-Control', 'max-age=3600')
</script>

Solution - Use Route Rules:

// nuxt.config.ts
export default defineNuxtConfig({
  routeRules: {
    '/products/**': {
      headers: {
        'Cache-Control': 'max-age=3600'
      }
    }
  }
})

Custom Response Status

<script setup>
// ❌ Wrong - status codes don't apply on client
const { data, error } = await useFetch('/api/data')
if (error.value) {
  setResponseStatus(404)  // Warning on client!
}
</script>

Solution:

<script setup>
const { data, error } = await useFetch('/api/data')

// ✅ Set status only on server
if (process.server && error.value) {
  const event = useRequestEvent()
  if (event) {
    setResponseStatus(event, 404)
  }
}

// Handle client-side differently
if (process.client && error.value) {
  // Redirect or show error UI
  navigateTo('/404')
}
</script>

Setting Cookies

<script setup>
// ❌ Wrong - using H3 cookie function on client
setCookie(event, 'theme', 'dark')
</script>

Solution:

<script setup>
// ✅ Use useCookie which works on both server and client
const theme = useCookie('theme')
theme.value = 'dark'
</script>

Security Headers

<script setup>
// ❌ Wrong - security headers must be set server-side
setResponseHeaders({
  'X-Content-Type-Options': 'nosniff',
  'X-Frame-Options': 'DENY'
})
</script>

Solution - Server Middleware:

// server/middleware/security.ts
export default defineEventHandler((event) => {
  // ✅ Security headers set on every request
  setResponseHeaders(event, {
    'X-Content-Type-Options': 'nosniff',
    'X-Frame-Options': 'DENY',
    'X-XSS-Protection': '1; mode=block',
    'Referrer-Policy': 'strict-origin-when-cross-origin'
  })
})

Or in Nitro Config:

// nuxt.config.ts
export default defineNuxtConfig({
  nitro: {
    routeRules: {
      '/**': {
        headers: {
          'X-Content-Type-Options': 'nosniff',
          'X-Frame-Options': 'DENY'
        }
      }
    }
  }
})

Understanding SSR Context

Server-Only Functions

These only work during SSR:

// Only work on server
setResponseHeader(event, 'name', 'value')
setResponseHeaders(event, { ... })
setResponseStatus(event, 200)
appendResponseHeader(event, 'name', 'value')
removeResponseHeader(event, 'name')

// Getting request info (server-only)
getRequestHeaders(event)
getRequestHeader(event, 'name')
getCookie(event, 'name')

Universal Functions

These work on both server and client:

// useCookie works everywhere
const cookie = useCookie('name')
cookie.value = 'value'

// useRequestHeaders gets headers on server, empty on client
const headers = useRequestHeaders()

// useRequestURL works everywhere
const url = useRequestURL()

Proper Patterns

Conditional Server Code

<script setup>
// ✅ Check environment before using server functions
if (process.server) {
  const event = useRequestEvent()
  if (event) {
    // Safe to use server functions
    setResponseHeader(event, 'X-Custom', 'value')
  }
}
</script>

Server Composable

// composables/useServerHeaders.ts
export function useServerHeaders(headers: Record<string, string>) {
  if (process.server) {
    const event = useRequestEvent()
    if (event) {
      setResponseHeaders(event, headers)
    }
  }
}
<script setup>
// ✅ Clean usage
useServerHeaders({
  'Cache-Control': 'max-age=3600',
  'X-Custom': 'value'
})
</script>

Route-Specific Headers

// nuxt.config.ts
export default defineNuxtConfig({
  routeRules: {
    // Static assets
    '/assets/**': {
      headers: {
        'Cache-Control': 'public, max-age=31536000, immutable'
      }
    },

    // API routes
    '/api/**': {
      headers: {
        'Cache-Control': 'no-store'
      }
    },

    // HTML pages
    '/**': {
      headers: {
        'Cache-Control': 's-maxage=3600, stale-while-revalidate'
      }
    }
  }
})

Quick Checklist

  • Response header functions are only called on server
  • Use process.server guard before server-only code
  • Use useCookie() instead of setCookie() in components
  • Use route rules for static header configuration
  • Use server middleware for dynamic headers
  • Check useRequestEvent() returns a value before using