Guides
Benchmarks
How fast Kiln renders real pages, compared with other JavaScript template engines.
We render the same pages with Kiln and 7 other JavaScript template engines, and time how long each engine takes to render each page. Each page is something a real site renders, from a blog post to an admin table. Together they cover the template features sites use most. We didn't pick them because Kiln does well on them.
Longer bars are faster, and the fastest engine is listed first. The last run was on 17 October 2026, with Node 24.22.0 on Apple M4 Pro. Read how the benchmark works before you compare engines.
Blog post
A full article page: a layout with blocks, header and footer partials, trusted body HTML, related posts and twenty escaped comments.
Uses: Layouts, Partials, Loops, Conditionals, Escaping, Dates
| Engine | Renders per second | Time per render |
|---|---|---|
| Kiln 1.0.0 |
113,374
|
0.00882 ms |
| Pug 3.0.4 |
55,087
|
0.0182 ms |
| Eta 4.6.0 |
50,198
|
0.0199 ms |
| Handlebars 4.7.9 |
27,818
|
0.0359 ms |
| Nunjucks 3.2.4 |
25,768
|
0.0388 ms |
| EJS 6.0.1 |
20,120
|
0.0497 ms |
| Twing 7.3.1 |
3,771
|
0.265 ms |
| LiquidJS 10.29.0 |
1,281
|
0.78 ms |
CMS landing page
A page built from ten content sections, where each section's type picks the partial that renders it.
Uses: Dynamic partials, Nested loops, Conditionals, Prices
| Engine | Renders per second | Time per render |
|---|---|---|
| Pug 3.0.4 |
197,876
|
0.00505 ms |
| Kiln 1.0.0 |
195,451
|
0.00512 ms |
| Eta 4.6.0 |
166,192
|
0.00602 ms |
| Handlebars 4.7.9 |
34,054
|
0.0294 ms |
| Nunjucks 3.2.4 |
25,600
|
0.0391 ms |
| EJS 6.0.1 |
17,489
|
0.0572 ms |
| Twing 7.3.1 |
4,105
|
0.244 ms |
| LiquidJS 10.29.0 |
3,917
|
0.255 ms |
Product grid
A shop category page that renders a product card component 48 times, with sale prices, badges and star ratings.
Uses: Components, Slots, Loops, Prices
| Engine | Renders per second | Time per render |
|---|---|---|
| Kiln 1.0.0 |
46,613
|
0.0215 ms |
| Eta 4.6.0 |
39,960
|
0.025 ms |
| Pug 3.0.4 |
26,423
|
0.0378 ms |
| Nunjucks 3.2.4 |
7,004
|
0.143 ms |
| Handlebars 4.7.9 |
6,924
|
0.144 ms |
| EJS 6.0.1 |
4,333
|
0.231 ms |
| Twing 7.3.1 |
829
|
1.21 ms |
| LiquidJS 10.29.0 |
762
|
1.31 ms |
Data table
An admin table with 500 rows and 6 columns. Mostly loop and output speed.
Uses: Large loops, Loop variables, Escaping
| Engine | Renders per second | Time per render |
|---|---|---|
| Kiln 1.0.0 |
18,554
|
0.0539 ms |
| Eta 4.6.0 |
10,983
|
0.0911 ms |
| Pug 3.0.4 |
7,860
|
0.127 ms |
| Handlebars 4.7.9 |
2,974
|
0.336 ms |
| Nunjucks 3.2.4 |
2,529
|
0.395 ms |
| EJS 6.0.1 |
1,714
|
0.583 ms |
| Twing 7.3.1 |
247
|
4.05 ms |
| LiquidJS 10.29.0 |
220
|
4.55 ms |
Search results
25 search results that run text through filters: truncate, join, sort, case changes, defaults, URL encoding and number formatting.
Uses: Filters, Defaults, URL encoding
| Engine | Renders per second | Time per render |
|---|---|---|
| Kiln 1.0.0 |
126,765
|
0.00789 ms |
| Eta 4.6.0 |
74,273
|
0.0135 ms |
| Pug 3.0.4 |
68,268
|
0.0146 ms |
| Handlebars 4.7.9 |
35,357
|
0.0283 ms |
| Nunjucks 3.2.4 |
29,560
|
0.0338 ms |
| EJS 6.0.1 |
24,444
|
0.0409 ms |
| LiquidJS 10.29.0 |
3,532
|
0.283 ms |
| Twing 7.3.1 |
3,168
|
0.316 ms |
Async data
A blog index that loads its own posts, categories and each post's author by calling async functions from the template.
Uses: Async functions, Loops
| Engine | Renders per second | Time per render |
|---|---|---|
| Kiln 1.0.0 |
155,177
|
0.00644 ms |
| Eta 4.6.0 |
117,456
|
0.00851 ms |
| Nunjucks 3.2.4 |
43,947
|
0.0228 ms |
| EJS 6.0.1 |
32,250
|
0.031 ms |
| Twing 7.3.1 |
2,594
|
0.386 ms |
| LiquidJS 10.29.0 |
2,286
|
0.437 ms |
| Handlebars 4.7.9 | Can't wait for async data | – |
| Pug 3.0.4 | Can't wait for async data | – |
CMS data
A blog post from a headless CMS. The page loads its post and the latest posts, and the layout loads the header and footer menus. Each call waits 2 ms, as a database query would.
Uses: Async functions, Layouts, Partials
| Engine | Renders per second | Time per render |
|---|---|---|
| Kiln 1.0.0 |
385
|
2.6 ms |
| Eta 4.6.0 |
196
|
5.11 ms |
| EJS 6.0.1 |
98
|
10.2 ms |
| Nunjucks 3.2.4 |
97
|
10.3 ms |
| LiquidJS 10.29.0 |
88
|
11.4 ms |
| Twing 7.3.1 |
87
|
11.5 ms |
| Handlebars 4.7.9 | Can't wait for async data | – |
| Pug 3.0.4 | Can't wait for async data | – |
First render
The blog post page from a cold start: create the environment, compile every template and render once. This is what a serverless cold start or a template edit in a CMS costs.
Uses: Parsing, Compiling
| Engine | Renders per second | Time per render |
|---|---|---|
| Eta 4.6.0 |
19,145
|
0.0522 ms |
| Kiln 1.0.0 |
11,112
|
0.09 ms |
| EJS 6.0.1 |
8,453
|
0.118 ms |
| Nunjucks 3.2.4 |
3,568
|
0.28 ms |
| Handlebars 4.7.9 |
1,557
|
0.642 ms |
| Twing 7.3.1 |
1,525
|
0.656 ms |
| Pug 3.0.4 |
524
|
1.91 ms |
| LiquidJS 10.29.0 |
476
|
2.1 ms |
How the benchmark works
- Same HTML. Before timing anything, every engine renders every page and the benchmark checks the HTML matches Kiln's. Only whitespace and the way quotes are escaped may differ. An engine can't win by doing less work.
- Idiomatic templates. Each engine's templates use its own way of doing things: its layouts, partials, components or macros, and its built-in filters. Where an engine has no built-in for something, such as formatting a price, it gets a small helper with the same output, as its users would add.
- Production set-up. Templates are compiled once and cached, output is HTML-escaped, and templates load from memory, so no time goes on reading files. Engines with a synchronous API use it, except in the async data page.
- Separate processes. Each engine runs in its own Node process, so one engine can't slow another down. Each page is rendered for 250 ms to warm up, then timed for 1,000 ms with tinybench. Time per render is the mean of every timed render. Renders per second is 1,000 divided by that time in milliseconds.
- Async data. Handlebars and Pug have no way for a template to wait for a promise, so they sit out the async data and CMS data pages. On the async data page, the async functions resolve straight away, so it measures the cost of waiting, not of fetching data.
- CMS data. Each data function waits 2 ms before it returns, about as long as a query to a database near the server takes. Kiln runs calls that don't depend on each other at the same time, so the page's two documents and the layout's two menus all load at the same time. Eta templates are JavaScript, so its templates start calls together with
Promise.all. The other engines wait for each call to finish before they start the next. On a real site, the difference depends on where the data comes from, how long each call takes and how many calls a page makes. - Precompiled Kiln. Kiln renders precompiled templates, as its docs recommend for the templates in your codebase. The benchmark precompiles them before timing starts, as a site's build would.
- First render. This page times creating an environment, compiling every template and rendering once. A serverless cold start, or editing a template in a CMS, costs about this much. Kiln compiles from source here, as it does for a template edited in a CMS.
The templates for every engine are in packages/benchmarks/templates. If you know a faster way to write one of them in its engine, please open a pull request.
Reading the results
- Numbers depend on the machine. Compare engines within one run, not with numbers from another computer.
- Nunjucks, Handlebars, EJS, Eta and Pug turn templates into JavaScript and run it with
new Function. That makes compiled templates fast. But Cloudflare Workers blocksnew Function, so on Workers those engines need a build step that compiles templates ahead of time. Kiln generates JavaScript only when you precompile at build time, so the same templates run on Workers. LiquidJS and Twing don't generate code. - Kiln runs with its defaults, including strict mode, which checks every variable and property a template reads.
Run it yourself
From the root of the repository:
npm install
npm run bench
This builds Kiln, checks every engine's output, runs the benchmark and writes packages/benchmarks/results.json, which this page reads. A full run takes about two minutes.
| Last run | Details |
|---|---|
| Date | 17 October 2026 |
| Kiln commit | c5e1a5b |
| Node | 24.22.0 |
| CPU | Apple M4 Pro, 12 cores |
| Memory | 24 GB |
| Operating system | Darwin 25.3.0 (arm64) |