sveltejs/kit · error · Error

" " header is already set

Error message

"${key}" header is already set

What it means

SvelteKit stores response headers keyed by lowercase name. When you call event.setHeaders() (or set a header in an endpoint response) with a header name that maps to a lowercase key already present, it throws instead of silently overwriting — except for Server-Timing, which is appended per spec. This prevents accidental double-setting of headers like content-type or cache-control.

Solutions

  1. Find the code path setting the duplicate header (hook vs load vs endpoint) and remove one of the two setHeaders calls
  2. Use case-consistent naming and centralize header setting in src/hooks.server.js so it happens only once
  3. If appending is intended (e.g. timing), use the Server-Timing header which is concatenated, or merge values into a single comma-separated string yourself

Example fix

// before (hooks.server.js + page both set it)
export const handle = async ({ event, resolve }) => { event.setHeaders({ 'x-frame-options': 'DENY' }); return resolve(event); };
// in +page.server.js
event.setHeaders({ 'X-Frame-Options': 'DENY' });
// after
export const handle = async ({ event, resolve }) => { event.setHeaders({ 'x-frame-options': 'DENY' }); return resolve(event); };
// removed duplicate setHeaders in +page.server.js
Defensive patterns

Strategy: validation

Validate before calling

function canSetHeader(headers, key) {
  return !(key.toLowerCase() in headers) || key.toLowerCase() === 'server-timing';
}
// usage: if (canSetHeader(existing, 'x-frame-options')) event.setHeaders({ 'x-frame-options': 'DENY' });

Prevention

When it happens

Trigger: Calling event.setHeaders({ 'Content-Type': 'x' }) in load and also returning a Response with a content-type header; calling setHeaders twice with the same (case-insensitive) header in a handle hook and in load; setting a header in +layout.js load and again in +page.js load.

Common situations: A handle hook adds security headers globally while a page also sets the same header; migrating an endpoint from returning a plain object with headers to setHeaders; framework upgrade made duplicate-header collisions throw instead of being ignored.

Related errors


AI-assisted analysis of sveltejs/kit@03f1687fe6 (2026-09-02). Data as JSON: /api/errors/568b16acec067797. Report an issue: GitHub.

Appendix: source

Thrown at packages/kit/src/runtime/server/respond.js:225

		setHeaders: (new_headers) => {
			if (DEV) {
				validateHeaders(new_headers);
			}

			for (const key in new_headers) {
				const lower = key.toLowerCase();
				const value = new_headers[key];

				if (lower === 'set-cookie') {
					throw new Error(
						'Use `event.cookies.set(name, value, options)` instead of `event.setHeaders` to set cookies'
					);
				} else if (lower in headers) {
					// appendHeaders-style for Server-Timing https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Server-Timing
					if (lower === 'server-timing') {
						headers[lower] += ', ' + value;
					} else {
						throw new Error(`"${key}" header is already set`);
					}
				} else {
					headers[lower] = value;

					if (state.prerendering && lower === 'cache-control') {
						state.prerendering.cache = /** @type {string} */ (value);
					}
				}
			}
		},
		url,
		isDataRequest: is_data_request,
		isSubRequest: state.depth > 0,
		isRemoteRequest: !!remote_id
	};

	// @ts-expect-error this has to be assigned lazily
	event.fetch = create_fetch({

View on GitHub (pinned to 03f1687fe6)