Building my new website with VuePress and Bulma
Tags: vuepress, vue, webdev, javascript
Published: 2020-08-07 01:35:38

---
title: Building my new website with VuePress and Bulma
published: true
description: An overview of why I decided to ditch WordPress and rebuild my website using VuePress (a Vue-powered static site generator) and Bulma.
canonical_url: https://www.adamdehaven.com/blog/building-my-new-website-with-vuepress-and-bulma/
tags: VuePress, vue, webdev, javascript
---

After spending several weeks coming up with a new website look and feel, I'm thrilled to be ditching WordPress (are some of you screaming? :scream:) and launching [my new website](https://www.adamdehaven.com/) built with VuePress and Bulma!

![new website homepage](https://dev-to-uploads.s3.amazonaws.com/i/84l0kgcmekdnf0bv6yad.png)

In this post, I'll share why I decided on VuePress and outline some of the custom solutions I came up with for the "gotchas" I ran into along the way.

## Looking back at my previous site

From 2014 until August of 2020, my personal website stack was comprised of a Linux web server, a MySQL database, and whatever the latest version of WordPress was. I rolled out my own theme, and coded the entire site the way I wanted it to function, meaning I used very few plugins.

I chose WordPress for my website back in 2014 because at the time, a large portion of the web clients I worked with (through contract development work on my own, or through my employer's internal or external clients) were using WordPress as well. Since I was coding within the WordPress ecosystem on a daily basis, the flow was familiar to me, and I was good at it.

I even created [my own tools](https://www.adamdehaven.com/blog/easily-crawl-a-website-and-fetch-all-urls-with-a-shell-script/) and custom [WordPress plugins](https://www.adamdehaven.com/blog/easily-add-pullquotes-to-your-wordpress-posts-with-my-perfect-pullquotes-plugin/) for development tasks I had to do over and over. For the designers out there, I also developed an [Adobe Illustrator extension for specifying dimensions](https://www.adamdehaven.com/blog/specify-dimensions-for-adobe-illustrator-designs-with-a-simple-script/) in layouts that came from the creative department. This sped up everything from layout recreation, to asset exports, to even mocking up trade show booths and engineering drawings.

## Why I decided to ditch WordPress

WordPress is a fantastic content management system with a seemingly infinite supply of custom themes and plugins to do relatively anything you need to do. As a web developer, it also offers similarly infinite ways to customize and develop solutions for clients, no matter their requirements. WordPress is great **if** the complexity it adds to your website is worth the trouble. Let me explain...

### Security vulnerabilities

WordPress, being one of the most popular content management systems on the planet, brings with that attention the vast array of vulnerabilities that WordPress sites are susceptible to (brute-force attacks, SQL injections, cross-site scripting, and even malware).

### Always updating my updates

Have you ever logged in to a WordPress site after a few weeks of being away? Unless you wrote all the code yourself (e.g. no plugins) you're typically greeted by a notification alerting you that there are updates available. Even though the updates are typically one-click-and-done, WordPress Core and plugin updates are notorious for introducing breaking changes; not to mention if you're using a custom theme.

I know none of us are guilty of *ever* skipping regression testing :grimacing:, but even the best of us will miss something that isn't outlined in a changelog from time to time.

### Change doesn't come easy

Making changes to the site's content required logging in to the Dashboard CMS and navigating through the corresponding menus and settings panels to tweak post content and plugins.

Speaking of making changes, if I wanted to update the site design or overall structure, I had to run an entire LAMP stack on my development machine, configure local host files, and keep everything from the code to the database in sync with the live version of the site. Looking back, after rebuilding my website with VuePress, everything involved with WordPress development now seems like a *lot* of overhead.

## My website wish list :pray:

If I was so good at WordPress development, why do something different? The short answer: **it's too complex for my needs**. My website really just consists of a few sections about me and a collection of posts about random projects, code snippets, and whatever else I want to share with my twelve loyal readers (I'm going to try and post more consistently during quarantine :mask: -- lucky you).

As I was evaluating my options for a new development stack, I came up with a wish list of main features I was looking for:

* Fewer security vulnerabilities
* No database required
* Integrated site search
* Use Markdown for creating posts and content
* Code display with syntax highlighting
* Easier workflow for updating the site structure, design, etc.
* Speed improvements. Unless you're using a CDN, WordPress page load times are kinda slow :snail:
* Utilize [Vue.js](https://vuejs.org) - I actually used Vue in a lot of the WordPress sites I developed, but with VuePress, it's built right in!

After doing some research, I knew I wanted to move to a pre-rendered static HTML website. By moving to a static site, I could easily check off several of my wish list items regardless of the actual framework I decided on.

I tried out several other static site generators, but in the end, my love of Vue.js and its ecosystem convinced me to stay in my lane.

Enter VuePress. :boom:

## Why I chose VuePress

[VuePress](https://vuepress.vuejs.org/) is a Vue-powered static site generator. It checks all of the boxes on my wish list above in terms of what I was generally looking for, and has the added benefit of being *extremely* well-integrated with Vue.

### Static HTML

Static sites by nature are extremely fast, and are susceptible to fewer vulnerabilities as they are served as pre-rendered, static HTML. This means there is no database, no login system, and less risk for malware.

The entire site runs as an SPA once the first page is loaded, meaning subsequent page loads are almost instantaneous.

### Markdown

All pages on the site are compiled into HTML from their source Markdown files and then processed as the template of a Vue component. Markdown is extremely easy to write in and allows for fast, efficient content creation.

VuePress uses [markdown-it](https://github.com/markdown-it/markdown-it) as the Markdown renderer which means there are a ton of great plugins and extensions available to customize and extend Markdown functionality on the site.

#### Code snippets

Within Markdown files, I can easily display blocks of code in the body of a page. VuePress utilizes [Prism](https://prismjs.com/) to highlight language syntax in Markdown code blocks which supports a wide range of languages.

I can type the code directly into Markdown syntax (and even highlight a specific line), as shown here:

``` js{2}
const banana = 'b' + 'a' + + 'a' + 'a'
console.log(banana) // -> 'baNaNa'
```

...or, thanks to VuePress's ability to import code snippets, I can even import code directly from other files within my site's structure. Importing turns this Markdown...

``` md
<<< @/path/to/file.js
```

...into this code block:

``` js
export default function () {
  // ..
}
```

#### Linking all the things

As with any site on the web, links are the bread and butter of getting around. Since I'm writing in Markdown, I can easily link to other pages, or even sections on the same page, and when you click, you'll be smooth-scrolled to the corresponding anchor.

VuePress automatically adds `target="_blank" rel="noopener noreferrer"` to outbound links, saving me from having to hard-code all the links in a page.

#### Emoji Support

I can use emoji anywhere I'm using Markdown (basically everywhere). I'm one of the <del>cool</del> kids now.&nbsp;:poop:&nbsp;:man_shrugging:

#### Using Vue inside Markdown files

Since VuePress is powered by Vue, it comes with all of the added benefits of Vue, one of which being that we can use any of our Vue components directly within our Markdown files? This allows for dynamic content within all of the site's pages!

All over this site, I've used Vue components for a majority of the elements that appear on the page; from the page sections themselves, to the images, tooltips, and even the animations.

### Reusable components
We already know that VuePress is powered by Vue, but did you know that means we can reuse components anywhere in the site? By utilizing Vue components, prototyping and new features can be implemented at lightning speed :zap:. Components can even be used and reused inside markdown files.

For example, I like to add images to posts with my `<BaseImage>` Vue component so that I can pass props that define everything from the source file to how the image will be styled. This also means that the component is reactive to any data changes, even after the component is mounted! In my Markdown code, it looks something like this:

``` html
<BaseImage :src="$withBase(img.src)" :alt="img.alt" fullwidth></BaseImage>
```

### Built-in Search :mag:

VuePress offers a search plugin that automatically builds its index from the page title, `h2` and `h3` headers and `tags`. I can't tell you how much time this saves; not only for finding content on the site, but for managing a search index as well.

The integrated search works great for most of my content; however, if you want full text search, you can integrate something like [Algolia Search](https://docsearch.algolia.com/).

## Moving away from traditional hosting

In addition to moving from WordPress to VuePress, I also decided to re-evaluate my web hosting provider. Since I made the decision to move to a static HTML site (i.e. no database, no "server-side" code) I was no longer tied to a traditional web host.

While I actually really liked the hosting company I was with (shoot me a message in the comments if you want the details), I didn't need to continue to pay for services I no longer have any use for.

Looking around the web for different static site hosts allowed me to compile a list of features that you would typically (with a more traditional web host) have to pay for, some whether you use them or not.

### SSL/TLS certificates

If your web host is charging you for SSL/TLS certificates, it's time to move your site. Most hosts are starting to offer these certificates for free and even manage certificate renewal for you. HTTPS is non-negotiable at this point.

If your host doesn't play nice, you can always roll your own! (Shoutout to [Let's Encrypt](https://letsencrypt.org/))

### Hosting a static site is <del>free</del> cheaper

Speaking of free, *some* static site hosts even offer **free** hosting solutions for smaller projects, like this site. :clap:

The mileage varies depending on your needs, but even the paid plans for static site hosting are comparably cheaper to many traditionial hosting plans. It all depends on the size of your team, features of your project, and what nice-to-haves you opt for.

### Making changes is as easy as `git push`

In the crazy age of APIs and web services (how old am I?) you no longer need to sit and watch your FTP client upload files (or even your SSH agent transferring files) whenever you want to make changes to your slick new site. Many hosting providers offer continuous deployments for static sites that connect directly with your source code repository.

This means that you can make your changes locally, and when you're ready, simply push the code to your repository. Within minutes, your changes will be deployed and live on the web!

A similar push-to-deploy strategy is definitely possible with traditional web hosts (I had a bare git repository on my previous web server set up with a post-commit hook); however, integrated continuous deployments take care of all the setup for you.

## Using Bulma with VuePress

So after spinning up a new site with VuePress, why did I decide to import the [Bulma](https://bulma.io/) CSS framework as well? Again, my prior experience led me here. In my current role, I have built several web applications using Bulma as the underlying CSS framework. Bulma is easy to customize, has many useful components that speed up styling elements from scratch, and the framework is modular, meaning I only have to import the components and features I want to implement.

Arguably, the best thing about Bulma that sets it apart from other various options is that it contains *no JavaScript* whatsoever. This fact alone makes it a perfect companion for any Vue app since you would utilize Vue for any necessary JavaScript anyway.

## Custom solutions to weird problems

Building my new site with VuePress was a breeze; however, as goes with any development project, I ran into a few weird problems to solve along the way. When I say "problems," what I am really referring to are quirks with using most static site generators, and more specifically, SPAs. Let's walk through a couple of the solutions I came up with.

### Injecting canonical URLs and additional metadata

All websites should take care to incorporate all of the metadata needed in order to make finding the page via search engines and other sites as easy as possible. This includes page meta tags, [Schema.org structured data](https://schema.org/), [Open Graph tags](https://ogp.me/), and [Twitter Card tags](https://developer.twitter.com/en/docs/tweets/optimize-with-cards/overview/abouts-cards). For sites that are not pre-rendered and run as an SPA, this content is even *more* important, since the page is initially loaded as an empty container (meaning search indexing bots don't have much to look at).

VuePress actually serves pre-rendered static HTML pages (which is *way* better); however, generating all of the desired tags and metadata is still, well, mostly a manual process. You can define some general metadata both in the `.vuepress/config.js` file as well as at the top of pages in the YAML `frontmatter`, but again, this data is not dynamic for the most part and has to be hard-coded into each and every page.

#### Canonical URLs

All sites should include a canonical URL tag in the `<head>` of the page. Canonical URLs are a technical solution that essentially tells search engines which URL to send traffic to for content that it deems worthy as a search result. Another way to think of it is the canonical URL is the preferred URL for the content on the page.

<del>At the time of writing, VuePress does not have a default way to inject canonical URL tags onto a page.</del>

> As of VuePress version `1.7.1` (and thanks to help from me :tada:) the canonical URL can now be set in the `frontmatter` of your VuePress pages by providing a `canonicalUrl` entry. Refer to the [VuePress canonical URL documentation](https://vuepress.vuejs.org/guide/frontmatter.html#canonicalurl) for more details.

#### Additional metadata

Another important consideration for a page's `<head>` tag is the metadata that tells search engines (and even other websites) about the content and purpose of each individual page on your website. This metadata helps to determine whether your page is relevant enough to display in search results, and can be used to give users a preview of the content they will find on your website.

Adding page metadata is supported; however, without using a [plugin](https://vuepress.vuejs.org/plugin/), the information must be hard-coded into the YAML frontmatter block at the top of each individual page and cannot be dynamically generated with JavaScript. This is a problem if, like me, you're kind of lazy and don't like doing the same task over and over.

#### Injecting metadata, structured data, and canonical URLs

Instead of hard-coding all of the metadata and structured data directly into each and every page, I developed a custom solution that injects the canonical URL, structured data, and additional metadata content into each page at compile time along with the rest of the page content.

The plugin loops through all of the common metadata (title, description, etc.) along with a few custom `frontmatter` properties and `.vuepress/config.js` settings and creates the corresponding tags and injects them into the rendered page.

You can check out the other post to see [how to add metadata, canonical URLs, and structured data to your VuePress project](https://dev.to/adamdehaven/how-to-add-metadata-canonical-urls-and-structured-data-to-your-vuepress-site-with-a-plugin-5acm) :thumbsup:

### Prevent draft posts from being published

My site utilizes the official [VuePress Blog Plugin](https://vuepress.vuejs.org/plugin/official/plugin-blog.html) to add post classification (e.g. tags and categories), pagination, and other blog-related APIs.

Since any Markdown file placed into my `/_posts/` directory will automatically be published as a live post, I had to figure out a way to allow these drafts to live alongside the rest of my codebase, but prevent them from being published when I push changes to the site.

Ok, admittedly, I shouldn't be writing draft posts on the `master` branch in the first place, but hey, I like to live on the edge :man_shrugging:

I handle this with a setting in the `frontmatter` of any draft post, and then detect the build context in my custom theme's config file located at `/.vuepress/theme/index.js` to filter out pages where `frontmatter.draft === true`, as shown here:

``` yaml
# Draft blog post (showing frontmatter YAML)
---
draft: true
---
```

``` js
module.exports = (options, ctx) => {
    return {
        // other code...
        async ready () {
            // Filter out draft posts in prod mode
            if (ctx.isProd) {
                ctx.pages.splice(
                    0,
                    ctx.pages.length,
                    ...ctx.pages.filter(({ frontmatter }) => frontmatter.draft !== true),
                )
            }
        },
        // more code...
    }
}
```

### Adding sticky posts to the blog plugin

Sometimes it's nice to "pin" a post at the top of the list in order to feature the content on the site when it's relevant (even after other posts have been published). The VuePress Blog Plugin doesn't offer this functionality out of the box; however, it's easy to add the feature by simply modifying the post's `frontmatter` along with the `sorter` function in the theme's `/.vuepress/index.js` file:

``` yaml
---
# Make the post sticky in the .md file frontmatter
sticky: true
---
```

``` js
// .vuepress/index.js
// VuePress Blog Plugin config
['@vuepress/blog',
    {
        directories: [
            {
                // ... other plugin configuration properties
                pagination: {
                    sorter: (prev, next) => {
                        // Sticky posts to the top
                        if (prev.frontmatter.sticky) return -1
                        if (next.frontmatter.sticky) return 1
                        // Sort all other posts by date
                        const dayjs = require('dayjs')
                        const prevTime = dayjs(prev.frontmatter.date)
                        const nextTime = dayjs(next.frontmatter.date)
                        return prevTime - nextTime > 0 ? -1 : 1
                    },
                },
            }
        ]
    }
]
```

### Prevent indexing of deploy previews

I decided to host my new site on [Netlify](https://www.netlify.com/) (which is great by the way) due to their offering of continuous deployments, free SSL/TLS certificates, and other great features they offer for hosted sites. One issue I ran across were the [deploy previews](https://docs.netlify.com/site-deploys/overview/#deploy-preview-controls) that Netlify offers and finding a means of locking them down from search engines.

When the automated depoy preview is created, the URL is accessible to the world (well, if they find the public URL). This isn't ideal. To prevent the deploy preview URLs from being indexed, I actually hooked into the [file-based configuration](https://docs.netlify.com/configure-builds/file-based-configuration/#deploy-contexts) settings to change my `robots.txt` file when a deploy is not triggered by the main branch of my codebase.

As you can see from the `netlify.toml` config file below, if my site is being deployed from the `master` branch (i.e. code that is ready to go live), Netlify runs the default `npm run build` command; however, if it is deploying a preview, it runs `npm run build:noindex` which changes the content of my `robots.txt` file to prevent the indexing of this preview URL.

``` toml
# netlify.toml

[build]
    command = "npm run build"

[context.deploy-preview]
    command = "npm run build:noindex"

[context.branch-deploy]
    command = "npm run build:noindex"
```

The corresponding build commands in my `package.json` file are shown below. When the `netlify.toml` configuration calls the `npm run build:noindex` script, it builds the VuePress site and then rewrites the content of my `/robots.txt` file to disallow all user agents.

``` json
// package.json

{
    "scripts": {
        "build": "vuepress build",
        "build:noindex": "npm run build && echo 'User-agent: *\nDisallow: /' > .vuepress/dist/robots.txt"
    }
```

There are other ways of preventing access to deploy previews, but since my previews are really just for me (and not live on the web for long) this solution serves its purpose.

## So am I glad I ditched WordPress?

The short answer: **definitely**. :tada:

VuePress is packed full of great features and that makes building a new site (for documentation, a blog, or anything really) a breeze. It incorporates <del>the best</del> arguably the best JavaScript framework currently available, and makes incorporating custom content and components easy since most of the underlying logic is baked right in.

:wave: Thanks for checking out my experience with rebuilding my site with VuePress! Share any questions or feedback in the comments!