Use VuePress to build static html files

Finding a way to build with VuePress has been a source of confusion for many people. Some very trivial issues get in the way, but they were difficult to discover. The main issue this article solves is the current working directory causing viewpress build to not find config.js (but not warn you of that) and hence ignore the base setting.

This article contains a summary followed by detailed instructions for creating a new site from scratch.

A list of other static site generators is at the bottom (a website and repo using these instructions are just before that).

Before (broken due to vuepress build not finding .vuepress, or .vuepress/config.js not containing correct settings):

After:

Summary

I solved this by simply running vuepress directly instead of using yarn (same problem may apply to npm or npx):

cd src
../node_modules/.bin/vuepress build

Why: Even if I do cd src, yarn vuepress build would run vuepress build from the package directory (the directory containing package.json)–the wrong directory. The same problem likely exists with npm or npx trying to solve other problems, confusing vuepress. I originally used yarn to run vuepress for me since npx usually works on Windows for that type of thing. However, in this case, the executable expected a different directory (src in this case) not the root of the package.

You must always run vuepress from the directory containing the .viewpress directory (not that directory itself, nor some higher directory than the one containing it). If you ran yarn create vuepress-site, the directory will be called src.

If you are creating a site from scratch, continue reading. Skip to “Configure viewpress” if your site is already setup as recommended in the bullet points under that.

I’ll explain how to set up your package with a git repo to serve files using a plain html server (but not serve the package directory). In this case, the package directory containing package.json is the same directory as the git repo.

  • These are special instructions for when you have a subdirectory on your site which is a different VuePress instance than the main website address.

Windows Differences

