When I migrated all of my old posts to Jekyll, my build time skyrocketed. The average time was 39 seconds for 853 posts. Abysmal. This is especially annoying when I’m actively working on the site with jekyll serve
and waiting 40 seconds for changes to reflect.
The first step to figuring out what takes so long is running the Liquid profiler: $ jekyll build --profile
This is what it output for me:
Filename | Count | Bytes | Time
--------------------------------------------------------+-------+----------+------
_layouts/default.html | 853 | 9652.42K | 7.462
search/feed.json | 1 | 1394.16K | 4.148
_includes/header.html | 853 | 1230.35K | 3.485
_layouts/post.html | 755 | 5119.00K | 2.301
_includes/opengraph.html | 853 | 870.67K | 1.694
_includes/icons.html | 810 | 1752.89K | 1.454
sitemap.xml | 1 | 203.34K | 1.321
_includes/footer.html | 853 | 109.12K | 0.273
til/posts.yml | 1 | 14.27K | 0.147
about.md | 1 | 18.02K | 0.085
_posts/2016-07-04-posts-heatmap-calendar.md | 1 | 16.78K | 0.080
isotope.html | 1 | 389.52K | 0.048
_layouts/book.html | 34 | 198.47K | 0.039
til.md | 1 | 58.76K | 0.023
feed.xml | 1 | 139.11K | 0.022
... with a dozen more tiny blog index pages from paginator
done in 39.359 seconds.
I tried a bunch of random stuff that made tiny improvements before I started doing research: Removing extraneous files, cleaning up if blocks that I no longer used, hardcoding things in the header that relied on variables from _config.yml
, etc. None of this made significant improvements. I had already moved extraneous plugins long ago and I keep Jekyll up to date, so there was nothing more to do there.
I eventually came across this post by Mike Neumegen, which mentioned jekyll-include-cache, which caches the files you tell it to with the first instance and then serves the cached versions up for subsequent requests. For includes that don’t change based on the page content, you can replace {% include example.html %}
with {% include_cached example.html %}
. When I did this with my sidebar, nav bar, and footer, it cut my average first build time to 28 seconds, with subsequent regeneration times around 19 seconds. Awesome! (Neumegen’s post, which mentioned jekyll-include-cache contains a lot of other helpful advice for Jekyll build optimization. Check it out!)
My next step was to look at other includes to see if I could shave more time off. Some files had a mix of static and dynamic assets, so I split out all of the static assets into more include_cached
includes. This cut another 5-6 seconds off of my build time. Boom.
Now that my layouts and my includes were as optimized as I could get them without major rewrites, I turned to the next major time hog: search/feed.json
. 4 seconds for a single file! This file loops through everything on the site and dumps it into a single file so that I can crawl it with Javascript to power search on my site.
I used Mat Hayward’s search project with a few tweaks. As I dug deeper on the specific functions in the feed.json generator, I noticed that everything was being converted from Markdown to HTML first, then HTML stripped out, then turned into JSON. I wondered if skipping the markdown conversion step would lead to inaccurate search, so I tried it. Not only did my search function the same, but it dropped feed.json
’s build time down to around a sixth of a second.
My build time now looks like this:
Filename | Count | Bytes | Time
--------------------------------------------------------+-------+----------+------
_layouts/default.html | 853 | 9564.71K | 2.922
_includes/head.html | 853 | 1571.79K | 2.623
_includes/opengraph.html | 853 | 870.67K | 2.171
sitemap.xml | 1 | 203.34K | 1.177
_layouts/post.html | 755 | 5195.80K | 0.509
search/feed.json | 1 | 1394.65K | 0.176
_posts/2016-07-04-posts-heatmap-calendar.md | 1 | 16.78K | 0.091
about.md | 1 | 18.02K | 0.076
isotope.html | 1 | 389.52K | 0.050
_layouts/book.html | 34 | 198.47K | 0.030
_includes/header.html | 1 | 1.43K | 0.021
til.md | 1 | 58.76K | 0.019
feed.xml | 1 | 139.24K | 0.018
... with a dozen more tiny blog index pages from paginator
done in 19.594 seconds.
Regeneration times when running jekyll serve
are hovering around 13 seconds now, which is a significant quality of life improvement for me when managing this blog.
There is probably more work I can do with moving the highlighter to the front end with JS and simplifying the Liquid in head.html
and opengraph.html
, but I’m stopping here for now.