Svelte-Breadcrumbs makes it easy to generate meaningful breadcrumbs by leveraging Svelte's directory structure and Module Context Exports.
For example, when navigating to a route such as /todos/[id]/edit with the URL Pathname being /todos/1/edit you can immediately generate the breadcrumb todos > 1 > edit.
Replacing the id 1 with meaningful data such as the todo's name proves to be slightly more difficulty. The crux of this issue lies in the fact that we are currently on the /todos/[id]/editpage, so any breadcrumb ui elements generated in/todos/[id]/+page.svelteor breadcrumb data returned in/todos/[id]/+page.server.ts will not be immediately available.
With Svelte-Breadcrumbs, the route id is first split (e.g. /todos/[id]/edit -> ['todos', '[id]', 'edit']) giving us the directory for each route. We then import the +page.svelte file from the corresponding directory and access a constant string pageTitle or getter function getPageTitle that was exported. The getter function is called with the current page's data passed in as a parameter.
The title is then generated with the following priority, each one acting as a fallback for it's greater:
- Page data
crumbsproperty which overrides the entirecrumbsarray pageTitle: stringvariable in the svelte page's module contextgetPageTitle(data: any) -> stringfunction in the svelte page's module context- The value in the original URL route path
Breadcrumb title definition can now exist within the view itself!
The biggest drawback of this solution is that getter functions have no way of knowing the data that will be provided to them at compile time which can make development a bit tricky. It can be hard to know if a page has the data the getter will need, but this is why the fallbacks exist.
Another drawback I see is that the glob import in Breadcrumbs.svelte may be inefficient, specifically may be storing extra data in memory. This hasn't proven to be an issue for my project, but I'm not completely sure how it would fare in larger projects with more Svelte files...
$ npm i svelte-breadcrumbsIn +layout.svelte:
<!--
Add the `Breadcrumbs` component and feed in the current page url
and the route id.
-->
<Breadcrumbs url={$page.url} routeId={$page.route.id}>
{#snippet children({ crumbs })}
<div>
<span><a href="/">Home</a></span>
<!--
Loop over the generated crumbs array
-->
{#each crumbs as c}
<span>/</span>
<span>
<a href={c.url}>
{c.title}
</a>
</span>
{/each}
</div>
{/snippet}
</Breadcrumbs>In the example above, Breadcrumbs.svelte will handle grabbing all of the modules itself under the assumption that all Svelte files exist in /src/routes/. If your directory structure is different, you can implement this yourself. If you pass a value in the routeModules prop the Breadcrumbs component will not try to populate it. You will also need to update the path prefix for your Svelte directory.
<script lang="ts">
let routeModules = $state({} as Record<string, ModuleData>);
onMount(async () => {
// Note: that the path prefix here is now /src/svelte/
// Note: eager is required
routeModules = import.meta.glob("/src/svelte/**/*.svelte", {eager: true});
})
</script>
<!-- Note: routeModules and globImportPrefix passed through -->
<Breadcrumbs
url={$page.url}
routeId={$page.route.id}
{routeModules}
globImportPrefix={'/src/svelte/'}
let:crumbs
>
<!-- ...-->
</Breadcrumbs>The Breadcrumbs component will have access to your Svelte components based on the route id and will be looking for the following exported variables in the Module Context:
pageTitle: stringgetPageTitle: (data: any) -> string
getPageTitle will receive the value of $page.data passed through in the Breadcrumbs prop. (see the Breadcrumbs usage above).
Here is an example:
<script module lang="ts">
// Getter function
export function getPageTitle(data: any) {
// When this is undefined it will fall back to the value in the route (in this case the todo id for the route /todos/1/edit)
return data.todo?.name;
}
// Or a constant
export const pageTitle = 'Random Todo';
</script>export type Crumb = {
title?: string; // The default title being the sanitized page inferred from the URL (e.g. Edit)
url?: string; // The URL of this page (e.g. /todos/1/edit)
metadata?: any; // Any metadata you want passed through to the Breadcrumbs component
};
// The data we will be grabbing from each +page.svelte file
export type ModuleData = {
pageTitle?: string;
getPageTitle?: (data: any) => string;
};This component will provide an array of Crumbs to a single slot. The final Crumb will never have a URL as it is the current page.
Optional
The exported data for each module. If not provided it will be populated on mount with an eager glob import of "/src/routes/**/*.svelte" which is SvelteKit specific.
Completely disable this feature by passing in an empty value as shown below.
<Breadcrumbs routeModules={{}}>
<!-- ...-->
</Breadcrumbs>Optional
Default Value:
'/src/routes/'
The path to the directory where your Svelte files live. In SvelteKit, if we are on a route /todo/[id]/ and we have imported the svelte files like so:
import.meta.glob("/src/routes/**/*.svelte")
it will produce an object with the following:
{
'/src/routes/todo/[id]/+page.svelte': ...Promise obj...
}Thus in order to match that file we need to specify the prefix /src/routes/. Breadcrumbs.svelte will essentially do the following to generate a path to the +page.svelte file:
relPathToRoutes + routeId + "/+page.svelte";Optional
Route id for the current page. In Sveltekit this is $page.route.id.
Required
URL for the current page. Used to generate the url that each breadcrumb should link to when clicked on. In SvelteKit this is $page.url.
Optional
Page Data to pass through to the getPageTitle function living in a route's page.svelte file
Optional
A list of Crumbs that will override/bypass any breadcrumb generation via routes. In SvelteKit if you pass $page.data.crumbs or something similar you will be able to override any bread crumbs via page loads.
Optional
When set to true, it will completely skip rendering breadcrumbs if there is no page for the route.
Optional
Default Value:
(title) => title.replace(/([A-Z])/g, " $1").replace(/^./, (str) => str.toUpperCase());
Each title of the generated Crumb items will pass through this function. By default it will add spaces and capitalize (e.g. myTodos -> My Todos).