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.
When Google finds the data it needs, it can display the site structure in results.
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.
export default {
computed: {
crumbs() {
const crumbs = []
return crumbs
},
},
}
Now, we'll get the current full path.
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.
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.
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.
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.
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!