Improving the User Experience of Remote Documentation

Last edit: Jul 08, 2019
  • Contributors:
  • pavelloz

Before we started implementing our new design, we decided that it would be a good opportunity to refactor some of our hard to maintain code to serve our users' needs better.

Our documentation site is a combination of pages written in Markdown and generated, structured JSON files describing various APIs, for example, Liquid Filters. We do it this way mostly to be absolutely sure that our documentation is always up-to-date with the codebase. It is automatically generated and uploaded to our Content Delivery Network on every deploy.

Problem/situation

Our first implementation was based on client-side rendering, fetching a JSON file with the data and injecting it into a template written in ejs as a callback. For a JavaScript solution, it was very light and very fast, but it had some disadvantages:

  • Content was not searchable, because searchable: true in platformOS pre-renders pages and the JavaScript versions of those pages were empty.

  • It was rendered in the browser after the JSON file has been downloaded, so it would always be slower than content rendered on the server-side.

  • Both templates and the system for it required maintenance in JavaScript.

  • Because content was rendered client-side, JavaScript was necessary to support deep linking. Side effects of those complementary scripts were causing issues to some users.

Challenges

As the old saying goes, there are only two challenges in programming:

  1. Cache invalidation

    Knowing when to invalidate the cache is the key to a good cache system. Fortunately, we had something prepared to make this very easy which we will explain in the Solution section.

  2. Naming things

    Keeping naming/nomenclature consistent between the API (autogenerated JSON file) and the consumer (templates) proved to be harder than anticipated, so not everything is 1:1, as the code is living in two separate projects.

  3. Off-by-one bugs

Solution

We did the conversion in the following steps:

  1. Hardcoding all the JSON files in Liquid

    This allowed us to not worry about downloading/caching them at the beginning of the process — we could focus on rendering.

  2. Converting templates from ejs to liquid

    Having data hardcoded and exported in context.exports, we converted all the ejs templates into Liquid. This was pretty easy as the syntax is almost the same. A couple for loops,variables,and if conditions, and the template was converted. After conversion, we removed ejs templates.

  3. Using the download_file filter instead of AJAX in the browser

    Instead of using API call notifications, we used the download_file Liquid filter. As the name suggests, it is downloading the file asynchronously (in a background job) and assigns its content to a variable when it is ready. This concept is very similar to promises.

    {% assign data = url | download_file | parse_json %}
    
  4. Invalidating the cache using context.version

    Each platform release can contain updates to our autogenerated documentation, so every time a platform version is changing (it is basically an SHA of a git commit currently deployed), it is updating the cache in the background, at the same time showing old results for the first person that comes into that page after the deploy. To create one cache per page, we added a prefix. This cache is keeping the rendered HTML and pulls new JSON data every time context.version changes.

    
    {% assign key = context.params.slug3 | append: context.version %}
    {% cache key %}
    ...
    {% endcache %}
    
    

    After setting up the cache, we could safely remove hardcoded data and rely only on downloaded and cached JSON.

  5. Enabling search

    Our system can pre-render a Liquid page if it is not dynamic, by using the searchable: true directive. This means that the page will be rendered as soon as it is modified (usually deploy or sync) and will be returned straight from the cache. Additionally, its content is indexed and easily accessible by the admin_pages query.

Results

Content is searchable

Every filter, tag, GraphQL operation can now be discovered using on-page search. This fixed an often-reported issue with the previous implementation.

Better user experience

The browser receives the already rendered HTML, so there is never a situation with no content on the page. The JavaScript only has to highlight code snippets (asynchronously) and generate the table of contents (made specifically not to move around content on the page when it happens - no layout recalculation needed).

Less JavaScript

This was not a big problem, but no JavaScript is always faster than any JavaScript. It means less maintenance, fewer assets to send over the wire and less of the user's CPU cycles wasted on tasks that can be done (and cached) server-side.

Templates are easier to understand and edit by others

Code is much simpler and has no external dependency (ejs) which means it is done completely using native features of platformOS.

Read more

Author information

Paweł Kowalski
Frontend Developer and Performance Advocate, platformOS

Questions?

We are always happy to help with any questions you may have.