skip to content

#11ty

#tech

#webc

WebC at scale

Some things to check when going from hundreds to thousands.


When setting out to build Capital Brief, 11ty (v2 at the time) supported on-demand builders in Netlify via the 11ty serverless plugin. This allowed me to use a build strategy along the lines of:

This gave us predicable build times and ensured the pages we expected humans to read were super fast.

Sounds good right? Unfortunately for me:

So the new build strategy is: build all the things.

All fun and games in theory, but the second the rubber hit the road, the build failed with:

FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory

I gave the process 8GB of RAM: npx --node-options='--max-old-space-size=8192' @11ty/eleventy --serve --quiet... and got the same error.

Uh oh.

So I started breaking things up into smaller chunks to see what part of the build was the most expensive. Here’s what I found:

Use @raw instead of @html

Since the good old days, I’ve used a layout pattern like:

base.webc

<!doctype html>
<html
  <head></head>
  <body>
    <template webc:nokeep @html="content"></template>
  </body>
</html>

page.webc

---
layout: base.webc
---

<custom-header></custom-header>
<main @html="content"></main>
<custom-footer></custom-footer>

I’m pretty sure it’s a standard pattern, and from my first read of the WebC docs, the usage of @html seems right?

- Content returned from the @html prop will be processed as WebC.
- Using [@raw] will prevent processing the result as WebC.

Turns out that in the context of a layout, using @html for content is not necessary, and just super expensive.

Out of everything I discovered, this is the biggest, easiest, and most practical win. I haven’t dug into the internals to work out why, but this change is what allowed the build to complete without running out of memory.

Avoid nesting WebC components, where practical

This change had a smaller impact, but I noticed improvements when reducing component and layout nesting, particularly where the data being passed through was chunky. As a follow up example from above, instead of splitting the layouts into base + page, I now use use just page.

page.webc

<!doctype html>
<html
  <head></head>
  <body>
    <custom-header></custom-header>
    <main @raw="content"></main>
    <custom-footer></custom-footer>
  </body>
</html>

Get expensive data into required shape

If you’re in a position where every millisecond counts, you can shave a few off by getting complex or looped data ready as HTML in your data first, then use <div @raw="related.html"></div> instead of <div webc:for="related.items">...</div>.

I actually rolled this change back in the end, as the many costs of the change outweighed the benefit. I much, much prefer to handle the HTML in the template rather than in data.


TLDR;