I’ve migrated my blog infrastructure from Nikola + reStructuredText to Sapper + Asciidoctor.

Why leaving Nikola

This blog was powered by Nikola since 2014, when JAMStack term didn’t even exist. It really did the job, it has out-of-the-box features that I needed:

  • Simple multi-lang support.

  • reStructuredText which I prefer over Markdown.

  • It was developed in Python, a programming language that I know and I enjoy.

But, I faced some issues which annoyed me for quite some time:

  • My config file got bloated.

  • It was not easy to modify or create templates, neither to find templates I liked.

  • Site performance was not awesome, my last Lighthouse audits report was around 80, depending on the section. Except SEO section, which was 96.

Why Sapper

DX: Developer experience

I already used Sapper for creating some tiny PWAs [1], here 2 examples I developed to learn Svelte and Sapper:

Currency Exchage Loss Calculator

It is a helpful application for travelers visiting currency exchange houses. Based on the rate they offer and the money you want ot change it calculates the amount of money that you are losing in that transaction.

COVID-19 Stats

It shows COVID-19 statistics by country and date.

While creating those PWAs, developing experience with Sapper&Svelte was quite impressive.

UX: Final result of blogs in Sapper

Lately I’ve stumbled upon with some blogs using Sapper like the own Sapper blog, Coding with Jessie or swyx.io. Check those blogs by yourself, IMHO user experience is pretty good in all of them.


I don’t have a strong opinion about using Asciidoctor or reStructuredText, I am comfortable with both of them, but there is more support for Asciidoctor in other programming languages, like JavaScript. So basically I switched to Asciidoctor because I didn’t find a JavaScript library able to properly convert reStructuredText to HTML.

The main issue I found not using Markdown was the lack of Rollup plugins to convert Asciidoctor to HTML, so I just created one, rollup-plugin-asciidoc. Implementation was quite simple, the plugin is just using Asciidoctor.js JavaScript library to convert the Asciidoctor text input to HTML.

rollup-plugin-asciidoc to the rescue

With rollup-plugin-asciidoc we can import Asciidoctor files in our blog and Rollup will convert them to HTML.


= Post title
:date: 2019-11-11

Such a post!
With rollup-plugin-asciidoc we can import Asciidoctor files one by one.
import doc from './a-blog-post.adoc';

  meta: {
    title: "Post title",
    date: "2019-11-11"
  html: "<p>Such a post!</p>"

Importing files one by one is not really convenient for a blog where we have many files which we don’t want to import manually.

rollup-plugin-glob to the rescue

With rollup-plugin-glob we can import all the files in a directory by extension, so now we have our index of posts automatically converted to HTML.

import allAdoc from '../posts/**/*.adoc';

allAdoc.forEach(post => console.log(post));
Output: List of posts already converted to HTML
{ meta: { title: "Post title", date: "2019-11-11" },
  html: "<p>Post 1.</p>"
{ meta: { title: "Post title", date: "2020-02-22" },
  html: "<h2>Title post</h2><p>This is a sample post...</p>"
// ...

Syntax highlighting

My blog is mainly about Software Engineering, so I have a strong requirement, code highlighting.

At the begging I started using highlightjs library from a CDN for source code highlighting. We use imported library to parse the source code and for styling the result we need to import also a CSS file.

Later I realized that we can do the parsing work when we compile Asciidoctor to HTML in rollup-plugin-asciidoc implementation, so we don’t have to download the JavaScript file.

Doing code highlighting transformation during the site building phase we are improving application performance and reducing bundle size.

Code highlighting transformation during site build step bring 2 great benefits:

  • Reducing bundle size: we don’t need the 27KB of highlightjs javascript library.

  • Improving performance: source code parsing is done only once while site is built.


I still have some work to do, but so far I have a blog with following features:

  • Automated generation of Sitemap and RSS feed.

  • Multi-language support (I still have to translate some texts).

  • Syntax highlighting.

  • 100 score in Lighthouse audits.

lighthouse score


I am a big fan of automated testing, I don’t love writing them, but I think they are the best way to know if your software is behaving as expected.

Sapper template brings a pre-configured simple end to end test. It is using Cypress, an E2E [2] testing framework which has a nicer developer experience than Selenium, although I think it is still far of being the Selenium Killer.

Not yet a Selenium killer: The main reason is that Cypress lacks of some features you might need, depending on the project, like cross-browser and cross-platform testing offered by Selenium grid.

I’ve added some tests for this blog (and I plan to add more), I’ve tested that post header information is correct, that redirection logic is working, main navigation works and metadata is correct, in a couple of hour and including bugfixes! That’s why I love writing tests with Cypress, the productivity.

Writing a test

It works pretty much as Selenium. . Opens a page . Access to an element using HTML identifier (#element-id), XPath (/a[@title='link title']) or CSS selector (div > a.cssClass). . Interact with selected element on the page. . Validate expectations.

You can find this blog tests at cypress/integration folder.

Simple test example
it('Header', () => {
  cy.visit('/posts/this-is-sapper/en') (1)
  cy.get('.subtitle .date').contains('28/08/2020') (2)
  1. It navigates to the post absolute path

  2. It checks that date is correct in subtitle


Finally Svelte support for Typescript is completed and after quite few some work, I’ve migrated this Blog supported by Sapper to Typescript also. Feel free to use it as template or example, there are still some caveats, like clarify wether keep using eslint or svelte-check or both.

1. Progressive Web Application
2. End to end