Improving the User Experience of Remote Documentation
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:
-
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.
-
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.
-
Off-by-one bugs
Solution
We did the conversion in the following steps:
-
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.
-
Converting templates from
ejs
toliquid
Having data hardcoded and exported in
context.exports
, we converted all theejs
templates into Liquid. This was pretty easy as the syntax is almost the same. A couplefor
loops,variables
,andif
conditions, and the template was converted. After conversion, we removedejs
templates. -
Using the
download_file
filter instead of AJAX in the browserInstead 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 %}
-
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.
Results
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 code is faster than any code. 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 built-in features of platformOS.
Read more
Author information
Paweł Kowalski
Frontend Developer and Performance Advocate, platformOS