An SPA router for Svelte that allows you to divide & conquer your app with nested routers, snippets, and more.
Live demo: https://svelte5-router.vercel.app
- Supports Svelte 5, just one rune baby 🚀!
- Divide & conquer - use nested routers all over the place.
- Use components, snippets, or both!
- Use regex paths (e.g.
/foo/(.*?)/bar
) and/or named parameters together 🔥. - Use async routes simply with
component: async () => import("./my-component.svelte")
. - Base path support.
npm install @mateothegreat/svelte5-router
All you need to do is define your routes and then use the Router
component with the routes
array.
To make a link, use the route
directive with the href
attribute such as <a use:route href="/foo">foo</a>
.
In some cases you may not be able to use the base path of your app as the root path. For example, if you are using a nested router, you may want to use a base path of /foo
instead of /
.
Simply pass the basePath
prop to the Router
component and it will handle the rest:
No need to update your routes either, it will support both
/mybasepath/foo
and/foo
just fine.
<Router basePath="/mybasepath" {routes} />
Navigates to the given path.
Returns the value of the query parameter for the given key or null if the key does not exist.
A helper class for working with the query string.
Check it out at src/lib/query.svelte.ts. or import it with:
import { QueryString } from "@mateothegreat/svelte5-router";and start using it now!
Basic usage:
import { QueryString } from "@mateothegreat/svelte5-router";
const query = new QueryString();
query.get("foo", "bar"); // "bar"
query.set("foo", "baz");
query.toString(); // "foo=baz"
Using it with navigation:
import { QueryString } from "@mateothegreat/svelte5-router";
const query = new QueryString();
// ...
query.set("foo", "baz");
// ...
query.goto("/test"); // Navigates to "/test?foo=baz"
You can simply use static paths like /foo
or dynamic paths like /foo/(.*?)
with regex.
Example patterns:
Pattern | Description |
---|---|
/ |
The root path. |
/foo |
A static path. |
/foo/(.*?) |
A dynamic path. |
/cool/(.*?)/(.*?) |
A dynamic path with two parameters. |
(?<myRoute>.*) |
A dynamic path with a named parameter. |
For transparency, here's the type definition for a route:
Only
path
is required at a minimum with either pre/post hooks or a component/snippet.
export interface Route {
path: RegExp | string;
component?: Component<any> | Snippet;
props?: Record<string, any>;
pre?: PreHooks;
post?: PostHooks;
}
Hooks are typed as follows:
As you can see, you can pass an array of hooks or a single hook as a promise or not:
export type PreHooks = ((route: Route) => Route)[] | ((route: Route) => Promise<Route>)[] | ((route: Route) => Route) | ((route: Route) => Promise<Route>);
export type PostHooks = ((route: Route) => void)[] | ((route: Route) => Promise<void>)[] | ((route: Route) => void) | ((route: Route) => Promise<void>);
Use async routes simply with component: async () => import("./my-component.svelte")
.
const routes: Route[] = [
{
path: "simple",
component: Simple
},
{
path: "async",
component: async () => import("./lib/async/async.svelte")
}
];
For the quickest and easiest routes, you can use components:
const routes: Route[] = [
{
path: "/foo",
component: Foo
}
];
For more complex routing needs, you can use snippets:
<script lang="ts">
import { route, Router, type Route } from "@mateothegreat/svelte5-router";
import All from "./all.svelte";
const routes: Route[] = [
{
path: "/snippetsarecool",
component: mySnippet
}
];
</script>
{#snippet mySnippet()}
<div class="flex flex-col gap-3 bg-green-400 p-4">
I'm a snippet!<br />
Click on a link above to see the params..
</div>
{/snippet}
When your component is rendered, the route
object will be passed in as a prop. You can then access the parameter(s) of a route using the route.params
property:
<script lang="ts">
import type { Route } from "@mateothegreat/svelte5-router";
let { params }: { params: string[] } = $props();
</script>
<pre>{JSON.stringify(params, null, 2)}</pre>
If you were to route to /cool/bar/baz
, this will result in the following output:
[
"bar",
"baz"
]
You can pass props to a route by using the props
property on any route. These props will be passed to the component as a prop:
const routes: Route[] = [
{
path: "/user/profile",
component: UserProfile,
props: {
myProps: {
date: new Date(),
name: "mateothegreat"
}
}
}
];
Then, in your component, you can access the prop like this:
<script lang="ts">
let { myProps } = $props();
</script>
<pre>{JSON.stringify(myProps, null, 2)}</pre>
Use pre
and post
hooks to run before and after a route is rendered to do things like authentication, logging, etc.
Syntax | Location | Description |
---|---|---|
<Router pre={myHooks}> |
<Router/> |
Runs before any route is rendered. |
<Router post={myHooks}> |
<Router/> |
Runs after any route is rendered. |
{ path: "/", pre: () => {...}} |
Route |
Runs before the specific route is rendered. |
{ path: "/", post: () => {...}} |
Route |
Runs after the specific route is rendered. |
You can pass an array or single method for the
pre
andpost
hooks.
const routes: Route[] = [
{
path: "unprotected",
component: Unprotected
post: () => {
console.log("post hook fired");
}
},
{
path: "protected",
component: Protected,
// Use a pre hook to simulate a protected route:
pre: (route: Route) => {
console.log("pre hook #1 fired for route:", route);
// Crude example of checking if the user is logged in. A more
// sophisticated example would use a real authentication system
// and a server-side API.
if (!localStorage.getItem("token")) {
// By returning a new route, the user will be redirected to the
// new route and then the post hook(s) will be executed:
return {
path: "/login",
component: Login
};
} else {
// By returning a new route, the user will be redirected to the
// new route and then the post hook(s) will be executed:
return {
path: "/bankaccount",
component: BankAccount
};
}
},
post: [
(route: Route): void => {
console.log("post hook #1 fired for route:", route);
},
(route: Route): void => {
console.log("post hook #2 fired for route:", route);
}
]
}
];
<script lang="ts">
import { Router } from "@mateothegreat/svelte5-router";
const routes: Route[] = [
{
path: "",
component: Homepage
},
{
path: "about",
component: About
}
];
let navigating: boolean; // This is $state<boolean>
</script>
<Router bind:navigating {routes} />
<span class="rounded border border-zinc-800 px-1 py-0.5 text-orange-500">
{navigating ? "(true) navigating..." : "(false) idle"}
</span>
<script lang="ts">
import type { Route } from "@mateothegreat/svelte5-router";
import { route, Router } from "@mateothegreat/svelte5-router";
...
const routes: Route[] = [
{
path: "/",
component: Homepage
},
{
path: "about",
component: About
},
{
path: "settings",
component: Settings
}
];
</script>
<div class="flex gap-2">
<a use:route href="/">Home</a>
<a use:route href="/about">About</a>
<a use:route href="/settings">Settings</a>
</div>
<Router base="/" {routes} />
</div>
For a real world example, check out the test app.
To deploy to Vercel, add the following to your vercel.json
:
{
"routes": [{ "src": "/[^.]+", "dest": "/", "status": 200 }]
}