Blog Index in VuePress
VuePress is an awesome static site generator. Thanks to this tool, it's really easy to transform any static content into a performant, mobile-friendly, good looking website.
It was created mainly to support Vue.js organization own sub projects. However, since it's powered by Vue.js itself, it is possible to create Vue components within VuePress and use them in markdown.
I leveraged that feature to design an automatic blog index showing the latest posts that would serve as a landing page when accessing this blog. This post is to share how I did it.
Blog Index Component
This is the component that will dynamically render the list of recent posts in the blog landing page (i.e. the page at /blog/
).
The goal is to have the README.md
file to have minimal content.
---
title: Blog
---
<BlogIndex/>
At build time, VuePress will look at the BlogIndex.vue
component and render it in that page based on the data it has.
WARNING
All the custom Vue components must be defined in the .vuepress/components
directory.
BlogIndex.vue
The goal is to have the following:
- List the posts sorted by posted date with the most recent post at the top.
- Show the title.
- Enumerate a list of tags that describes the post.
- Show the posted date
- Show a description
- Have a
Read More
call-to-action link that links into the post.
The component template could roughly look something like the following.
<template>
<div>
<div v-for="post in posts">
<h2>
<router-link :to="path">{{title}}</router-link>
</h2>
<div v-for="tag in tags">{{tag}}</div>
<div>{{date}}</div>
<div>{{description}}</div>
<div>
<router-link :to="path">Read More</router-link>
</div>
</div>
</div>
</template>
Globally Computed Properties
The next question is:
Where to dynamically get the data?
Thankfully, VuePress computes some data based on the structure of the site and exposes them in the root Vue instance.
The first property to define is the posts
property used in <div v-for="post in posts">
. We need to find the posts from the structure and sort them latest first. Furthermore, any other pages on this website should not end up in that blog index.
The main computed property that VuePress expose is $site
. This object has a few useful properties, but the one that interests us here is $site.pages
. It is an array with informations on all web pages present on the site. We'll use that to find the blog posts.
Since all the posts are under /blog/posts/
, we can filter the pages to only keep the posts themselves.
$site.page.filter(page => page.path.startsWith('/blog/posts/')))
Note, that each element of this array is a page object, which defines the path
property. Furthermore, the page object has all the frontmatter data for that page under its frontmatter
property.
This is really useful to have metadata on the post defined at the top of the markdown file defining the post. In addition to have theme configuration in the frontmatter, custom properties can be defined.
For example, this is the frontmatter of this very post.
---
title: Blog Index in VuePress
sidebar: auto
postId: 3
date: October 16th, 2018
description: How to make an automatic blog index to present available blog posts in VuePress using Vue components.
tags:
- VuePress
- Vue
- Blog
---
Utilizing that fact, the BlogIndex
component is then really easy to implement.
<template>
<div>
<div v-for="post in posts">
<h2>
<router-link :to="post.path">{{post.frontmatter.title}}</router-link>
</h2>
<div v-for="tag in post.frontmatter.tags">{{tag}}</div>
<div>{{post.frontmatter.date}}</div>
<div>{{post.frontmatter.description}}</div>
<div>
<router-link :to="post.path">Read More</router-link>
</div>
</div>
</div>
</template>
<script>
export default {
computed: {
posts() {
return this.$site.pages
.filter(page => page.path.startsWith('/blog/posts/'))
.sort((a, b) => b.frontmatter.postId - a.frontmatter.postId);
}
}
}
</script>
Post Title Component
The blog index page shows useful informations regarding the posts. It would be nice to reuse some of that information to automatically format the post title.
Thus, I created a new Post Title Component to insert at the beginning of each post to format the title.
Refactor Common Components
Since the post title will reuse some parts from the blog index, I wanted to refactor two common components first: PostedOn
and TagList
. This refactor avoids to duplicate layout, style, and data management across the components.
PostedOn
<template>
<div>Posted on {{date}}</div>
</template>
<script>
export default {
props: {
date: String
}
}
</script>
TagList
<template>
<div class="tags">
<div v-for="tag in tags" class="tag">{{tag}}</div>
</div>
</template>
<script>
export default {
props: {
tags: Array
}
}
</script>
Final Result
This refactor allows the PostTitle
component to be really straightforward.
<template>
<div>
<h1>{{postDetails.title}}</h1>
<TagList :tags="postDetails.tags"/>
<PostedOn :date="postDetails.date"/>
</div>
</template>
<script>
export default {
computed: {
postDetails() {
return this.$page.frontmatter;
}
}
}
</script>
References
- VuePress - Using Vue in Markdown
- Vue.js - Components Basics
- Vue.js - Single File Components
Check it out!
The blog index idea and this post was inspired by a similar post: In-Depth VuePress Tutorial: Vue-Powered Docs & Blog by Charles Ouellet.
