npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@snapstrat/switchboard

v1.0.0

Published

<div align="center"> <h1><img alt="SwitchBoard Logo" width="480" src="switchboard-logo.png"/></h1> <p><i>A simple, extensible, component-based Svelte 5 SPA router.</i></p> <img alt="License" src="https://img.shields.io/github/license/snapstrat

Readme

Features

  • A declarative, component-based method to declare routes.
  • A simple API to programmatically navigate between routes.
  • Layouts, nested routes, and route parameters.
  • Svelte 5 compatibility, with TypeScript support.

Why Switchboard?

Switchboard is designed to be a simple, extensible router for Svelte 5 applications. Using composition and a declarative API, developers are able to quickly create SPA routes without the complexity of larger routing libraries, or dealing with SSR.

Switchboard is ideal for applications which:

  • don't require server-side rendering. See Use with SvelteKit for more info.
  • want a simple, component-based routing solution.
  • want to stray away from strictly file-based routing systems, such as SvelteKit's routing.
  • want more flexibility in defining routes and layouts.
  • want to be able to define routes in a way that fits their application's architecture.

Getting Started

To install Switchboard, run:

# npm
npm install @snapstrat/switchboard
# yarn
yarn add @snapstrat/switchboard
# pnpm
pnpm install @snapstrat/switchboard
# bun
bun install @snapstrat/switchboard

Basic Usage

Switchboard exposes several components and functions to help you set up routing in your Svelte application.

Creating a Router

Switchboard leaves the router setup to you. You can either use some form of global state management to hold a reference to it, or create it directly in your root component.

Switchboard includes a createWebRouter() function to create a router instance that works with the browser's history API.

Global Access

main.ts

import App from './App.svelte';
import { createWebRouter } from '@snapstrat/switchboard';

// now anything can import router from here to access the router!
export const router = createWebRouter();

mount(App, {
	target: document.body,
});

Creation in Root Component

App.svelte

<script lang="ts">
	import { BrowserRouter, createWebRouter } from '$lib';
	import PersistenceLayout from './PersistenceLayout.svelte';

    // we can also create the router here!
	const router = createWebRouter()
</script>

<!-- all BrowserRouter needs is a Router instance, constructed from anywhere. -->
<!-- any component nested in BrowserRouter gets access to getRouter() using Svelte contexts. -->
<BrowserRouter {router}>
    <!-- Your routes and layouts go here -->
</BrowserRouter>

Defining Routes

Switchboard is very unopinionated about how you define your routes. The main building blocks are the Route, Layout, PageInfo and Link components.

<script lang="ts">
    import { BrowserRouter, Route, Link } from '@snapstrat/switchboard'
    
    // maybe make a router here? or import one.
</script>

<BrowserRouter router={your_router_instance}>
    <Route path="/">
        <h1>Home Page</h1>
    </Route>

    <Route path="/users/:id">
        <h1>You're looking for a user with the id: {getRouteParams().id}</h1>
    </Route>
    
    <!-- You can use Route404 to catch all unmatched routes -->
    <Route404>
        <h1>404 - Page Not Found</h1>
    </Route404>
</BrowserRouter>

Links

Links can be created using the Link component, which is a very, very thin wrapper around an <a> tag. Alternatively, you can use the Router.switchTo() method to programmatically navigate between routes, or use the {@attach href('my/path')} attachment to attach hrefs to any element.


<script lang="ts">
	import { Link } from '@snapstrat/switchboard';
</script>

<!-- both are equivalent: -->
<Link href="/some/path">Go to Some Path</Link>
<a {@attach href("/some/path")}>Go to Some Path</a>

<!-- not recommended for accessibility, but possible: -->
<button on:click={() => router.switchTo('/some/path')}>
    Go to Some Path
</button>

<!-- not recommended at all, this will reload the page and lose SPA state: -->
<a href="/some/path">Go to Some Path</a>

PageInfo

The PageInfo component allows you to declaratively set metadata for the current page, such as the document title and meta tags.

<script lang="ts">
    import { PageInfo, Route } from '@snapstrat/switchboard';
</script>

<Route path="/my-cool-page">
    <PageInfo title="My Page Title" icon="/page-icon.ico" />
</Route>

Layouts

Layouts can be used to simplify routes that share common structure. State of a layout will persist between nested route changes. For example, if you start at /user/1, modify some state in the layout, then navigate to /user/1/profile, the layout state will persist. However, if you navigate to /user/2, or /, or /any-other-route-outside-of-this-layout the layout will be re-created and state will be destroyed since the route parameter changed.

