A history-based router for Svelte Single Page Applications (SPAs).
Tip
Svelte's official routing library is SvelteKit. This library is designed for small or simple projects.
- History-Based Routing
- Path matching and Variable Capture using regular expressions
- Resolver for dynamic routing, code splitting, data preloading, etc.
- Hash-based Routing
- Nested router
- Server-Side Rendering (SSR)
$ npm install --save-dev svelte-spa-history-router
$ # or
$ yarn add svelte-spa-history-router
Import Router
and include it in your main component (typically App.svelte).
For example:
# App.svelte
<script>
import { Router } from 'svelte-spa-history-router';
import Home from './Home.svelte';
import Article from './Article.svelte';
import NotFound from './NotFound.svelte';
const routes = [
{ path: '/', component: Home},
{ path: '/posts/(?<postId>.*)', component: Article},
{ path: '.*', component: NotFound},
];
</script>
<Router {routes}/>
-
Router
requires theroutes
parameter. -
routes
is a list of route objects. Each route object must have apath
property, and either acomponent
orresolver
property.path
can be a regular expression.^
and$
are automatically added when matching.component
is a Svelte component. There are no specific requirements for the component.resolver
is a function to return a dynamic component and return props of the type expected by the component.
-
Matching is simply performed in the order defined by
routes
.
For routes that do not use a resolver, matched parameters are passed to the component via the params
prop.
For example:
# App.svelte
<script>
import { Router } from "svelte-spa-history-router";
import ItemPage from "./ItemPage.svelte";
const routes = [
{ path: "/items/(?<itemId>\\d+)", component: ItemPage },
];
</script>
<Router {routes}/>
# ItemPage.svelte
<script lang="ts">
let { params }: { params: { itemId: string } } = $props();
const itemId = $derived(parseInt(params.itemId));
</script>
<div>
{ itemId }
</div>
To navigate to another page, link
and push
are available.
link
turns an<a>
tag into a spa navigation link. For example:
<script>
import { link } from 'svelte-spa-history-router';
</script>
<a use:link href="/">Home</a>
push
navigates to the given path programatically.
<script>
import { push } from 'svelte-spa-history-router';
<script>
<button onclick={ () => push('/') }>Go to Home</button>
A resolver is a mechanism for dynamically determining which component to render. It can be used for various purposes, such as:
Example: code splitting (dynamic import)
<script>
import { Router } from 'svelte-spa-history-router';
const routes = [
{ path: '/', resolver: () => import("Home.svelte") },
];
</script>
<Router {routes}/>
Example: dynamic routing and pass value to component props.
<script lang="ts">
import { Router } from 'svelte-spa-history-router';
import Article from "./Article.svelte";
import NotFound from "./NotFound.svelte";
async function prefetchArticle(params: Record<string, string>) {
const article = await getArticle(params.postId);
if (article) {
return { component: Article, props: { article } };
} else {
return NotFound;
}
}
const routes = [
{ path: '/posts/(?<postId>.*)', resolver: prefetchArticle },
];
</script>
<Router {routes}/>
Tip
This routing mechanism allows preloading data before rendering the component, minimizing layout flicker and improving perceived performance. While skeleton screens are a common modern pattern, this approach can simplify state handling in simple apps.
Example: guard
<script lang="ts">
import { Router, redirect } from 'svelte-spa-history-router';
import Admin from "./Admin.svelte";
let user: User = $state()
function adminGuard() {
if (!isAdmin(user)) {
return redirect("/");
}
return Admin;
}
const routes = [
{ path: '/', component: Home },
{ path: '/admin', resolver: adminGuard },
];
</script>
<Router {routes}/>
A resolver must return one of the following types:
| Component
| { component: Component, props: ComponentProps }
| Redirection // return value of `redirect()`
| Promise<
| Component
| { component: Component, props: ComponentProps }
| Redirection
| { default: Component } // return value of `import()`
>
(Added in v2.0.0)
(Changed resolver interface in v3.0.0-next.1)
state to detect URL changes (including query string or hash)
<script>
import { currentURL } from "svelte-spa-history-router";
let name = $derived(currentURL().searchParams.get("name") ?? "unknown");
</script>
<div>{ name }</div>
(Added in v2.1.0)
(Replaced with Svelte5's $state()
in v3.0.0-next.1)
svelte-spa-history-router provides Route
type to check combination of component and props.
<script lang="ts">
import type { Route } from "svelte-spa-history-router"
// BlogPost requires article property
import type BlogPost from "./pages/BlogPost.svelte"
import Top from "./pages/Top.svelte"
const routes: [
Route<typeof Top>,
Route<typeof BlogPost | typeof NotFound>,
] = [
{ path: "/", component: Top },
{
path: "/blog/posts/(?<slug>.*)",
resolver: async (params: Record<"slug", string>) => {
const article = await getArticle(params.slug);
if (article) {
const component = (await import("./pages/BlogPost.svelte")).default;
return { component, props: { article } }
} else {
return NotFound;
}
},
},
];
</script>
(Added in v3.0.0-next.1)
MIT License.
A history-based router generally requires server-side routing to support direct links or page reloads.
For example, the following nginx configuration allows proper routing:
location / {
try_files $uri /index.html =404;
}
If you are considering using firebase hosting for your application, rewrite may be useful.
svelte-spa-history-router is inspired by svelte-spa-router and Svelte Router SPA.
If you don't need support for both history-based routing and regular expressions, I recommend these powerful routers.