This site is a work-in-progress. Some of the information is incomplete and may not work as described. See the homepage for details.

The first thing to think about when trying to improve Jekyll build times is, are we talking about improving build times during development or deployment? During development we can take out plugins or reduce the amount of posts Jekyll processes or a few other things. During a production build, we likely can’t do these things. With that, it is best to start improving things for production and, once we’ve done all that can be done there, improve things for development.

Benchmarking Your Build Time

To know where we’re starting, you use the --profile flag.

bundle exec jekyll build --profile

The --profile flag will show a list of what Jekyll is doing and how long it is taking to do each thing in the list1. Here is an example.

$ bundle exec jekyll build --profile
Configuration file: ~/project/_config.yml
            Source: ~/project
       Destination: ~/project/_site
 Incremental build: disabled. Enable with --incremental
      Generating...

Build Process Summary:

| PHASE      |   TIME |
+------------+--------+
| RESET      | 0.0001 |
| READ       | 0.1088 |
| GENERATE   | 0.0036 |
| RENDER     | 8.7762 |
| CLEANUP    | 0.0072 |
| WRITE      | 0.0785 |
+------------+--------+
| TOTAL TIME | 8.9744 |


Site Render Stats:

| Filename                        | Count |     Bytes |   Time |
+---------------------------------+-------+-----------+--------+
| _includes/breadcrumbs.html      |   155 |   131.38K |  5.652 |
| _includes/crumb.html            |   554 |    14.63K |  5.472 |
| _layouts/guide.html             |    70 |   867.58K |  3.321 |
| _layouts/documentation.html     |    82 |  1478.93K |  2.633 |
| _layouts/default.html           |   166 | 10508.85K |  1.062 |
| sitemap.xml                     |     1 |    26.86K |  0.678 |
| _includes/header.html           |   166 |  6159.49K |  0.636 |
| _includes/banner.html           |   166 |   621.22K |  0.308 |
| _includes/language-switch.html  |   166 |   161.94K |  0.252 |
| _includes/sidenav.html          |   147 |  1124.43K |  0.200 |
| _includes/doc-sidenav.html      |    77 |   851.72K |  0.125 |
| _includes/head.html             |   166 |   372.48K |  0.056 |
| _includes/lang_text.html        |  2539 |   132.90K |  0.043 |
| _includes/guides-sidenav.html   |    70 |   244.86K |  0.039 |
| providers/es/planning.md        |     1 |     7.53K |  0.019 |
| _includes/touchpoints.html      |   163 |    51.98K |  0.014 |
| _includes/search-form.html      |   166 |    53.56K |  0.013 |
| providers/planning.md           |     1 |     6.22K |  0.011 |
| _includes/footer.html           |   166 |   578.55K |  0.009 |
| _includes/alert.html            |    78 |    61.62K |  0.009 |
| _layouts/research.html          |     1 |   132.60K |  0.009 |
...
+---------------------------------+-------+-----------+--------+
| TOTAL (for 50 files)            |  5767 | 23914.10K | 20.699 |

                    done in 8.991 seconds.

The biggest area for improving the example above is in the breadcrumbs and crumbs includes. This particular site is running a 7-level nested loop to build their breadcrumbs. That’s a for loop inside a for loop inside a for loop … et cetera. Refactoring should always be your first step. In my experiance Liquid’s most computationally expensive tag is its for iterator, so we should start with those. But let’s assume this nested for loop situation is the best it can be, meaning it has been refactored already. The next biggest improvment is likely to come from caching.

Cache Your Includes

Continuing with the breadcrumbs example above, by caching the crumbs include I can drop number of times that include is called from 554 to 146 which will drop the build time from 9 seconds to 6 seconds.

Using Ben Balter’s plugin, you can use

{% include_cached breadcrumbs.html %}

instead of

{% include breadcrumbs.html %}

The profile output now shows we’ve decreased our build time by 33%.

$ bundle exec jekyll build --profile
Configuration file: ~/projects/_config.yml
            Source: ~/projects
       Destination: ~/projects/_site
 Incremental build: disabled. Enable with --incremental
      Generating...

Build Process Summary:

| PHASE      |   TIME |
+------------+--------+
| RESET      | 0.0001 |
| READ       | 0.1044 |
| GENERATE   | 0.0036 |
| RENDER     | 5.7150 |
| CLEANUP    | 0.0117 |
| WRITE      | 0.0695 |
+------------+--------+
| TOTAL TIME | 5.9043 |


Site Render Stats:

| Filename                    | Count |     Bytes |  Time |
+-----------------------------+-------+-----------+-------+
| _includes/breadcrumbs.html  |   155 |   131.53K | 1.816 |
| _includes/crumb.html        |   146 |     5.66K | 1.658 |
...
+-----------------------------+-------+-----------+-------+
| TOTAL (for 50 files)        |  5040 | 23906.76K | 9.734 |

                    done in 5.916 seconds.

Footnotes

  1. The --profile flag has inconsistant time output between the Build Process Summary and the Site Render Stats as of November 2021. An issue is open with the core Jekyll team, but the data is still usesful to us in improving our site build time.