sveltejs/kit · warning

Avoid using `history.pushState(...)` and `history.replaceSta

Error message

Avoid using `history.pushState(...)` and `history.replaceState(...)` as these will conflict with SvelteKit's router. Use `goto(...)` from `$app/navigation` instead.

What it means

SvelteKit's client-side router takes ownership of the History API; calling the raw `history.pushState()`/`history.replaceState()` directly bypasses the router and desynchronizes navigation state. The router monkey-patches these methods and warns once when an app (outside node_modules) calls them, suggesting `goto()` instead.

Source

Thrown at packages/kit/src/runtime/client/client.js:139

		if (warned) return;

		// Rather than saving a pointer to the original history methods, which would prevent monkeypatching by other libs,
		// inspect the stack trace to see if we're being called from within SvelteKit.
		let stack = new Error().stack?.split('\n');
		if (!stack) return;
		if (!stack[0].includes('https:') && !stack[0].includes('http:')) stack = stack.slice(1); // Chrome includes the error message in the stack

		// skip over `warn` and the place where `warn` was called
		const frame = stack[2];

		// Ignore calls that happen inside dependencies, including SvelteKit.
		// The second condition is only relevant when developing SvelteKit and running it, as there's no node_modules in the stack then (but we still do it to not get repeatedly confused)
		// `frame` can be falsy if we came from an anonymous function
		if (frame?.includes('node_modules') || frame?.includes(current_module_url)) return;

		warned = true;

		console.warn(
			"Avoid using `history.pushState(...)` and `history.replaceState(...)` as these will conflict with SvelteKit's router. Use `goto(...)` from `$app/navigation` instead."
		);
	};

	const push_state = history.pushState;
	history.pushState = (...args) => {
		warn();
		return push_state.apply(history, args);
	};

	const replace_state = history.replaceState;
	history.replaceState = (...args) => {
		warn();
		return replace_state.apply(history, args);
	};
}

/** @param {number} index */

View on GitHub (pinned to 03f1687fe6)

Solutions

  1. Replace `history.pushState(state, '', url)` with `goto(url, { state })` from `$app/navigation`
  2. Replace `history.replaceState(state, '', url)` with `goto(url, { replace: true, state })`
  3. Use the `$app/state`/`navigating` stores or `beforeNavigate` if you need URL state tied to SvelteKit's router state

Example fix

// before
history.pushState({ tab: 'settings' }, '', '/settings');
// after
import { goto } from '$app/navigation';
goto('/settings', { state: { tab: 'settings' } });
Defensive patterns

Strategy: fallback

Validate before calling

// ensure navigation goes through the router
function appPush(url, state) { return goto(url, { state }); }

Type guard

function isHistoryCall(fn) { return fn === history.pushState || fn === history.replaceState; }

Try / catch

try { await goto(url, { state }); } catch (e) { console.error('navigation failed', e); }

Prevention

When it happens

Trigger: Calling `history.pushState(state, '', url)` or `history.replaceState(...)` in app code (component, action, library bundled into the app) after the SvelteKit router has started; the patched method in packages/kit/src/runtime/client/client.js warns once unless the call comes from node_modules or SvelteKit itself.

Common situations: Tabs/modals/URL-state sync code written with plain History API; third-party widgets integrating history directly; porting a vanilla SPA page into SvelteKit.

Related errors


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