I used Linux. For Windows:

  • Install git, nodejs, and yarn differently. See their respective websites for instructions.
  • git, nodejs, and yarn must be in your PATH for your user or system (close and re-open the Command Prompt, or log out then back in if necessary after you change the PATH)
  • MAINSITE= becomes SET MAINSITE=
  • $MAINSITE becomes %MAINSITE%
  • mv becomes move
  • mkdir becomes md
  • \ at the end of a line to continue it becomes ^
  • cat becomes type (and to echo multiple lines, change cat << ENDEND to ()> as shown here (but you can also do the steps by hand instead of using neither cat nor type).
  • Other changes may be necessary.

Install Node 12 and Yarn

As of December 2020, VuePress requires Node 10.x, but the commands below install Node 12.x. The install steps below for installing Node.js on Ubuntu 18.04 are at https://www.digitalocean.com/community/tutorials/how-to-install-node-js-on-ubuntu-18-04 (See also https://computingforgeeks.com/installing-node-js-10-lts-on-ubuntu-18-04-16-04-debian-9/). The same goes for yarn instructions at https://classic.yarnpkg.com/en/docs/install/#debian-stable.

Ubuntu or Debian:

This article assumes you have already nginx, Apache, or another http
server to show $DOMAIN_PATH when the user accesses your web server from
the domain you desire, and that the domain you desire is registered and
points to your server.

Make a Subdirectory Site From Scratch

This article assumes you have already nginx, Apache, or another http server to serve the $DOMAIN_PATH mentioned below when the user accesses your web server from the domain you desire, and that the domain you desire is registered and points to your server.

Create a blank git repo

It should only initialized with README.md, then continue. Make sure you have git, nodejs, and yarn installed (and in your PATH on windows).

Make the VuePress demo into a git repo

These steps set up the repo so package.json is at the root of the repository and /var/www/*-meta/*-meta contains your site’s subdirectory (to avoid serving the metadata publicly, do not put them in the html subdirectory–the steps in the “Publish to Static HTML” section will put html files in the correct place under there). Ideally, these command should be run as an unpriveleged user (to avoid running dev tasks as root, and avoid unpriveleged user not being able to publish its data) as shown in sudo su $WWW_USER -s /bin/bash command (where $WWW_USER is www-data or other server user).

At your own risk, you can force www-data to have a structure similar to a home directory (skip this if you have a writeable home directory for your web server user or aren’t sure this is safe):

# If $WWW_USER can write to $WWW_HOME, skip all of this.

WWW_USER=www-data
WWW_GROUP=www-data
WWW_HOME=/var/www
# ^ change that to user-data if you are serving files from
# Mail-in-a-Box, or to other user if you have some other www user.

MAINSITE="the-great-website"
# ^ Change the variable to whatever you are using.

mkdir -p $WWW_HOME/$MAINSITE-meta
mkdir -p $WWW_HOME/.cache
mkdir -p $WWW_HOME/.config
mkdir -p $WWW_HOME/.yarn
rm -Rf $WWW_HOME/.yarnrc  # FILE not directory
touch $WWW_HOME/.yarnrc
touch $WWW_HOME/.bashrc
touch $WWW_HOME/.bash_logout
mkdir -p $WWW_HOME/.local
sudo chown -R $WWW_GROUP:$WWW_USER $WWW_HOME/$MAINSITE-meta
# ^ -R in case you ran as root before and there is a
#   yarn-error.log or other files there.
sudo chown -R $WWW_GROUP:$WWW_USER $WWW_HOME/.cache
sudo chown -R $WWW_GROUP:$WWW_USER $WWW_HOME/.config
sudo chown -R $WWW_GROUP:$WWW_USER $WWW_HOME/.yarn
sudo chown $WWW_GROUP:$WWW_USER $WWW_HOME/.yarnrc
sudo chown $WWW_GROUP:$WWW_USER $WWW_HOME/.bashrc
sudo chown $WWW_GROUP:$WWW_USER $WWW_HOME/.bash_logout
sudo chown -R $WWW_GROUP:$WWW_USER $WWW_HOME/.local

Setup the environment (change the values in quotes to your own configuration!):

WWW_USER="www-data"
sudo su $WWW_USER -s /bin/bash

WWW_USER="www-data"
WWW_GROUP="www-data"
MAINSITE="the-great-website"
SUBDIR="some-new-directory"
# If you want a sub-sub directory, you must put the 
# slashes in PUBLIC_HTML (below) instead of in SUBDIR,
# or modify the commands.
SUBDIR_REPO="https://github.com/poikilos/some-repo.git"
WWW_HOME="/var/www"
PUBLIC_HTML="/var/www/html"
# ^ Change these variables to whatever you are using.
#   You have to set them again for this user.

Generate the package (and move files around so that package.json is in the root of the repo):

# I do NOT cd to html directory,
# because I only want to serve the html, not my vuepress metadata 
mkdir -p $WWW_HOME/$MAINSITE-meta
# ^ should exist by now, so sudo or root could change owner
cd $WWW_HOME/$MAINSITE-meta

git clone $SUBDIR_REPO $SUBDIR-old-repo

echo "Follow the instructions to setup the site"
echo "(for name of project, you can leave out -tmp-meta):" 

yarn create vuepress-site $SUBDIR-tmp-meta

# I don't need an extra subdirectory--
# lets just put the directory containing package.json
# into the root of the repo:
if [ -z "$TEMPLATE_DOCS" ]; then
    TEMPLATE_DOCS=/docs
fi
mv $SUBDIR-tmp-meta$TEMPLATE_DOCS $SUBDIR-meta
rmdir $SUBDIR-tmp-meta  # this should be empty if yarn
if [ $? -neq 0 ]; then
    echo "STOP HERE and set TEMPLATE_DOCS to the"
    echo " subdirectory of $SUBDIR-tmp containing"
    echo " package.json, then run again."
else
    if [ ! -f $SUBDIR-meta/README.md ]; then
        mv -n $SUBDIR-old-repo/README.md \
            $SUBDIR-meta/README.md
    else
        mv -n $SUBDIR-old-repo/README.md \
            $SUBDIR-meta/README-$SUBDIR-old-repo.md
    fi
    mv -n $SUBDIR-old-repo/.git $SUBDIR-meta/
    rmdir $SUBDIR-old-repo
fi

Static html file build settings

These are the two goals for this section:

  • $WWW_HOME/$MAINSITE-meta/$SUBDIR-meta will be the repo
  • $PUBLIC_HTML/$SUBDIR will get the generated static HTML. Don’t edit these, VuePress will overwrite this directory on each build. $PUBLIC_HTML is equivalent to $MAINSITE but the way we are doing this, VuePress will only write html files to $SUBDIR.

Configure VuePress

Before continuing, ensure that:

  • Run the “Setup the environment” steps as $WWW_USER.
  • ls $WWW_HOME/$MAINSITE-meta/$SUBDIR-meta includes package.json
  • ls $WWW_HOME/$MAINSITE-meta/$SUBDIR-meta/src/.vuepress` includes config.js.
  • cat $WWW_HOME/$MAINSITE-meta/$SUBDIR-meta/.gitignore includes node_modules

Generate settings to ensure the correct directories are used (base ensures that your html can see your css, dest ensures vuepress will build to the $PUBLIC_HTML directory not the repo):

cat << END
Copy the following:

  dest: '$PUBLIC_HTML/$SUBDIR',
  base: '/$SUBDIR/',

END

Press enter, then copy to the clipboard (whatever shows when you run the cat command) then:

sudo apt install nano  # or use a different text editor below
nano $WWW_HOME/$MAINSITE-meta/$SUBDIR-meta/src/.vuepress/config.js

Paste the base and dest lines you generated above under module.exports = {. You should end up with something like:

    dest: '/var/www/html/some-new-directory',
    base: '/some-new-directory/',

Save & Exit (in nano, Ctrl+x, press y to confirm, enter to confirm name).

The dest can be a relative path as well (you need an extra ../ since it is relative to src, or to whatever directory from which you are running).

If it is a sub-sub directory of some kind, you must have the same number of slashes in base, plus one at the end, as are after /var/www/html in dest. For example, if you want /var/www/html/sub/subsub, you should set PUBLIC_HTML to /var/www/html/sub for the purposes of these instructions, or change the commands.

For that to work, you must serve /var/www/html or whatever directory PUBLIC_HTML is as your main website address. The purpose of base is to handle the slashes.

Get the required packages to run the site:

# YARN:
cd $WWW_HOME/$MAINSITE-meta/$SUBDIR-meta && yarn install
# NPM:
# cd $WWW_HOME/$MAINSITE-meta/$SUBDIR-meta && npm install

Create a build script for later (running the script will generate the html files):

cat > $WWW_HOME/$MAINSITE-meta/$SUBDIR-meta/export-html.sh << END
#!/bin/sh
cd src || exit 1
../node_modules/.bin/vuepress build
# ^ You must run vuepress directly! If you use yarn, it will cd to the
#   root of the repo which is wrong (You'll get bad absolute links
#   including to css; it won't use your .vuepress/config.js at all).
END
chmod +x $WWW_HOME/$MAINSITE-meta/$SUBDIR-meta/export-html.sh

Now, whenever you run ./export-html.sh (or cd src && ../node_modules/.bin/vuepress build), you will end up with a static website in the proper place!

If everything went well and you created a site from scratch using this article, this is what you’ll see if you visit /some-new-directory on your website:

Example of working website: https://expertmultimedia.com/usingpython/

Example of matching git repo: https://github.com/poikilos/usingpython-website (has instructions for using GitHub to edit the website)

You can repeat these instructions and create as many VuePress instances as you want under your main site.

Other static site generators

I chose a static site generator not a flat file CMS for performance on my various sub sites on my main website. I don’t see the need to place load on the server for every side venture. Along the same lines, I used the following criteria:

  • *(Asterisks below refer to this, not to a general dislike for a particular technology) I like to stick to only a few technologies on one machine for brevity (to reduce storage space and system updates). For example, I don’t want to install Java or Ruby unless there is clearly superior software doing something I want. I already use Node.js and PHP for other parts of my server, therefore, I prefer a PHP or Node.js solution.
  • A two-tier navigation system is a priority for me, so many “flat file” or “Markdown” generators didn’t apply. Nested navigation is clean and responsive even with the scaffold vuepress provides via yarn create vuepress-site! Even generators like Couscous are therefore not applicable to a real website, since documentation generators may have chapters and pages but no CMS-like tier structure. Many Markdown (& other similar) html generators are oriented around documentation.
  • It has to be well-maintained: No matter how great it is, I don’t really want to use one that hasn’t been maintained for years. It should exist within an ecosystem of technologies that can be easily added and updated.
  • It must generate static html files, not just “static” html pages, without intense meddling. Some articles list Grav but it apparently serves the “static” files dynamically. Other articles errantly other CMSs that serve flat files. A list of flat file CMSs ruled out is at the CMS critic flat file CMS list which includes Flextype.

The above requirements ruled out many smaller, older, or bloated projects such as listed at CMS critic’s article, static site generators at Jamstack, the unfinished blog series by Borsatto myles/awesome-static-generators, ziadoz/awesome-php or vinta/awesome-python.

However, its worth mentioning there are some other powerful site generators (generate static html files, not just use flat files). My reason for not using these is after the colon:

  • Lektor – why not for me: It uses a proprietary template language.
  • Orchid – why not for me: It is in Java+Kotlin*, and it uses many technologies which are site generators in and of themselves seemingly causing bloat. Even with all that, it is oriented around documentation generation.
  • nanoc – (Pros: It does allow flat files suitable for version control like VuePress does) why not for me: It uses Ruby*.
  • gollum – why not for me: It uses Ruby*, and it is wiki-oriented– two-tier navigation seems difficult to implement. Also, it seems to require an unmaintained third-party tool for static generation: gollum-site (See also “github local wiki…” on StackOverflow, which has no solution shown there or on the GitHub Issue)
  • Netlify CMS – why not for me: The guides seem to be oriented around using their web service (Netlify, as opposed to the open source Netlify CMS)–I couldn’t tell if there was a good way to do what I’m doing here, & the example site seems difficult to customize as a two-tier navigation system.
  • Pelican – why not for me: This is the first one that really interested me since it uses Python, but it seems to primarily use Jinja2 for templating. Jinja is similar to ejs but more oddly “similar to Python syntax” and it compiles to Python. I want to write flat files and host them in git, and have a separate html folder the files generate. Most people want to use it for GitHub Pages, so finding documentation regarding my needs seemed to be diluted. Does the generated Python generate plain html files, and can the root be a subdirectory?
  • Tina.io – why not for me: requires a React framework such as Next.js or Gatsby, so that is just too much for small sub-pages of my site that I want to be highly optimized and negligible to maintain.
  • 11ty – why not for me: It seems to be able to what I want with heavy tweaking, and it has odd syntax such as by using Nunjucks do to something JSX-like but not quite the same.

The nanoc website kindly links to a list of static site generators here: https://staticsitegenerators.net/

Aside from the issues above, they all seem to generate html files that a regular html server can host. Whether they can generate static documentation (offline html) with relative URLs, which VuePress cannot, is unclear, but I don’t need offline files.

If any well-maintained option is missing or my understanding of any of the listed well-maintained CMS or static site generators is incorrect or you have more information about any of them, please leave a comment below.


Posted

in

by