A simple and efficient declarative routing library for Svelte 5 built with runes.
npm i svelte-tiny-router
Here's a basic example demonstrating simple routes:
<!-- App.svelte -->
<script>
  import { Router, Route } from 'svelte-tiny-router';
  import Home from './Home.svelte';
  import About from './About.svelte';
  import User from './User.svelte';
</script>
<Router>
  <!-- Exact match for home page -->
  <Route path="/" component={Home} />
  <!-- Static route -->
  <Route path="/about" component={About} />
  <!-- Dynamic route: "/user/123" will match and pass { id: "123" } as a prop -->
  <Route path="/user/:id" component={User} />
  <!-- Fallback route: no "path" prop means it always matches (e.g. for a 404 page) -->
  <Route>
    <p>Page not found.</p>
  </Route>
</Router><!-- App.svelte -->
<script>
  import { Router, Route } from 'svelte-tiny-router';
  import Home from './Home.svelte';
  import About from './About.svelte';
  import User from './User.svelte';
</script>
<Router>
  <!-- Exact match for home page -->
  <Route path="/" component={Home} />
  <!-- Static route -->
  <Route path="/about" component={About} />
  <!-- Dynamic route: "/user/123" will match and pass { id: "123" } as a prop -->
  <Route path="/user/:id" component={User} />
  <!-- Fallback route: no "path" prop means it always matches (e.g. for a 404 page) -->
  <Route>
    <p>Page not found.</p>
  </Route>
</Router><!-- SomeComponent.svelte -->
<script>
  import { useTinyRouter } from 'svelte-tiny-router';
  const router = useTinyRouter();
  function goToAbout() {
    router.navigate('/about'); // Use router.navigate
  }
  function goToUser(id) {
    router.navigate(`/user/${id}`);
  }
  function replaceWithHome() {
    router.navigate('/', { replace: true }); // Replace current history entry
  }
  function navigateWithQuery() {
    router.navigate('/search?q=svelte&category=router'); // Navigate with query string
  }
</script>
<button on:click={goToAbout}>Go to About Page</button>
<button on:click={() => goToUser(123)}>Go to User 123</button>
<button on:click={replaceWithHome}>Replace with Home</button>
<button on:click={navigateWithQuery}>Search</button><!-- SomeComponent.svelte -->
<script>
  import { useTinyRouter } from 'svelte-tiny-router';
  const router = useTinyRouter();
  // Access the entire query object
  console.log("Current query:", router.query);
  // Check if the "foo" query parameter exists (i.e /myroute?foo=bar) and log it
  if (router.hasQueryParam('foo')) {
    console.log("Value of foo:", router.getQueryParam('foo'));
    router.removeQueryParams(["foo"]);
  }
  // Get a specific query parameter
  const searchTerm = router.getQueryParam('q');
  console.log("Search term:", searchTerm);
</script>You can define navigation guards using the beforeEach prop on the <Router> component. These guards are functions that are executed before each navigation. They can be used to cancel navigation, redirect to a different route, or perform asynchronous tasks like authentication checks.
<!-- App.svelte (with navigation guards) -->
<script>
  import { Router, Route } from 'svelte-tiny-router';
  import Home from './Home.svelte';
  import AdminDashboard from './AdminDashboard.svelte';
  import Login from './Login.svelte';
  // Example authentication check function
  function isAuthenticated() {
    // Replace with your actual auth logic (e.g., check token in localStorage)
    return localStorage.getItem('authToken') !== null;
  }
  // Define navigation guards
  const authGuard = async ({ to, from, next }) => {
    console.log('[authGuard] Navigating from:', from?.path, 'to:', to.path, 'Query:', to.query);
    if (to.path.startsWith('/admin') && !isAuthenticated()) {
      console.log('Authentication required for admin route, redirecting to login.');
      // Redirect to login page, replacing the current history entry
      next({ path: '/login', replace: true });
    } else {
      // Continue navigation
      next();
    }
  };
  const loggingGuard = ({ to, from, next }) => {
    console.log('[LOG] Navigation attempt:', from?.path || 'N/A', '->', to.path, 'Query:', to.query);
    next(); // Always call next() to proceed
  };
  const myGuards = [loggingGuard, authGuard]; // Guards are executed in order
</script>
<Router beforeEach={myGuards}>
  <Route path="/" component={Home} />
  <Route path="/admin" component={AdminDashboard} />
  <Route path="/login" component={Login} />
  <Route>
    <p>Page not found.</p>
  </Route>
</Router>A navigation guard function receives an object with the following properties:
to: An object representing the target route ({ path: string, params: Record<string, string>, query: Record<string, string> }).from: An object representing the current route, ornullif this is the initial navigation ({ path: string, params: Record<string, string>, query: Record<string, string> } | null).next: A function that must be called to resolve the hook.next(): Proceed to the next hook in the pipeline, or to the navigation if no more hooks are left.next(false): Cancel the current navigation.next('/path')ornext({ path: '/path', replace: true }): Redirect to a different location. The current navigation is cancelled, and a new one is initiated.
The library supports nested routing, particularly useful with wildcard routes (/*). When a wildcard route matches, it automatically sets up a NestedRouterProvider context for its children <Route> components. These children routes then match paths relative to the parent wildcard's matched segment.
For example, with a structure like:
<Router>
  <Route path="/app/*">
    <Route path="/" component={AppHome} /> {/* Matches /app */}
    <Route path="/settings" component={AppSettings} /> {/* Matches /app/settings */}
  </Route>
</Router>Navigating to /app/settings will first match the /app/* route. The NestedRouterProvider within /app/* then makes /settings match relative to /app.
Alternatively, you can render a separate component that contains its own <Router> instance for nested routes. This component will receive the matched parameters from the parent route.
<!-- App.svelte -->
<script>
  import { Router, Route } from 'svelte-tiny-router';
  import Home from './Home.svelte';
  import About from './About.svelte';
  import User from './User.svelte';
  import DashboardRouter from './DashboardRouter.svelte'; // Component containing nested routes
</script>
<Router>
  <Route path="/" component={Home} />
  <Route path="/about" component={About} />
  <Route path="/user/:id" component={User} />
  <!-- Wildcard route rendering a component that contains a nested router -->
  <Route path="/dashboard/*" component={DashboardRouter} />
  <Route>
    <p>Page not found.</p>
  </Route>
</Router><!-- DashboardRouter.svelte -->
<script>
  import { Router, Route } from 'svelte-tiny-router';
  import DashboardHome from './DashboardHome.svelte';
  import Profile from './Profile.svelte';
  import Settings from './Settings.svelte';
  // This component receives params from the parent route if any were captured
  // let { paramFromParent } = $props(); // Example if /dashboard/:param/* was used
</script>
<!-- This Router instance handles routes relative to the parent's matched path (/dashboard) -->
<Router>
  <Route path="/" component={DashboardHome} /> {/* Matches /dashboard */}
  <Route path="/profile" component={Profile} /> {/* Matches /dashboard/profile */}
  <Route path="/settings" component={Settings} /> {/* Matches /dashboard/settings */}
  <!-- Nested fallback for /dashboard/* -->
  <Route>
    <p>Dashboard page not found.</p>
  </Route>
</Router>This library now includes comprehensive TypeScript definitions, providing improved type checking and autocompletion for users of TypeScript or JavaScript with JSDoc. Key types include RouterContext, RouteInfo, NavigationGuard, and NextFunction.