How to create category and tag pages on VuePress

Category:
Last Updated: 2021/12/13 12:40:54

VuePress itself doesn't have any pages for tags or categories. You can create them by using @vuepress/blog plugin, but I decided to customize for this function because I want to use Layout.vue as a layout file for tag and category pages and control details.

Note that the word taxonomy is referred to as tag and category in this article.

# eject default theme

Although VuePress default theme is inside VuePress , you can extract the theme files by viuepress eject.

$ npx vuepress eject src/.vuepress/theme/
1

If you want to customize the default theme, you can also create a theme which extends the default theme. But in my case, I want to customize the theme by reading the original one and I feel I'm able to customize details more efficiently.

If you eject the original theme to src/.vuepress/theme/, VuePress automatically load the ejected files, so what you have to do is just to modify the ejected files.

# File Structure

First, I intended to create the function in which, when I put category/tags to a page on frontmatter, the pages of category/tags archives are automatically created. However, I tried and could not achieved this, finally I have to put .md files when I need a new taxonomy. Then by this customization in this article, when you add a new tags or categories to a page, you should create .md file correspondent to the added tags/categories.

In the end, you need to create or modify following files.

src/
├── .vuepress
│   ├── components
│   │   └── Taxonomies.vue(*Taxonomy list page)
│   └── theme
│       ├── components
│       │   ├── Sidebar.vue(*Sidebar)
│       │   ├── SidebarLatest.vue(*(Added)Latest articles in sidebar)
│       │   ├── SidebarLink.vue(*for each sidebar link : add category to the title text)
│       │   ├── SidebarTaxonomies.vue(*(Added)Taxonomies in sidebar)
│       │   └── Taxonomy.vue(*Taxonomy archive page)
│       └── layouts
│           └── Layout.vue(*Call Taxonomy Component in Taxonomy archive page)
├── articles(*Articles)
│   ├── README.md
│   └── (略)
├── categories(*Categories)
│   ├── README.md
│   ├── nginx.md
│   └── vuepress.md
└── tags(*Tags)
    ├── README.md
    ├── nginx.md
    └── vuepress.md
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# Set taxonomies to each articles

# articles directory

Put category and tags in the frontmatter in pages in articles/ directory like this.

# articles/vuepress/taxonomies.md

---
title:  "How to create category and tag pages on VuePress"
date:   2021-11-27T13:23:00+09:00
category: vuepress
tags:
  - "vuepress"
  - "customize"
---
1
2
3
4
5
6
7
8

This article has vuepress category and vuepress tags and customize tags, then this article is listed in these taxonomy archive pages.

# taxonomy directory and taxonomy archive pages

