“Surely I’m overthinking this,” I kept saying to myself, impatiently waiting for a solution to appear before my eyes. I learned that it’s tough to create versioned content with a static site generator, while making sure that the source control system does the version tracking and scripts do the build automation and your theme doesn’t lose its mind.
I needed to find the best way to move our current non-versioned content in a Jekyll site to give us output that showed users versioned sites. This is an age-old problem in the technical writing world, solved over and over again. I couldn’t quite get there on my own. Eventually I consulted with my former co-worker Carolyn Van Slyck, to help me talk through solutions. She was able to show me how to copy source files at build time without checking them into Git. She also provided a valuable sounding board for “am I the crazy one?” Much gratitude to Carolyn for the help with my test repository at versions-jekyll!
Complexities
I found that there are a couple of key places where the complexity arises, when using a static site generator. The first layer is within the source. When using source control system, I shouldn’t need multiple copies of content files in the repo itself. The second layer is in the output. When using static sites, I should build to a specific folder name, with the version value, each time a release occurs.
Other complexities may arise as you analyze, such as needing versioned translated content, or making sure that only certain users have access to certain releases or must login before accessing the site.
In the output I’ll still need to make sure my links work, my CSS and JS files are properly linked. I shouldn’t have to maintain copies of actual output files in my source repo because I do not want to figure out merge conflicts in HTML.
So with those general guidelines in mind, I set out. I wanted to see if what we do in OpenStack with versions using Sphinx, and what Read The Docs does with versions using Sphinx, could translate to versions and Jekyll. What I learned is that it can, but not exactly in the way I was thinking about it.
Setting a version value in the source
With Jekyll, you can get access to values by setting them in a _config.yml
file, which is used to build the site each time. You can also set values in a specific config file used only for that version, and build with both the “base” _config.yml file and the versioned config file. You can also have a theme that uses a metadata value called “permalink” that sets the output folders that make up the URL for the output file. All these configuration options could be used to set the version value. Testing each option took some time.
Outputting version values into directories
With a Jekyll theme like Minimal Mistakes, you cannot build directly to the gh-pages branch, because it uses gems that are not supported by GitHub Pages. Instead, the build script in the versions-jekyll repo builds locally and copies the files to the gh-pages branch for publishing. So, the script needs to build the versions available to it locally. Right now, a “for” loop in bash does this work, so the script reads in version values.
An approach I learned was that you could pass in more than one _config.yml
file at build time. With the correct configuration, Jekyll builds all the relative links to CSS and JS files needed for the theme. I did have several failed attempts while I played with permalinks, baseurl, and so on. I found this post, Configuring Jekyll for User and Project GitHub Pages with reference tables super helpful.
As versions are added, you’d need to update the script with the values, or you could read in a versions list from a file. Here’s an example for three version values to be outputted into three folders, example.com/4.1/, example.com/4.2/, and example.com/4.3/:
for v in 4.1 4.2 4.3 do # Checkout a versioned branch with the version name git checkout $v # Create a configuration file that sets a new baseurl based on version echo "baseurl : /$v" > _config.$v.yml # Build using both the basic configuration file and the version config file bundle exec jekyll build --config _config.yml,_config.$v.yml -d _site/$v/ done
Explanations
The version control of source is done with git through stable branches or tags. You can debate a bit about which is “better” but I believe stable branches make more sense than tags so that you can later backport any fixes to a branch sensibly.
The versions of the output are not semantically meaningful, and can have silly names if needed. Output versions can also skip a release, so the version value is treated like a string and not an integer.
The settings in the _config.yml sets the destination folder at build time for the versions. I’ve tested using both “collection” settings and “baseurl” settings and found “baseurl” to be the simplest for our theme. Your experience and theme settings may push you one way or another.
The output goes to GitHub Pages, in separate folders per release, so that the URL also reflects the version number you read on a given web page. In our case, we build locally, then push to the gh-pages
branch from that local build.
The master branch always reflects the current, or latest, version of the docs site. I’ve chosen to use /latest/ in the output URL but you may have reasons for naming it in another way. For example, you may not want to always publish master to a public site, and instead need to publish from a stable version only.
Minimal Mistakes is the theme in use, for us, which supports collections. The permalinks are encoded in the markdown files themselves, but those are not in use when versioning docs when using the baseurl approach, so the collections remain intact and unchanged for our solution.
Design decisions
On the front-end, you’d still need to design for scenarios such as “What if the page I want to access on version 4.1.1 does not exist in version 4.1.1? Do you give a 404 error or a special “not found” page when navigating versions? What about an archived version, can someone still find it and browse it?
Also, if your product provides n.n.n release versions, but really the docs site only changes drastically at n.n release versions, you could version the source files at n.n.n but only output content at n.n releases. This design decision would make the docs simpler to navigate, but may cause confusion if a user looks for the site versioned at n.n.n.
I’ve also not discussed the problem of search scope here. The design decision here is whether to enable searching across all releases? Or scope the search to only one release at a time?
Testing
Testing different solutions took weeks of time. I wanted to have confidence in the maintainability and design of our solution. I also had several weeks of diving into complexity before resurfacing and seeing the baseurl solution was in front of me the whole time.
So when I went to try out the “fake” install guide in the versions-jekyll repo, I built the site, went to the site url, and entered a version number and install-guide/ in the browser URL. But, there’s no such file as an index.html file in /install-guide/ because the only files are named introduction.md and deployment.md. So I add an index.md, but in fact, Jekyll won’t give me a URL like site.com/current/install-guide/index.html, even when I put it in the permalink metadata to be /install-guide/ in an /install-guide/index.md file. Why not? I guess it’s because Jekyll wants to ensure the permalink settings are for the same depth across the site? This confusion caused me to look for an even simpler solution.
Summary
For our Jekyll docs, changing the baseurl for the entire site as a version value became the simplest solution. I investigated collections for versions quite deeply, but wasn’t happy with needing to change so many permalinks each build, and the risk of broken links seemed higher. Fortunately, our layers of complexity are minimized, in that we only have a few deliverables, our site’s content is not translated, and we do not need to require logins on the content.