Blog Index in VuePress

VuePress
Vue
Blog
Posted on October 20th, 2018

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

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.

Author's Picture

Damien Springuel

Software Developer

Saskatoon, SK, Canada
If you have any questions, comments, or feedback regarding this post, you can contact me at contact@damien-springuel.ca.
Last Updated: 10/22/2018, 1:14:16 AM