Pick your starting point
Jump to the section that matches the version you're upgrading from.
- Upgrading within v5.x — already on v5.0 or later, moving to v5.3.0
- Upgrading from v4.x to v5.0 — full rewrite to Svelte 5 runes
Upgrading within v5.x (→ v5.3.0)
Most v5.x → v5.3.0 upgrades require no code changes. The new helpers/nav-tree and subtree: true features are purely additive,
and the stacked use:active fix is backward compatible. The only breaking change
in this version range is an older v5.2.0 removal (covered further down).
v5.3.0 — no migration needed
No breaking changes for stable consumers. The stacked use:active fix is backward
compatible for the overwhelmingly common single-action-per-node case (existing tests pass
unchanged). Adopt the new subtree: true option when you want to collapse two
stacked use:active calls into one, and helpers/nav-tree when you want
a single tree to drive both routes and a permission-filtered sidebar — see the What's New page for examples. disabled nav-tree nodes
render as forbidden in both filter modes, and the new FilterOptions.disabledClassName lets you style "coming soon" placeholders
distinctly from permission-denied items.
5.3.0-rc01 build and used
the isHidden field on any nav-tree node, rename it to hidden — the
field was renamed before stable release to match the KeenMate web-components convention (bare
HTML-attribute names on data-model booleans, mirroring @keenmate/web-multiselect's MultiSelectOption.isDisabled → disabled). The shape, semantics, and getter reactivity are unchanged — a pure
find-and-replace. The helper predicate isNodeHidden(node) keeps its is* prefix because it's a function, not a field. If you're coming from a stable
release (5.2.x or earlier), the field has always been hidden — nothing to do.v5.2.1 — no migration needed
Single-issue release: bare-function routes ({ '/': Foo }) no longer throw Invalid component object under Svelte 5.5+ / Vite 7 / plugin-svelte 6. If you
were on an older toolchain you wouldn't have hit it; if you were on the newer one you were
probably workarounded with wrap({ component: Foo }) and can now drop that
workaround. Either way: just upgrade.
⚠️ showToast removed from GlobalErrorHandler config (v5.2.0)
The built-in error toast is gone as of v5.2.0. The render guard was broken under
the default navigateSafe strategy (the toast was effectively unobservable), so
rather than patch it, the library now expects you to wire your own toast library inside the onError callback. TypeScript will flag the now-unknown property.
navigationContext() now returns null when nothing was passed (v5.2.0)
In v5.1.x and earlier, navigationContext() could return { _routeName: '/some-path' } after any push() — the router's
internal _routeName key leaked through the public accessor, so any {#if !navigationContext()} branch was effectively unreachable. As of v5.2.0,
the public accessor filters internal keys and returns null if no user-visible
context exists.
If you were relying on the truthy-but-empty behavior to detect "navigation
happened" (you almost certainly weren't), use location() for that. If you need
the raw context including internal keys, import getRawNavigationContext() from /utils — but note that those keys are internal and may change.
routeContext() — use the current name
If you were importing routerouteContext() (a mangled name from an early
find-replace accident) or the README's old routeUserData(), both are gone. The
correct, current name is routeContext().
New features worth adopting
subtree: trueonuse:active(v5.3.0) — one action call for "parent stays active on its index AND on every nested URL"; pair withsubtreeClassNamefor distinct stylinghelpers/nav-tree(v5.3.0) — permission-aware filtering for tree-shaped menus; one tree drives both routes and sidebardefineRoutes()(v5.2.0) — type-safe routes/nav/paths with full IDE autocompletesetCurrentUser()(v5.2.0) — drop your customgetCurrentUsergetter and get reactivehasPermission()for freerevalidateCurrentRoute()(v5.2.0) — re-check the active route on websocket permission updates without remountingrelativeLocationon event payloads (v5.2.0) — prefix-stripped path for nested routers
See What's New for full details on each.
Upgrading from v4.x to v5.0
Version 5.0 is a major release built for Svelte 5, featuring a complete rewrite using runes instead of stores. This section covers all breaking changes and provides step-by-step upgrade instructions.
Breaking Changes Summary
| Change | v4.x (Old) | v5.0 (New) |
|---|---|---|
| State management | Svelte stores | Svelte 5 runes ($state, $derived) |
| State access | $location, $params | location(), routeParams() |
| Parameter name | params | routeParams |
| Router events | onrouteLoading | onRouteLoading (camelCase) |
| Logger API | setDebugLoggingEnabled() | enableLogging(), setCategoryLevel() |
| Unauthorized handling | Hash-based navigation + onUnauthorized | Component-based + configurePermissions() |
| Import paths | /stores export path exists | No /stores path - removed |
Step-by-Step Migration
1. Update Dependencies
2. Update State Access Patterns
Replace store subscriptions with function calls:
3. Update Route Component Props
Route components receive params via $props():
4. Update Router Event Handlers
Event handler props are now camelCase:
5. Update Logger API
The debug logging API has been completely redesigned:
6. Update Permission System
Unauthorized handling is now component-based:
7. Remove /stores Import Path
The /stores export path no longer exists:
Common Migration Issues
Issue: "Cannot read properties of undefined (reading 'before')"
Cause: Using sync component import with createProtectedRoute()
Solution: This is fixed in v5.0.0. Both patterns now work:
Issue: "enableCategory is not a function"
Cause: Using old logger API name
Solution: Use setCategoryLevel() instead:
Issue: "$params is not defined"
Cause: Trying to use store syntax with v5
Solution: Use function call with $derived:
Issue: "Missing './stores' specifier"
Cause: Trying to import from removed /stores path
Solution: Import from main module:
Migration Checklist
Before You Start
- ☐ Back up your project
- ☐ Review breaking changes list
- ☐ Ensure tests exist for critical routes
Code Changes
- ☐ Update Svelte to v5.0+
- ☐ Update @keenmate/svelte-spa-router to v5.0
- ☐ Replace all
$location,$params,$querystringwith function calls - ☐ Rename
paramstorouteParams - ☐ Update Router event props to camelCase
- ☐ Replace
export let paramswithlet { routeParams } = $props() - ☐ Update logger API calls
- ☐ Update permission system configuration
- ☐ Remove /stores import paths
Testing
- ☐ Test all routes navigate correctly
- ☐ Test route parameters work
- ☐ Test protected routes and permissions
- ☐ Test navigation guards and conditions
- ☐ Test referrer tracking (if used)
- ☐ Test error handling (if configured)
Cleanup
- ☐ Remove old debug logging code
- ☐ Update documentation
- ☐ Review and remove unused imports
New Features to Explore
After migrating, consider adopting these new v5 features:
- Category-based Logging: Fine-grained debug logging with 12 categories
- Referrer Tracking: Automatic previous route tracking with scroll position restoration
- goBack() Helper: Navigate to referrer with automatic scroll restoration
- Hierarchical Routes: Automatic breadcrumb and permission inheritance
- Global Error Handler: Comprehensive error handling with recovery strategies
- Enhanced Permission System: Component-based unauthorized handling
- Tree Route Structure:
createHierarchy()for nested route definitions
See the What's New in v5 page for detailed information about all new features.
Getting Help
If you encounter issues during migration:
- Check the API Reference for updated function signatures
- Review example code in the documentation
- Enable debug logging to troubleshoot routing issues
- Open an issue on GitHub