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 🙏

© 2026 – Pkg Stats / Ryan Hefner

svelte-crumbs

v1.4.0

Published

Automatic, SSR-ready breadcrumbs for SvelteKit — zero config, async resolvers, remote function support

Readme

svelte-crumbs

Automatic, SSR-ready breadcrumbs for SvelteKit via route-level metadata exports. Zero config, fully reactive, server-rendered with top-level await.

Svelte 5 + SvelteKit 2 only. Data layer only — bring your own rendering.

Documentation & Live Demo

Quick Start

1. Install

npm install svelte-crumbs

2. Enable experimental async

This library relies on Svelte's experimental async compiler option for top-level await in components. This is required.

// svelte.config.js
const config = {
    kit: {
        experimental: {
          remoteFunctions: true
        }
    },
    compilerOptions: {
        experimental: {
          async: true
        }
    }
};

To also use remote functions in your breadcrumb resolvers, add kit.experimental.remoteFunctions: true as well.

3. Export breadcrumbs from your routes

<!-- src/routes/products/+page.svelte -->
<script lang="ts" module>
  import type { BreadcrumbMeta } from 'svelte-crumbs';

  export const breadcrumb: BreadcrumbMeta = async () => ({
    label: 'Products'
  });
</script>

4. Render in your layout

<!-- src/routes/+layout.svelte -->
<script lang="ts">
  import { createBreadcrumbs } from 'svelte-crumbs';

  const getBreadcrumbs = createBreadcrumbs();
  const crumbs = $derived(await getBreadcrumbs());
</script>

<nav>
  {#each crumbs as crumb, i}
    {#if i > 0} / {/if}
    <a href={crumb.url}>{crumb.label}</a>
  {/each}
</nav>

No {#await} blocks needed. Breadcrumbs resolve during SSR and update reactively on client navigation.

Examples

Static breadcrumb

<script lang="ts" module>
  import type { BreadcrumbMeta } from 'svelte-crumbs';

  export const breadcrumb: BreadcrumbMeta = async () => ({
    label: 'Settings'
  });
</script>

From load data

The breadcrumb resolver receives the full page object, including page.data. Use +layout.server.ts (not +page.server.ts) so the data is available to child routes' breadcrumbs too:

// src/routes/products/[id]/+layout.server.ts
export async function load({ params }) {
  const product = await db.products.find(params.id);
  return { product };
}
<!-- src/routes/products/[id]/+page.svelte -->
<script lang="ts" module>
  import type { BreadcrumbMeta } from 'svelte-crumbs';

  export const breadcrumb: BreadcrumbMeta = async (page) => ({
    label: page.data.product.name
  });
</script>

<script lang="ts">
  let { data } = $props();
</script>

<h1>{data.product.name}</h1>

Why +layout.server.ts? Breadcrumb resolvers run for every segment of the URL. When visiting /products/42/edit, the resolver for /products/[id] fires too. If you put the load in +page.server.ts, page.data on child routes won't have product — layout data cascades down, page data doesn't.

From a remote function

Breadcrumb resolvers can call remote functions that run on the server:

// src/lib/products.remote.ts
import { query } from '$app/server';

export const getProductName = query('unchecked', async (id: string) => {
  const product = await db.products.find(id);
  return product.name;
});
<!-- src/routes/products/[id]/+page.svelte -->
<script lang="ts" module>
  import type { BreadcrumbMeta } from 'svelte-crumbs';
  import { getProductName } from '$lib/products.remote';

  export const breadcrumb: BreadcrumbMeta = async (page) => ({
    label: await getProductName(page.params.id ?? '')
  });
</script>

Multi-route breadcrumb

For dynamic routes that map to known paths:

<script lang="ts" module>
  import type { BreadcrumbMeta } from 'svelte-crumbs';

  export const breadcrumb: BreadcrumbMeta = {
    routes: {
      '/docs/getting-started': async () => ({ label: 'Getting Started' }),
      '/docs/api-reference': async () => ({ label: 'API Reference' })
    }
  };
</script>

With icon

<script lang="ts" module>
  import type { BreadcrumbMeta } from 'svelte-crumbs';
  import HomeIcon from './HomeIcon.svelte';

  export const breadcrumb: BreadcrumbMeta = async () => ({
    label: 'Home',
    icon: HomeIcon
  });
</script>

Custom rendering

Since svelte-crumbs only provides data, you render however you want:

<script lang="ts">
  import { createBreadcrumbs } from 'svelte-crumbs';

  const getBreadcrumbs = createBreadcrumbs();
  const crumbs = $derived(await getBreadcrumbs());
</script>

<ol class="breadcrumb-list">
  {#each crumbs as crumb}
    <li>
      {#if crumb.icon}
        {@const Icon = crumb.icon}
        <Icon />
      {/if}
      <a href={crumb.url}>{crumb.label}</a>
    </li>
  {/each}
</ol>

API Reference

createBreadcrumbs()

Creates a reactive breadcrumb resolver. Returns a getter function () => Promise<Breadcrumb[]>.

Call createBreadcrumbs() once to set up the reactive state, then use the returned getter inside $derived(await ...) to get breadcrumbs that update on navigation and resolve during SSR.

Types

// What you export from +page.svelte
type BreadcrumbMeta = BreadcrumbResolver | { routes: Record<string, BreadcrumbResolver> };

// Resolver function
type BreadcrumbResolver = (page: Page) => Promise<BreadcrumbData | undefined>;

// Data for one breadcrumb
type BreadcrumbData = { label: string; icon?: Component<any> };

// Resolved breadcrumb with URL
type Breadcrumb = BreadcrumbData & { url: string };

Utility exports

  • buildBreadcrumbMap() — manually build the route-to-resolver map
  • filePathToRoute(filePath) — convert glob file path to route
  • matchDynamicRoute(map, route) — match a concrete path against dynamic patterns
  • getResolversForRoute(map, route) — collect resolvers for a given route path

How It Works

  1. import.meta.glob eagerly imports all +page.svelte files at build time
  2. Each file's breadcrumb export is collected into a Map<route, resolver>
  3. Route groups like (app) are stripped from paths
  4. On navigation, the root (/) resolver is checked first, then each segment is walked from left to right with dynamic [param] matching
  5. Matching resolvers run in parallel, producing the final breadcrumb array
  6. On SSR, top-level await ensures breadcrumbs are rendered in the initial HTML
  7. On the client, $derived re-evaluates when the route changes

Requirements

  • Svelte 5 with compilerOptions.experimental.async: true — uses $derived(await ...) for reactive, SSR-safe breadcrumbs
  • SvelteKit 2 — relies on $app/state and import.meta.glob
  • Route groups ((group)) are stripped from paths

License

MIT