Effortlessly Nuxt navigation: Crafting Dynamic breadcrumbs with Storefront UI
Tags: nuxt, javascript, typescript, vue
Published: 2023-05-05 12:46:21

---
title: "Effortlessly Nuxt navigation: Crafting Dynamic breadcrumbs with Storefront UI"
published: true
description: "Explore our step-by-step tutorial on crafting dynamic breadcrumbs using the powerful SfLink component from Storefront UI. Enhance user experience and make every click count!"
tags: ['Nuxt', 'javascript', 'typescript', 'vuejs']
cover_image: https://res.cloudinary.com/mayashavin/image/upload/v1683290212/articles/Nuxt/breadcrumbs1.png
series: Efficient E-commerce with Storefront UI
canonical_url: https://mayashavin.com/articles/breadcrumbs-storefrontui-nuxt-router
---

Using breadcrumbs can be beneficial for a complex site since it provides users a clear visual understanding of the site hierarchy and a more straightforward approach for a user to retrace their steps, improving their experience and engagement while reducing the bouncing rates for our sites.

In this post, we will discover the step-by-step process of creating an intuitive Breadcrumbs component with Nuxt and Storefront UI in our comprehensive guide. 

## Table of Content

- [Table of Content](#table-of-content)
- [The importance of breadcrumbs in web navigation](#the-importance-of-breadcrumbs-in-web-navigation)
- [Prerequisites](#prerequisites)
- [Understanding the file-based routing system](#understanding-the-file-based-routing-system)
- [Creating a useBreadcrumbs composable](#creating-a-usebreadcrumbs-composable)
  - [Handling dynamic and nested routes](#handling-dynamic-and-nested-routes)
  - [Matching the route's pattern](#matching-the-routes-pattern)
- [Integrating with the UI component](#integrating-with-the-ui-component)
  - [Using the meta field for breadcrumbs](#using-the-meta-field-for-breadcrumbs)
- [Summary](#summary)

## The importance of breadcrumbs in web navigation

Breadcrumbs trail displays the current location within a web application in the context of the application's hierarchy and allows users to navigate back to previous pages or levels easily. It offers an alternative visual presentation for assisting navigation, often a horizontal sequence of text links separated by a `>` symbol to indicate the nested level of the text that comes after it.

<img src="https://res.cloudinary.com/mayashavin/image/upload/f_auto,q_auto/v1675680195/articles/Nuxt/breadcrumbs_demo" loading="lazy" alt="The levels of breadcrumbs" class="mx-auto"/>

You can view breadcrumbs like a file directory hierarchy, where the root appears first and the current page link appears last.

There are three types of breadcrumbs: location, attribute, and path based. This post will discuss implementing a path-based Breadcrumbs component for our Nuxt application using its routing mechanism and Storefront UI for the visual display.

**Note**: For a Vue application with Vue Router installed, you can easily reuse the discussed code in this post with a little bit of modification.

## Prerequisites

It would help if you had a Nuxt application set up and ready to go by using the following command:

```bash
npx nuxi init breadcrumbs-demo
```

Which `breadcrumbs-demo` is the name of our code demo application. You can change to any name per your preference.

Next, let's add Storefront UI to our project by installing its package and the TailwindCSS module for Nuxt with the following command:

```bash
yarn add -D @nuxtjs/tailwindcss @storefront-ui/vue
```

You should follow the [Storefront UI instructions](https://docs.storefrontui.io/v2/vue/getting-started.html) to set up TailwindCSS and Storefront UI together in the Nuxt product. Once done, we are ready for our tutorial!

## Understanding the file-based routing system

Nuxt uses Vue Router and a file-based system to generate the application's routes from the files in the `/pages` directory. For example, if you have the following file structure:

```
\page
|--index.vue
|--\products
|-----index.vue
|-----details.vue
```

Behind the scene, the Nuxt engine will automatically build the following routes: 

* `\` as home page with `pages\index.vue` as its view component. Its name will be `index`, the same as the file name.
* `\products` with `pages\products\index.vue` and has `products` as its name.
* `\products\details` for `pages\products\details` view, with `products-details` as its name, since `details` is nested inside `products`.

You can view the routes using the router instance's `getRoutes()` method. You can also name a route as dynamic using the `[]` syntax, such as `[sku].vue`. In this case, Nuxt will generate the dynamic path as `/:sku()`, which `:sku()` is the Regex pattern Vue Router will use to match and extract the target params to `sku` field when relevant.

Great. Now we understand how the routing system works in Nuxt. We can build our breadcrumbs mechanism, breaking down the URL into crumbs.

## Creating a useBreadcrumbs composable

To construct our breadcrumbs, we create a `useBreadcrumbs()` composable, where we perform the following actions:

1. Watch for the route's changes, specifically on `name`, `path`, and `meta` properties instead of the whole `route` object.
2. Initialize a default value for our `breadcrumbs` array as the Home route.
3. Trigger the watcher immediately so we will also have the breadcrumbs calculated on the initial page load or page refresh.
4. Pass `breadcrumbs` as the return value for the composable.
5. We only trigger watchers when the current route is not on the Home page.

The essential code for `useBreadcrumbs()` is as follows:

```typescript
export const useBreadcrumbs = () => {
    const route = useRoute()

    const HOMEPAGE = { name: 'Home', path: '/' };
    const breadcrumbs:Ref<Array<{ name: string; path: string; }>> = ref([ HOMEPAGE ])

    watch(() => ({
        path: route.path,
        name: route.name,
        meta: route.meta,
        matched: route.matched,
    }), (route) => {
        if (route.path === '/') return;
    
        //TODO - generate the breadcrumbs
    }, {
        immediate: true,
    })

    return {
        breadcrumbs
    }
}
```

Next, we will implement how to compute the breadcrumbs from the current route, including dynamic and nested ways.

### Handling dynamic and nested routes

To construct a page's breadcrumbs, it's always better to have the current route know who its parent is. However, in Vue Router and Nuxt, unfortunately, there isn't a way to do so. Instead, we can construct the breadcrumbs' paths by recursively slicing the current breadcrumb's path by the last index of the symbol `/`.

Take our `/products/about/keychain` path, for instance. We will break it down into the following paths: `"/products/about/keychain"`, `"/products/about"`, `"/products"`, and `""`. Each path is a breadcrumb we need to display. And to get the display name for these breadcrumbs, we need to do the following:

1. Get the list of the available routes from the `router` instance of `useRouter()`.
2. We will find the matching route for each breadcrumb's path.
3. Our stop condition is when the reduced path is the Home page.

Our code for `getBreadcrumbs()` looks as follows:

```typescript
function getBreadcrumbs(currPath: string): any[] {
    //1. When we reach the root, return the array with the Home route
    if (currPath === '') return [ HOMEPAGE ];

    //2. Continue building the breadcrumb for the parent's path
    const parentRoutes = getBreadcrumbs(currPath.slice(0, currPath.lastIndexOf('/')));

    //3. Get the matching route object
    //TODO
    //4. Return the merged array with the new matching route
    return [
        ...parentRoutes,
        {
            path: currPath,
            //TODO
            name: currPath,
        }
    ]
}
```

We currently return the `currPath` as the `path` and `name` for the breadcrumb. Still, we must implement how to detect the matching route based on the generated route configurations from Nuxt, including dynamic routes and dynamic nested routes. Let's do that next.

### Matching the route's pattern

When working with matching routes' paths, there are many scenarios related to dynamic routes we need to handle, including:

* Dynamic routes such as `/products/:id()`
* Dynamic route and static route under the same parent, such as `/products/:id()` (`pages/produts/[id].vue`) and `/products/about` (`pages/products/about.vue`)
* Dynamic route nested in another dynamic route, such as `/products/:id()/:field()`

The most straightforward approach is to split the route's and current paths into parts by the separator `/`. Then we iterate the elements and compare one to one to see if it is the same value or if the subpath starts with `:` as shown in the following code for `isMathPatternPath`:

```typescript
const isMathPatternPath = (pathA: string, pathB: string) => {
    const partsA = pathA.split('/');
    const partsB = pathB.split('/');

    if (partsA.length !== partsB.length) return false;

    const isMatch = partsA.every((part: string, i: number) => {
        return part === partsB[i] || part.startsWith(':');
    })
    
    return isMatch;
}
```

We then use `isMathPatternPath` in our `getBreadcrumbs()` function on the `currPath`, and receive an array of the matched route(s) as a result with the following assumptions:

* If there is a static route and a dynamic route resides on the same parent, it will be a match for both.
* The static routes will always appear before the dynamic routes in such a matched routes array (letters appear before symbols like ':') 
* The matched array contains more than one result for a static route with a dynamic sibling. In this case, we will take the exact match using the `===` comparison. Otherwise, the array should contain a single result.

And thus, our implementation for `getBreadcrumbs()` will be as follows:

```typescript
function getBreadcrumbs(currPath: string): any[] {
    //1. When we reach the root, return the array with Home route
    if (currPath === '') return [ HOMEPAGE ];

    //2. Continue building the breadcrumb for the parent's path
    const parentRoutes = getBreadcrumbs(currPath.slice(0, currPath.lastIndexOf('/')));

    //3. Get the matching route object
    const founds = routes.filter(r => isMathPatternPath(r.path, currPath));
    const matchRoute = founds.length > 1 ? founds.find(r => r.path === currPath) : founds[0];

    //4. Return the merged array with the new matching route
    return [
        ...parentRoutes,
        {
            path: currPath,
            //TODO
            name: matchRoute?.meta?.breadcrumb || matchRoute?.name || matchRoute?.path || currPath,
        }
    ]
}
```

Based on the `matchRoute`, we will use the `meta.breadcrumb` field to get the desired name for displaying, or its name, path or `currPath` as the fallback value.

And we can update our `useBreadcrumbs()` composable with the following code:

```typescript
export const useBreadcrumbs = () => {
    //...

    watch(() => ({
        path: route.path,
        name: route.name,
        meta: route.meta,
        matched: route.matched,
    }), (route) => {
        if (route.path === '/') return;

        breadcrumbs.value = getBreadcrumbs(route.path);
    }, {
        immediate: true,
    })

    //...
}
```

With that, our `useBreadcrumbs()` is ready to use. Let's display it!

## Integrating with the UI component

We will copy the code of [Storefront UI Breadcrumbs with a Home icon](https://docs.storefrontui.io/v2/vue/blocks/Breadcrumbs.html#breadcrumbs-with-home-icon), and paste in our `components/Breadcrumbs.vue`.

<img src="https://res.cloudinary.com/mayashavin/image/upload/f_auto,q_auto/v1675680195/articles/Nuxt/sfbreadcrumbs" loading="lazy" alt="The Breadcrumbs code in Storefront UI doc site" class="mx-auto"/>

In the `script setup` section, we will change the `breadcrumbs` to props, as follows:

```typescript
const props = defineProps({
  breadcrumbs: {
    type: Array,
    required: true,
  },
});
```

The sample code comes with each breadcrumb having a `name` and a `link`. Hence we need to look for `item.link` and replace them with `item.path` in the `template` section. Also, we want to render `SfLink` as a `NuxtLink` to avoid full page reload by adding `:tag="NuxtLink"` to every `SfLink` appears in the `template`, and the following to the `script` section:

```javascript
import { resolveComponent } from 'vue';

const NuxtLink = resolveComponent('NuxtLink');
```

Great. Our `Breadcrumbs` component is complete. 

Now in `/layouts/default.vue`, we will get the `breadcrumbs` from `useBreadcrumbs()` composable and pass it to `Breadcrumbs` component for rendering, as below:

```html
<template>
    <Breadcrumbs class="mt-4 ml-4" :breadcrumbs="breadcrumbs"/>
    <div class="h-px m-4 bg-neutral-200 divider"></div>
    <slot />
</template>
<script setup>
import { useBreadcrumbs } from '../composables/useBreadcrumbs';

const { breadcrumbs } = useBreadcrumbs();
</script>
```

Finally, make sure you have the following code in your `app.vue`:

```html
<template>
  <NuxtLayout>
    <NuxtPage />
  </NuxtLayout>
</template>
```

And that's all it takes. When we navigate to a page, the app will display the corresponding breadcrumbs:

<img src="https://res.cloudinary.com/mayashavin/image/upload/f_auto,q_auto/v1675680195/articles/Nuxt/simple_breadcrumbs" loading="lazy" alt="The screenshot of breadcrumbs component in nested page" class="mx-auto"/>

### Using the meta field for breadcrumbs

As you notice, the current breadcrumbs use the default name defined in the matched route, which is only sometimes readable. In our implementation, we have used `route.meta?.breadcrumb` as the breadcrumb's name. To define `meta.breadcrumb`, we will use Nuxt's built-in method `definePageMeta` as in the following example:

```html
<script setup>
/**pages/products/index.vue */
definePageMeta({
    breadcrumb: 'Products Gallery',
})
</script>
```

On the build time, Nuxt will merge the desired page's meta into the route's `meta`, and we will have the breadcrumbs displayed accordingly:

<img src="https://res.cloudinary.com/mayashavin/image/upload/f_auto,q_auto/v1675680195/articles/Nuxt/good_breadcrumbs" loading="lazy" alt="The screenshot of formatted breadcrumbs component using meta field" class="mx-auto"/>

Note that you can't define the meta following the above approach for the dynamic route. Instead, in `useBreadcrumbs`, you can watch the `route.params` and get the appropriate name from the params and the relevant data, such as a product's title.

## Summary

You can find the working code [here](https://github.com/mayashavin/storefrontui-nuxt-demo).

In this post, we have explored how to craft a breadcrumbs mechanism for our Nuxt application using its built-in router and visualize them with a Breadcrumbs component using Storefront UI. The implementation is straightforward and may not be optimal in a more complex routing system with multiple layers of nesting dynamic and static routes. However, it is a good starting point for you to build your own breadcrumbs system, and remember, KISS rules!

I hope you find this post helpful. If you have any questions or suggestions, please leave a comment below. I'd love to hear from you.

👉 _If you'd like to catch up with me sometimes, follow me on [Twitter](https://twitter.com/MayaShavin) | [Facebook](https://www.facebook.com/mayashavin/)._

👉 _Learn about Vue with my new book [Learning Vue](https://www.oreilly.com/library/view/learning-vue/9781492098812/). The early release is available now!_

Like this post or find it helpful? Share it 👇🏼 😉