<BrowserRouter router={your_router_instance}>
    <!-- Layouts can have parameters too! -->
    <!-- Layouts have two snippets they take in: `routes` and `layout` -->
    <Layout path="/user/:id">
        <!-- layout() holds the common layout structure. -->
        {#snippet layout(route: Snippet)}
            <p>You're looking at something for {getRouteParams().id}!</h1>
            {@render route()}
        {/snippet}
        
        <!-- routes() holds all nested routes -->
        {#snippet routes()}
            <Route path="profile">
                <h2>This is the profile page for user {getRouteParams().id}!</h2>
            </Route>
            
            <Route path="settings">
                <h2>This is the settings page for user {getRouteParams().id}!</h2>
            </Route>
            
            <!-- in this context, / will match for /user/:id/ -->
            <Route path="/">
                <h2>This is the settings page for user {getRouteParams().id}!</h2>
            </Route>
            
            <!-- You can also have a Route404 for unmatched nested routes. -->
            <Route404>
                <h1>404 - User Not Found</h1>
            </Route404>
        {/snippet}
    </Layout>
    
    <!-- You can use Route404 to catch all unmatched routes. -->
    <Route404>
        <h1>404 - Page Not Found</h1>
    </Route404>
</BrowserRouter>

Use with SvelteKit

Switchboard is primarily designed for client-side routing in Svelte applications, and does NOT support server-side rendering (SSR), nor do we plan to ever support SSR.

However, you can still use Switchboard within a SvelteKit application for client-side routing needs. To do this, you'll need two main things:

  1. A single, top-level SvelteKit route that will serve as the entry point for your Switchboard-powered SPA.
  2. SSR disabled for that route.

In practice, this looks like:

- /src
  - /routes
    - /[...any]            <-- catch-all used to route all traffic your only +page.svelte, containing your Switchboard app
      - +page.svelte      <-- your Switchboard app goes here
      - +page.ts          <-- disable SSR here with `export const ssr = false;`

In a normal Svelte application, you don't need to worry about any of this, and can just place a BrowserRouter at the root of your component tree (probably in App.svelte).

Advanced Usage

Advanced usage of Switchboard comes from the natural composition of its components. You can create your own custom route components, layouts, and even extend the router itself to add functionality.

Any Route or Layout can be declared anywhere that is within a BrowserRouter, or a Layout component, and will function as expected, no matter how these are nested, constructed, composed, or imported.

Custom Route Component

You can wrap around the Route component to create your own custom route components.

For example, you could create an authenticated route component that checks if a user is logged in before rendering the route.

[!CAUTION] Note that this isn't inherently secure, as all routing is done client-side. Make sure that when dealing with sensitive data, you also have server-side checks in place.

AuthenticatedRoute.svelte

<script lang="ts">
    import { Route, getRouter } from '@snapstrat/switchboard';
    import { onMount } from 'svelte';
    import { isUserAuthed } from './auth'; // your auth logic here
    
    const { children, path } = $props();
</script>

<Route path={path}>
    {#if isUserAuthed()}
        {@render children()}
    {:else}
        <h1>Please log in to access this page.</h1>
    {/if}
</Route>

App.svelte

<script lang="ts">
    import { BrowserRouter } from '@snapstrat/switchboard';
    import AuthenticatedRoute from './AuthenticatedRoute.svelte';
</script>

<BrowserRouter router={your_router_instance}>
    <AuthenticatedRoute path="/dashboard">
        <h1>Welcome to your dashboard!</h1>
    </AuthenticatedRoute>
</BrowserRouter>

Escaping Layouts

If you need to escape a layout for a specific route, you can set a custom 'container' on any route.

<script lang="ts">
    import { BrowserRouter, Layout, LayoutData, Route } from '@snapstrat/switchboard';
    
    let otherLayout: LayoutData = $state();
</script>

<BrowserRouter router={your_router_instance}>
    <Layout path="/app">
        {#snippet layout(route: Snippet)}
            <div class="app-layout">
                <nav>...navigation...</nav>
                <main>
                    {@render route()}
                </main>
            </div>
        {/snippet}
        
        {#snippet routes()}
            <Route path="dashboard">
                <h1>Dashboard</h1>
            </Route>
            
            <Route path="settings">
                <h1>Settings</h1>
            </Route>
            
            <!-- 
            container can either be a Router instance or another Layout.
            setting to whatever your router instance is completely disables the layout. 
             -->
            <Route path="/login" container={your_router_instance}>
                <h1>Login Page</h1>
            </Route>
            
            
            <!--
            setting container to another layout makes the route use that layout instead 
            of the current one.
            
            note that this will NOT impact the path; this route still exists 
            at /app/landing-page, NOT /other/landing-page.
            -->
            <Route path="/landing-page" container={outerLayout}>
                <h1>Landing Page</h1>
            </Route>
        {/snippet}
    </Layout>
    
    <Layout path="/other" bind:ref={otherLayout}>
        <!-- another layout... -->
    </Layout>
</BrowserRouter>

The same principle can be applied to Layouts nested within other Layouts as well, with the same container prop, and same rules regarding how paths are resolved.

[!WARNING] When escaping a layout, you must have the layout you're referencing defined above the layout you're escaping from.