pnpm add @dvcol/svelte-simple-router
The minimal setup requires a RouterView
component and a list of routes.
The RouterView
component will render the component associated with the current route in place.
You can find a complete example in the demo app.
<script lang="ts">
import { RouterView } from '@dvcol/svelte-simple-router/components';
import type { Route, RouterOptions } from '@dvcol/svelte-simple-router/models';
import HelloComponent from '~/components/hello/HelloComponent.svelte';
import GoodbyeComponent from '~/components/goodbye/GoodbyeComponent.svelte';
const RouteName = {
Hello: 'hello',
Goodbye: 'goodbye',
Home: 'home',
Any: 'any',
} as const;
type RouteNames = (typeof RouteName)[keyof typeof RouteName];
export const routes: Readonly<Route<RouteNames>[]> = [
{
name: RouteName.Home,
path: '/',
redirect: {
name: RouteName.Hello,
},
},
{
name: RouteName.Hello,
path: `/${RouteName.Hello}`,
component: HelloComponent
},
{
name: RouteName.Goodbye,
path: `/${RouteName.Goodbye}`,
component: GoodbyeComponent
},
{
name: RouteName.Any,
path: '*',
redirect: {
name: RouteName.Hello,
},
},
] as const;
export const options: RouterOptions<RouteNames> = {
routes,
} as const;
</script>
<RouterView {options} />
The RouterContext
component injects the router instance into the component tree.
It can be used to share a router instance between RouterView
components without the need to pass it down as a prop.
<script lang="ts">
import { RouterContext } from '@dvcol/svelte-simple-router/components';
import { RouterView } from '@dvcol/svelte-simple-router/components';
</script>
<RouterContext>
<!-- children -->
</RouterContext>
The RouterDebugger
and RouteDugger
component will display the current route and router state.
It can be used to debug the router configuration and the current route.
It requires to be placed inside a RouterView
or RouterContext
component.
<script lang="ts">
import { RouterDebugger, RouteDebugger } from '@dvcol/svelte-simple-router/components/debug';
import { RouterView } from '@dvcol/svelte-simple-router/components';
</script>
<RouterView>
<RouterDebugger />
<RouteDebugger />
</RouterView>
The RouterView
component can be nested under another RouterView
or RouterContext
component.
Named RouterView
components can be used to render different components on the same route.
Each RouterView
grabs the router context from the nearest RouterContext
or RouterView
component.
Note: Sibling RouterView
or RouterContext
components will instantiate a new router instance.
<script lang="ts">
import { RouterView } from '@dvcol/svelte-simple-router/components';
import { RouterContext } from '@dvcol/svelte-simple-router/components';
import type { Route, RouterOptions } from '@dvcol/svelte-simple-router/models';
import ParentComponent from '~/components/hello/ParentComponent.svelte';
import ChildComponent from '~/components/goodbye/ChildComponent.svelte';
const RouteName = {
Parent: 'parent',
Child: 'child',
} as const;
type RouteNames = (typeof RouteName)[keyof typeof RouteName];
export const routes: Readonly<Route<RouteNames>[]> = [
{
name: RouteName.Parent,
path: `/${RouteName.Parent}`,
component: ParentComponent
},
{
name: RouteName.Nested,
path: `/${RouteName.Parent}/${RouteName.Child}`,
components: {
default: ParentComponent,
nested: ChildComponent,
}
},
] as const;
export const options: RouterOptions<RouteNames> = {
routes,
} as const;
</script>
<RouterContext {options}>
<RouterView>
<!-- will render ParentComponent -->
</RouterView>
<RouterView name="nested">
<!-- will only render ChildComponent on /parent/child, and nothing on /parent -->
</RouterView>
</RouterContext>
The RouterView
component can take a transition
prop to animate the route transition.
It wraps the route component in a div with optionals in
and out
transitions.
A default fade/scale transition is provided, but you can pass your own transitions.
Note: By default the first enter transition is ignored, you can change this behavior by setting the skipFirst
option to false
.
<script lang="ts">
import { RouterView } from '@dvcol/svelte-simple-router/components';
import { transition } from '@dvcol/svelte-simple-router/utils';
...
</script>
<RouterView {transition} {options} />
If you want to use the view-transition API instead, you can pass a viewTransitionName
key in the transition
prop of the RouterView
component.
If viewTransitionName
is a string, it will be used as the viewTransitionName
, otherwise a unique id will be generated sr-container-<router-id>-<view-id>
.
Then, you can use one or a combination of lifecycle hooks like onChange
or onStart
to trigger transitions.
<script lang="ts">
import { onChange, onError, onLoaded } from '@dvcol/svelte-simple-router/router';
let resolve: () => void;
const starTransition = () => {
const { promise: transition, resolve: end } = Promise.withResolvers<void>();
resolve = end;
const { promise: wait, resolve: start } = Promise.withResolvers<void>();
document.startViewTransition(async () => {
start();
await transition;
});
return wait;
};
onChange(async () => {
if (resolve) resolve();
return starTransition();
});
onError(() => resolve?.());
onLoaded(() => resolve?.());
</script>
The link action
intercepts click events on dom elements and triggers a router navigation instead.
The link action will prevent the default behavior and use the router only if the following conditions are met:
Additionally:
Note: The action requires the router context to be present in the component tree.
<script lang="ts">
import { link } from '@dvcol/svelte-simple-router/router';
</script>
<a href="/path/:param?query=value" use:link>simple link</a>
<a href='goodbye' name use:link>named link</a>
<a href='/path/:param' data-query='{"query":"value"}' use:link>link with query</a>
<a href='/path/:param' use:link="{ params: { param: 'value' } }">link with params</a>
<div href='/path/:param' use:link="{ params: { param: 'value' } }">div link</div>
<button href='/path/:param' use:link="{ params: { param: 'value' } }">button link</button>
The active action
adds an active state (class, style or attribute) to an element when the route matches.
Additionally:
Note: The action requires the router context to be present in the component tree.
<script lang="ts">
import { active } from '@dvcol/svelte-simple-router/router';
</script>
<a href="/path" use:active>simple link</a>
<a href="/path" data-name="route-name" use:active>named link</a>
<button :use:active="{ path: '/path' }">button link</button>
<div :use:active="{ name: 'route-name' }">div link</div>
hasRouter
& useRouter
- Returns the router instanceMust be used within a RouterView
or RouterContext
.
<script lang="ts">
import { useRouter } from '@dvcol/svelte-simple-router/router';
const router = useRouter()
</script>
hasView
& useView
- Returns the view instanceMust be used within a RouterView
.
<script lang="ts">
import { useView } from '@dvcol/svelte-simple-router/router';
const view = useView()
</script>
useRoute
- Returns the current route
, location
and the routing
stateMust be used within a RouterView
or RouterContext
.
<script lang="ts">
import { useRoute } from '@dvcol/svelte-simple-router/router';
const { route, location, routing } = useRoute()
const reactiveRoute = $derived(route)
const reactiveLocation = $derived(location)
const reactiveRoutingState = $derived(routing)
</script>
useNavigate
- Returns utility function to start navigation logic.Must be used within a RouterView
or RouterContext
.
<script lang="ts">
import { useNavigate } from '@dvcol/svelte-simple-router/router';
const { resolve, push, replace, back, forward, go } = useNavigate()
</script>
beforeEach
- Returns a onMount hook that register (and auto-clean) a listener that triggers before each navigation eventMust be used within a RouterView
or RouterContext
.
<script lang="ts">
import { beforeEach } from '@dvcol/svelte-simple-router/router';
beforeEach((event) => {
console.info('before navigation', event);
})
</script>
onStart
- Returns a onMount hook that register (and auto-clean) a listener that triggers at the start of each navigation eventMust be used within a RouterView
or RouterContext
.
<script lang="ts">
import { onStart } from '@dvcol/svelte-simple-router/router';
onStart((event) => {
console.info('start of navigation', event);
})
</script>
onEnd
- Returns a onMount hook that register (and auto-clean) a listener that triggers at the end of each navigation eventMust be used within a RouterView
or RouterContext
.
<script lang="ts">
import { onEnd } from '@dvcol/svelte-simple-router/router';
onEnd((event) => {
console.info('end of navigation', event);
})
</script>
onChange
- Returns a onMount hook that register (and auto-clean) a listener that triggers at the start of a view change.Must be used within a RouterView
.
<script lang="ts">
import { onChange } from '@dvcol/svelte-simple-router/router';
onChange((event) => {
console.info('start of view change', event);
})
</script>
onLoading
- Returns a onMount hook that register (and auto-clean) a listener that triggers when a view start loading an async component.Must be used within a RouterView
.
<script lang="ts">
import { onLoading } from '@dvcol/svelte-simple-router/router';
onLoading((event) => {
console.info('loading view', event);
})
</script>
onLoaded
- Returns a onMount hook that register (and auto-clean) a listener that triggers when a view finish loading a component.Must be used within a RouterView
.
<script lang="ts">
import { onLoaded } from '@dvcol/svelte-simple-router/router';
onLoaded((event) => {
console.info('view loaded', event);
})
</script>
onError
- Returns a onMount hook that register (and auto-clean) a listener that triggers when an error occurs during navigation or view change.Must be used within a RouterView
.
<script lang="ts">
import { onError, NavigationEvent, ViewChangeEvent } from '@dvcol/svelte-simple-router/router';
onError((err, event) => {
if (event instanceof NavigationEvent) {
console.error('Navigation Error', { err, event });
} else if (event instanceof ViewChangeEvent) {
console.error('View change error', { err, event });
} else {
console.error('Unknown Error', { err, event });
}
});
</script>
onViewError
- Returns a onMount hook that register (and auto-clean) a listener that triggers when an error occurs during view change.Must be used within a RouterView
.
<script lang="ts">
import { onViewError } from '@dvcol/svelte-simple-router/router';
onViewError((err, event) => {
console.error('View change error', { err, event });
});
</script>
onRouterError
- Returns a onMount hook that register (and auto-clean) a listener that triggers when an error occurs during navigation.Must be used within a RouterView
or RouterContext
.
<script lang="ts">
import { onRouterError } from '@dvcol/svelte-simple-router/router';
onRouterError((err, event) => {
console.error('Navigation error', { err, event });
});
</script>
For more complexe usage, you can grab the router instance from the context and call the push
or replace
methods.
See the router model for more information.
<script lang="ts">
import { RouterContext } from '@dvcol/svelte-simple-router/components';
import { RouterView } from '@dvcol/svelte-simple-router/components';
import { useRouter } from '@dvcol/svelte-simple-router/router';
const router = useRouter();
const onPush = () => {
router.push({ path: "/route-path" });
};
const onReplace = () => {
router.replace({ name: "route-name" });
};
</script>
If you need to access the router instance outside of a component, you can instantiate a router instance and pass it to the RouterContext
or RouterView
component.
import { Router } from '@dvcol/svelte-simple-router/router';
export const router = new Router();
<script lang="ts">
import { RouterView } from '@dvcol/svelte-simple-router/components';
import { router } from './router';
</script>
<RouterView {router} />
Router navigation support several options:
name
or path
to navigate to a named or path route.params
to pass route parameters.query
to pass query parameters.state
to pass an history state object.stripQuery
to remove current query parameters from the url.stripHash
to remove the hash from the url (only in path mode).stripTrailingHash
to remove the trailing hash from the url (only in hash mode).You can also override the router's navigation options:
base
to set the base path for the router.hash
to enable hash routing.strict
to enable strictly match routes (i.e. /path will not match /path/child).force
to force navigation even if the route is the same.caseSensitive
to enable case sensitive route names.failOnNotFound
to throw an error when a route is not found.metaAsState
to pass the route meta as state when navigating.nameAsTitle
to set the document title to the route name.followGuardRedirects
to follow guard redirects.You can dynamically add or remove routes from the router instance.
Note that although the inner route map are reactive, adding or removing routes will not trigger a re-synchronization of the router state.
To force a re-synchronization, you can call the sync
method on the router instance.
You can also use the RouteView
component to declare routes dynamically.
When the component is added to the component tree, the routes will be added to the router instance.
When the component is removed from the component tree, the routes will be removed from the router instance.
RouteView can be passed a route
object and will add it to the closest router instance.
Additionally, if the component/components/redirect are missing, it will try to infer them from the component's children and snippets.
When inside a named RouterView
, the children will be added to the components object under the name
key (provided or discovered through context).
RouteView supports the same error and loading snippets as the RouterView component.
In addition, named children can be passed as snippets to the component and will be injected into the components
object.
If a snippet with the same name
as the RouterView
is found, the children will be injected into the components
object under the default
key instead.
Note: Inputs are not reactive, so you will need to un-mout and remount the component to trigger a route update. It is recommended to use the router instance directly if you need to frequently update routes.
<script lang="ts">
import { RouterView, RouterContext, RouteView} from '@dvcol/svelte-simple-router/components';
import { toLazyComponent } from '@dvcol/svelte-simple-router/utils';
import type { PartialRoute } from '@dvcol/svelte-simple-router/models';
import ParentComponent from '~/components/hello/ParentComponent.svelte';
import ChildComponent from '~/components/goodbye/ChildComponent.svelte';
const LazyComponent = toLazyComponent(() => import('./LazyComponent.svelte'));
const parent: PartialRoute = {
name: 'parent',
path: '/parent',
};
const child: PartialRoute = {
name: 'child',
path: '/parent/child',
};
</script>
<RouterContext {options}>
<RouterView>
<RouteView route={parent}>
<!-- Will render the children in this 'default' RouterView -->
<ParentComponent />
<!-- Will render this snippet in the 'nested' RouterView -->
{#snippet nested()}
<ChildComponent />
{/snippet}
</RouteView>
</RouterView>
<RouterView name="nested">
<RouteView route={parent}>
<!-- Will render the children in this 'nested' RouterView, and nothing in the default when on '/parent/child' -->
<ChildComponent />
</RouteView>
<!-- Inline example -->
<RouteView route={{ path: '/other' }} children={ParentComponent} nested={LazyComponent} />
</RouterView>
</RouterContext>
The route
and router
supports several navigation guards and listeners:
Guards trigger after url change and before the route component is rendered.
If a guard returns false
, and object of instance Error
or throws
, the navigation will be aborted and the error will be thrown.
If a guard returns an object with a path
or name
property, the navigation will be redirected to the provided route, if any is found and followGuardRedirects
is enabled.
The router
(dynamically or through options) and RouterView
also support several event listeners:
onStart
- executed when the navigation is triggered but before the route is resolved (fires on start and redirects).onEnd
- executed when the navigation is triggered and the route is resolved (fires on successful and failed navigation, but not on cancelled/redirected).onError
- executed when the navigation is triggered but an error occurs.Note: The onError
listeners passed to a RouterView
will listen to both the router and view events. If you want to listen to only the router events, you can pass the listeners to the router
options or instance directly.
The RouterView
supports several view change listeners that triggers once the navigation concludes and the view starts changing.
onChange
- executed when a view starts to change.onLoading
- executed when a view starts loading an async component.onLoaded
- executed when a view finish loading a component.onError
- executed when an error occurs during view change.Note: The onError
listeners passed to a RouterView
will listen to both the router and view events. If you want to listen to only the view events, you can pass the listeners to the view
instance directly.
The Route
object supports lazy loading of the route component(s).
When the component
property is a lazy import, the router will resolve the matched component before rendering it.
While the component is being resolved, the loading
component will be rendered if any, the loading
snippet if any, or nothing.
Similarly, if an error occurs during the component resolution, the error
component will be rendered if any, the error
snippet if any, or nothing.
The router will try to infer if a component is a lazy import by checking it's name, but for more complex cases, you can use the toLazyComponent
wrapper.
Nested lazy components require the wrapper to be used or the function to be manually named component
.
<script lang="ts">
import { RouterView } from '@dvcol/svelte-simple-router/components';
import { toLazyComponent } from '@dvcol/svelte-simple-router/utils';
import type { Route, RouterOptions } from '@dvcol/svelte-simple-router/models';
const routes: Readonly<Route[]> = [
{
name: 'lazy',
path: '/lazy',
component: () => import('./LazyComponent.svelte'),
loading: () => import('./LoadingComponent.svelte'),
error: () => import('./ErrorComponent.svelte'),
},
{
name: 'lazy-snippet',
path: '/lazy-snippet',
component: () => import('./LazyComponent.svelte')
},
{
name: 'lazy-nested',
path: '/lazy-nested',
components: {
default: toLazyComponent(() => import('./LazyComponent.svelte')),
nested: toLazyComponent(() => import('./NestedComponent.svelte')),
}
}
] as const;
</script>
<RouterView {routes}>
{#snippet loading()}
<h1>Default Loading...</h1>
{/snippet}
{#snippet error(err)}
<h1>Default Error: {err}</h1>
{/snippet}
</RouterView>
Note that loading indicator only trigger once the route has been resolved and not on initial navigation.
If you want to show a loading indicator on initial navigation, you can use the routing
snippet instead.
This means navigation will be a three-step process:
routing
snippet will be rendered.loading
component will be rendered./path/:param
/path/:param?
Parameters can also have a type constraint by adding :{string}
or :{number}
before the parameter name.
The router will only match the route if the parameter matches the type constraint.
/path/:{string}:param?
Will match /path/param
but not /path/1
.
/path/:{number}:param
Will match /path/1
but not /path/param
.
*
character will match any path segment./path/*
Will match /path/param
and /path/param/param
.
/path/*/:{number}:param/*
Will match /path/any/12
and /path/other/12/path/end
.
nameAsTitle
is enabled).In addition to default navigation options (see programmatic navigation), the router instance supports several options:
history
to set the history instance the router will use (defaults to global.history).location
to set the location instance the router will use (defaults to global.location).listen
to listen to popstate
or navigation
events and trigger synchronization.priority
to set the priority when multiple routes match the current path.caseSensitive
to enable case sensitive route names.Give a ⭐️ if this project helped you!
This project is MIT licensed.
This README was generated with ❤️ by readme-md-generator