Building a documentation website

By Artem Avetisyan on June 07, 20195 min read

So you want a documentation website for your project. You already have a good project README.md, but turning it into website seems like a big and intimidating task. Probably also because building a website is sufficiently different from whatever you are doing on your project. Even - somewhat bizarrely - if your project is related to building websites!

Anyway, like it often happens with things that seem big and intimidating, it turns out to be not that bad when you get right down to it.

Building documentation websites is no exception. All it takes is a few good tools put together.

Basic website

The crown jewel in our toolset is Docsify. Docsify turns a bunch of markdown files into a beautiful website literally in minutes. There is code highlighting, full text search, themes and more. There is even a codefund plugin to make you rich. But, above all, it just works and it’s easy to use. Follow their Quick Start to get going.

This implies you’ll need to chop the README into some sensible bits. By the end of this exercise, you’ll likely end up with a /docs folder containing Docsify’s index.html. It’s a static website, serve it locally to test. For example, with serve:

serve ./docs

Publishing

There are many ways to host a static website. By far the simplest option is to use GitHub Pages. This way it’s all within GitHub - no need for external tools, accounts, integrations, etc.

In the repository setting, there is a GitHub Pages section where you need to choose “master branch /docs folder” option and that’s all it takes for https://<yourname>.github.io/<yourproject> to get published. Further changes do the /docs on master branch will be automatically published too.

Runnable examples

So far our website looks pretty good. But, let’s face it, it’s little more than a glorified github readme. There’s got to be something more than to justify us pushing users to click an extra link and familiarize themselves with a new ui.

What that killer feature could be depends on the nature of the project. But if it has anything to do with javascript, then embedding runnable code examples definitely brings the documentation experience onto the whole new level.

Docsify comes with a plugin for embedding jsfiddle react/vue snippets. But what about other tech? And what if you need more than just snippets?

codesandbox is a great online development environment that can also be embedded in an iframe. It’s a perfect fit for showcasing small example projects along side the docs.

Create a sandbox project, make it work, and then copy an iframe code from the sharing options and paste it into the your markdown. It’ll appear on the docsify website in all its glory.

This approach works great for a couple of examples, but it does not scale very well. As the number of examples grows, it’ll get harder to keep them all in check and make sure they keep working.

One well established way to keep code from such deterioration is to use version control. So we should house all example code in the same repo along side the docs. Luckily, codesandbox allows to download the entire project so this gives us a starting point.

Next step is naturally to make sure codesandbox is using that code for the embedded projects. And - you guessed it - there is a tool for that too: codesandbox-example-links

Instead of pasting iframe code in the markdown, we put special markdown links that point to the example project location within the repo. Then codesandbox-example-links cli turns those links into actual iframes. For example, given a file /docs/doc.md:

## Demo

[codesandbox](/docs/examples/demo)

Running:

npx codesandbox-example-links --iframe --output-dir=./dist ./docs/*.md

Will generate this /dist/doc.md:

## Demo

<iframe src="https://codesandbox.io/api/v1/sandboxes/define?embed=1&parameters=N4IgZglgNgpgziAXKAdAIwIZplATgYyVHwHsA7AFxkqRGAB0yACJ-kAB13hgrjcSYBtRixZtqANzYAaEaLYALAJ7sYuACYkAtmzkBdRgF8Qh6SAhl1MAB4oFFLVCIhSlahVoAeAIQARAPIAwgAqAJoACgCiTPaOAHyMnrFQCcxMSTAY6qmi6RQQFLBx4RgEOEwAyhiWaCTWngD0-YUwOaKeWjwYTPgKpXA8ALxsAKrBAGIAtAAcbEwNbY0KmdmMcp616kptLJ7qEBJMEOrDIBjs7GxxjfsSqXK7cPi4EOwUTHAEp5_4DWi4JAA7gNcA0LFZbAArPgga4NJ4vN6LP4kLapJYOFKMExmdgYfAAawwAHMYChoeRnK4qDREHQ5GwyBhOvxWCBlKoNNpJqSKJM4BRSlR1JMLAUZAyQBI1HAIORWWwAIwoAAMqolaTYVgRr3y8qQbIAEio1JotExfDAtCQmAApCoasQgLQYCwK8yWGx2TGOtk6t4wgQMNJOgVC914spQI6e2zJJiTSYkVRkX1OtAAV2g6gjpXw5Uz2ZjEO9jl0aVMkqsKasZHwEHgrODuTYmGwUEmnG4fI5pu07qgGCoArTbN7XJ0BrYg-HFHLLErmpAVgklpr1HrjYNzfkHDzOEmmcssFwA6H8DnIDki6dBJgSkBJA0gaEBjIhmxphAPz-AOBagac52HJOBrCpcgaQ8OlXAFGITQnJhBiYLgAEcsy4AAKRR4LNNgAEoAG5GBg95gGWKAoBIQxEOQmA0IgTC2BQeEKCUWA4BQfA4BhQi1jIa11AzWAUBsdgn14Gj8EHbimAAQQuJgdy4T1cCYDC8MUh5aIoDNcGYJJFR6aS4EGMicEoww4kNcybTAAFzWNTkzW8JZFTaD930_Mwf3-IEQTBWMQPAtxaRcchYPHM0aNQ9CYCw9kcP7EBeLrcL3nk9horo2L4uYoD8KI1MyEi7QUCA6h1AwzR8AzTpKHQVElGkJgyBgQE5IudSUpxb8CF_PyAIFNj4E47jgsg2hEGJSjMCgDDNiUDSdzACDJjAZloCUAQ2GsqBpXyfBugAORgDMYBkOSXgwKBmrgao4H5NQIDAQqPMYOwbM0tIqGsPkKFwe6VtwLQBAzC41EOgZXs_QxDCAA" style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;" sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin"></iframe>

And while we’re at it, there is also another magic link that inserts regular markdown code snippets from example projects. This is to spare copy/pasting bits of example projects into the markdown. For example

## Demo

[view-code](/docs/examples/demo/file.js#L3-L5)

will be produce:

## Demo

```js
  // line 3 of file.js
  // line 4 of file.js
  // line 5 of file.js
```

This is pretty exciting, but we’ve just introduced a build step. Github Pages does not have a way to run it for us (unless we add Jekyll into the mix). We could keep both source of and built markdown in the repo. It would only be a matter of time, however, until someone starts editing the built markdown instead of the source one and then it’s all messed up. So let’s not do that.

In addition to “master branch docs folder”, Github Pages has a couple of other options to look for the source of your website. One of them is “gh-pages branch”. Whatever is in the root of gh-pages branch, is going to be served. All we need to do is to run the build step and push the result into the root of gh-pages branch. And - you guessed it again - there is a tool that’s doing just that: gh-pages

For example, the following command will push the contents of ./dist into the root of gh-pages branch:

npx gh-pages -t -d ./dist

-t option tells gh-pages to also include dot files. In our case there is an important one not to miss - .nojekyll. It tells Github Pages to skip Jekyll build.

You could have something like this in the package.json scripts to automate build and publish steps (assuming both codesandbox-example-links and gh-pages are added as dependencies):

    "build-website": "rm -rf ./dist && cp -r ./docs ./dist && codesandbox-example-links --iframe --output-dir=./dist ./docs/*.md",
    "publish-website": "npm run build-website && gh-pages -t -d ./dist"

And that’s all there is to it. We have a beautiful website with runnable examples.

The approach outlined in this post was used to create https://hyperdom.org. Check out github sources for further details.