sveltejs/kit · error · Error

"${key}" 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.

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)

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.