Category and tag directories and pages are like this:

  • /tags/ : Tag list
  • /categories/ : Category list
  • /tags/* : Tag archive pages
  • /categories/* : Category archive pages

# README.md

categories/README.md and tags/README.md is for taxonomy list.

Put Taxonomies component in README.md like this.

# categories/README.md

---
title:  "Categories"
---

# Categories

<Taxonomies taxonomy="category" />
1
2
3
4
5
6
7

# tags/README.md

---
title:  "Tags"
---

# Tags

<Taxonomies taxonomy="tags" />
1
2
3
4
5
6
7

And Taxonomies component will list each categories or tags.

# components/Taxonomies.vue

Most of vue files are put in .vuepress/theme/components , but this file is created in .vuepress/components because this component is referred from md files.



<template>
	<div class="sidebar-links">
	  	<ul class="sidebar-links">
			<li v-for="post in posts" :key="post.path">
				<RouterLink :to="post.path">{{ post.title }}</RouterLink>
			</li>
		</ul>
	</div>
</template>

<script>

import moment from "moment";

export default {
	name: 'Taxonomies',

	props: ["taxonomy"],
	data () {
		return {};
	},

	computed: {
		posts() {
			const tax = this.taxonomy;
			let pages = this.$site.pages.filter(post => {
				// skip if `taxonomy` is not defined or defferent
				if (!post.frontmatter || !post.frontmatter.taxonomy || 
					post.frontmatter.taxonomy !== tax) 
				{
					return false;
				}
				return true;
			}).map(post => {
				post._date = moment(post.frontmatter.date).format("YYYY/MM/DD");
				return post;
			});
			pages.sort(()=> Math.random() - 0.5);

			return pages;
		},
	}
}

</script>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

# Create Taxonomy archive pages

# .md file

As written above, you have to create each .md files correspondent to each taxonomy archive page.

The following is .md files for vuepress category and tag.

# categories/vuepress.md

---
title:  "Vue Press"
taxonomy: "category"
terms: "vuepress"
---

VuePress is a static site generator, which is also used in this site.
1
2
3
4
5
6
7

# tags/vuepress.md

---
title:  "Vue Press"
taxonomy: "tags"
terms: "vuepress"
---
1
2
3
4
5

For instance, in categories/vuepress.md, frontmatter includes taxonomy: category and terms: vuepress.
If you want to add categories or tags, you can create a new .md file.

# components/Taxonomy.vue

components/Taxonomy.vue shows a list of pages with a single taxonomy.


<template>
  <main class="page">
    <slot name="top" />
    <div class="theme-default-content">
      <h2>{{tax_name}}: {{ title }}</h2>
      <Content />
      <ul>
        <li v-for="post in posts" :key="post.path">
          {{post._date}}
          <RouterLink :to="post.path">
            {{ post.title }}
          </RouterLink>
        </li>
      </ul>
    </div>

    <slot name="bottom" />
  </main>
</template>

<script>
import _orderBy from "lodash/orderBy";
import moment from "moment";

export default {
  props: [],

  computed: {
    tax_name() {
    	return this.$page.frontmatter.taxonomy;
    }, 
    title() {
    	return this.$page.frontmatter.title;
    },

    posts() {
      const self = this;
      const taxonomy = this.$page.frontmatter.taxonomy;
      const terms = this.$page.frontmatter.terms;

      const posts = this.$site.pages.filter(post => {
		if (typeof post.frontmatter[taxonomy] !== "undefined") {
			if (Array.isArray(post.frontmatter[taxonomy])) {
				return post.frontmatter[taxonomy].includes(terms);
			} else {
				return post.frontmatter[taxonomy] == terms;
			}
		}
        return false;
      }).map(post => {
        post._date = moment(post.frontmatter.date).format("YYYY/MM/DD");
        return post;
      });
      return _orderBy(posts, ["_date"], ["desc"]);
    }
  },
}
</script>

<style lang="stylus">
@require '../styles/wrapper.styl'

.page
  padding-bottom 2rem
  display block

</style>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69

# modify layouts/Layout.vue

Then I modified Layout.vue to call the Taxonomy component I created above.

I already created taxonomy archive .md files and in those files I put taxonomy in frontmatter. Layout.vue renders the Taxonomy component when a .md file has taxonomy in frontmatter.

<template>
  <!-- ... -->
  <Home v-if="$page.frontmatter.home" />
  <Taxonomy v-else-if="$page.frontmatter.taxonomy" />
  <Page ...
  />
  <!-- ... -->
</template>

<script>
// ...

import Taxonomy from '@theme/components/Taxonomy.vue'

export default {
  // ...
  components: {
    // ...
    Taxonomy, 
  },
  // ...
};
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

And then, type yarn dev and if http://localhost:8080/tags/vuepress and http://localhost:8080/categories/vuepress work correctly, we've finished taxonomy archive pages 💮

# Add tags and categories on sidebar

Now I modified following 4 files to add tags and categories on the sidebar.
In addition, I also added a list of latest posts on the sidebar.

  • SidebarLink.vue(*for each sidebar link : add category to the title text)
  • (Added) SidebarLatest.vue(*Latest articles in sidebar)
  • (Added) SidebarTaxonomies.vue(*Taxonomies in sidebar)
  • Sidebar.vue(*Sidebar)

SidebarLink is originally used for showing the link(s) of items on the sidebar, and I modified this file so that you can set a category prop (boolean) to show the category of items.

export default {
  props: ['item', 'sidebarDepth', 'category'],

  render (h,
    {
      parent: {
        $page,
        $site,
        $route,
        $themeConfig,
        $themeLocaleConfig
      },
      props: {
        item,
        sidebarDepth, 
        category, // added
      }
    }) {

    // modified to add item.frontmatter.category
    let text_ = item.title || item.path;
    if (category && item.frontmatter.category) {
      text_ = `[${item.frontmatter.category}] ${text_}`;
    }
    const link = item.type === 'external'
      ? renderExternal(h, item.path, text_)
      : renderLink(h, item.path, text_, active)
    
    //...
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

# components/SidebarLatest.vue

SidebarLatest.vue shows latest articles on the sidebar. You can set the category prop true on SidebarLink.



<template>

<div class="sidebar-links">
	<div class="sidebar-group">
		<h4 class="sidebar-heading">Latest Posts</h4>

		<ul
			v-if="pages.length"
			class="sidebar-links"
		>
			<li
				v-for="(page, i) in pages"
				:key="i"
			>
				<SidebarLink
			  	:sidebar-depth="0"
			  	:item="page" 
					:category="true"
				/>
			</li>
		</ul>
	</div>
</div>

</template>

<script>
import SidebarGroup from '@theme/components/SidebarGroup.vue'
import SidebarLink from '@theme/components/SidebarLink.vue'
import _orderBy from "lodash/orderBy";

import moment from "moment";

export default {
	name: 'SidebarLinks',

	components: { SidebarGroup, SidebarLink },

	data () {
		return {};
	},
	computed: {
		pages() {
			let pages = this.$site.pages.filter(post => {

				if (post.frontmatter) {
					if (post.frontmatter.home) return false; //home
					if (post.frontmatter.taxonomy) return false; // exclude taxonomy pages
				}
				if (post.relativePath) {
					// just under articles/
					if (post.relativePath.indexOf("articles/") !== 0) {
						return false;
					}

					// ignore README.md
					if (post.relativePath.substr(-9) === "README.md") {
						return false;
					}
				}

				return true;
			}).map(post => {
				post._date = moment(post.frontmatter.date).format("YYYY/MM/DD");
				return post;
			});
			pages = _orderBy(pages, ["_date"], ["desc"]);

			if (pages.length > 10) pages = pages.slice(0, 10);

			return pages;
		},
	}
}
</script>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78

# components/SidebarTaonomies.vue

SidebarTaonomies.vue shows taxonomies on the sidebar.




<template>
<div class="sidebar-links">
	<div class="sidebar-group">
		<RouterLink :to="root_url" class="sidebar-heading clickable" v-if="root_url">{{title}}</RouterLink>
		<h4 class="sidebar-heading" v-else>{{title}}</h4>

	  	<ul class="sidebar-links">
			<li
			v-for="(tag, i) in tags" 
			:key="i"
			>
			<SidebarLink
				:sidebar-depth="0"
				:item="tag"
			/>
			</li>
		</ul>
	</div>
</div>
</template>

<script>
import SidebarGroup from '@theme/components/SidebarGroup.vue'
import SidebarLink from '@theme/components/SidebarLink.vue'
import _orderBy from "lodash/orderBy";

import moment from "moment";

export default {
	name: 'SidebarTaxonomies',

	components: { SidebarGroup, SidebarLink },
	props: ["taxonomy", "title", "root_url"],
	data () {
		return {};
	},

	computed: {
		tags() {
			const tax = this.taxonomy;
			let pages = this.$site.pages.filter(post => {
				// タグ指定が指定のものでないものはパス
				if (!post.frontmatter || !post.frontmatter.taxonomy || 
					post.frontmatter.taxonomy !== tax) 
				{
					return false;
				}
				return true;
			}).map(post => {
				post._date = moment(post.frontmatter.date).format("YYYY/MM/DD");
				return post;
			});
			//pages = _orderBy(pages, ["_date"], ["desc"]);
      pages.sort(()=> Math.random() - 0.5);

			return pages;
		},
	}
}

</script>



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67

# components/Sidebar.vue

I've already created SidebarLatest and SidebarTaxonomies components, then I made Sidebar refer to these components so that you can see latest posts and lists of taxonomies (categories and tags) on the sidebar.

<template>
  <aside class="sidebar">
    <NavLinks />

    <slot name="top" />

    <template v-if="items.length > 0">
      <SidebarLinks
        :depth="0"
        :items="items" 
      />
    <hr />
    </template>
    <slot name="bottom" />

    <SidebarLatest />
    <SidebarTaxonomies taxonomy="category" title="Categories" root_url="/categories/" />
    <SidebarTaxonomies taxonomy="tag" title="Tags" root_url="/tags/" />
  </aside>
</template>

<script>
// ...

import SidebarLatest from '@theme/components/SidebarLatest.vue'
import SidebarTaxonomies from '@theme/components/SidebarTaxonomies.vue'

export default {
  // ...
  components: { SidebarLatest, SidebarTaxonomies },
  // ...
}
</script>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

Category:
Last Updated: 2021/12/13 12:40:54
Copyright © Web Ninja All Rights Reserved.