The Perfect Breadcrumbs (in Nuxt)
Tags: nuxt, javascript, showdev
Published: 2020-11-10 09:46:52

---
title: The Perfect Breadcrumbs (in Nuxt)
published: true
description: A small component for semantic breadcrumbs in Nuxt (or VueJS)
tags: nuxt, javascript, showdev
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/tyi1t4vk05dvxx8iiyq0.png
series: "WordPress to Nuxt"
---

Breadcrumbs can be a bit of a pain. But there are lots of reasons you might want to make them.

TL;DR: I made a self-contained component that builds semantic breadcrumbs based on the path to the file using the router. It matches the paths against the router before outputting links.

The gist is at the end of the post. 

## What Are Breadcrumbs and Do They Really Deserve This Much Attention?

Breadcrumbs most commonly appear at the top of a page as text links denoting the path to the post (or back to the index). Breadcrumbs are an important navigation mechanism.

Combined with structured data or semantic markup like RDFa, they also act as an important SEO tool, for sites such as Google to understand the structure of your site.

![Showing breadcrumbs on Vonage.com](https://dev-to-uploads.s3.amazonaws.com/i/476kky930fh3xgb871hx.png)

When Google finds the data it needs, it can display the site structure in results.

![Showing Vonage.com breadcrumbs in a Google result](https://dev-to-uploads.s3.amazonaws.com/i/hbuyt5t5cgvpg5heybz7.png)

## Why Make This?

Most of the examples I found online take an array from the page you're placing the breadcrumbs. This works from the `/` divided path but skips paths that the router cannot match.

## How Does It Work?

I'll focus on the JS rather than the JSX. You'll likely make better markup for it than I would.

Starting with an empty output.

```js
export default {
  computed: {
    crumbs() {
      const crumbs = []

      return crumbs
    },
  },
}
```

Now, we'll get the current full path.

```js
export default {
  computed: {
    crumbs() {
      const fullPath = this.$route.fullPath
      const params = fullPath.substring(1).split('/')
      const crumbs = []

      console.log(params) 

      // url:     /blog/2020/11/20/my-post-url
      // outputs: ['blog','2020','11','20','my-post-url']

      return crumbs
    },
  },
}
```

Next, recompile the URL bit by bit.

```js
export default {
  computed: {
    crumbs() {
      const fullPath = this.$route.fullPath
      const params = fullPath.substring(1).split('/')
      const crumbs = []

      let path = ''

      params.forEach((param, index) => {
        path = `${path}/${param}`

        console.log(path)

      })

      // outputs: /blog
      //          /blog/2020
      //          /blog/2020/11
      //          /blog/2020/11/20
      //          /blog/2020/11/20/my-post-url

      return crumbs
    },
  },
}
```

Now, match each route on the router.

```js
export default {
  computed: {
    crumbs() {
      const fullPath = this.$route.fullPath
      const params = fullPath.substring(1).split('/')
      const crumbs = []

      let path = ''

      // test path
      params.push('fake')

      params.forEach((param, index) => {
        path = `${path}/${param}`
        const match = this.$router.match(path)

        if (match.name !== null) {
          console.log(`yep:  ${path}`)
        } else {
          console.log(`nope: ${path}`)
        }
      })

      // outputs: yep:  /blog
      //          yep:  /blog/2020
      //          yep:  /blog/2020/11
      //          yep:  /blog/2020/11/20
      //          yep:  /blog/2020/11/20/my-post-url
      //          nope: /blog/2020/11/20/my-post-url/fake

      return crumbs
    },
  },
}
```

Finally, capture only matches.

```js
export default {
  computed: {
    crumbs() {
      const fullPath = this.$route.fullPath
      const params = fullPath.substring(1).split('/')
      const crumbs = []

      let path = ''

      params.forEach((param, index) => {
        path = `${path}/${param}`
        const match = this.$router.match(path)

        if (match.name !== null) {
          crumbs.push(match)
        }
      })

      return crumbs
    },
  },
}
```

In mine, I turn the param into a title using `ap-style-title-case`. I have a prop that I let folks override the autogenerated page title for blog posts where the slug might not perfectly turn back into a title.

```js
const titleCase = require('ap-style-title-case')

export default {
  props: {
    title: {
      type: String,
      default: null,
    },
  },

  computed: {
    crumbs() {
      const fullPath = this.$route.fullPath
      const params = fullPath.startsWith('/')
        ? fullPath.substring(1).split('/')
        : fullPath.split('/')
      const crumbs = []

      let path = ''

      params.forEach((param, index) => {
        path = `${path}/${param}`
        const match = this.$router.match(path)

        if (match.name !== null) {
          crumbs.push({
            title: titleCase(param.replace(/-/g, ' ')),
            ...match,
          })
        }
      })

      return crumbs
    },
  },
}
```

## The Full Code

Check out the gist for the whole component!

{% gist https://gist.github.com/lukeocodes/34776ddfa3c690627e28e9bb110ed1c5 %}