Making Cloudflare Docs local development start ~2.27x faster with 1 line of Tailwind config


When investigating why astro dev was taking 25 seconds on average to be ready for the first pageload, your first port of call might be to check Vite’s (the build tool used by Astro) performance troubleshooting page.

Flamegraphing

We could guess all day at what might be the culprit - or we can profile the startup, and that will tell us! It wouldn’t be the first time I’ve reached for 0x when investigating performance in our dev/build processes.

Terminal window
npx 0x ./node_modules/.bin/astro dev

We’ll run astro dev, load http://localhost:1111 in our browser and CTRL+C the 0x process (well, this should work but I had to kill -15 the PID instead) when the page loads in our browser. This represents the slow startup that we’re interested in tackling.

0x flamegraph of astro dev

The Tailwind culprit here is getClassCandidates.

getClassCandidates

This represents 24.9% of the stack in this flamegraph! Why is it taking so long?

/** @type {[item: {file?: string, content?: string}, meta: {transformer: any, extractor: any}][]} */
let regexParserContent = [];
for (let item of context.changedContent) {
let transformer = getTransformer(context.tailwindConfig, item.extension);
let extractor = getExtractor(context, item.extension);
regexParserContent.push([item, { transformer, extractor }]);
}
const BATCH_SIZE = 500;
for (let i = 0; i < regexParserContent.length; i += BATCH_SIZE) {
let batch = regexParserContent.slice(i, i + BATCH_SIZE);
await Promise.all(
batch.map(async ([{ file, content }, { transformer, extractor }]) => {
content = file ? await fs.promises.readFile(file, "utf8") : content;
getClassCandidates(transformer(content), extractor, candidates, seen);
}),
);
}

This code looks through all of the files included in the content glob, from Tailwind’s configuration file, to find potential classes. After all, Tailwind doesn’t include all their classes in the CSS bundle but rather just the ones it found you using! So, what is our content glob set to?

content: ["./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}"],

At first glance, this seems innocuous - until you see the md,mdx. We have 478,079 lines of md and mdx in our repository, and Tailwind is using RegExes in JavaScript to try and find classes in them… Technically, this is valid - you can write HTML in md and mdx files but we don’t as we always use our Astro or React components.

Knowing that we have a lot of md,mdx content, and we know Tailwind will find nothing in them, we can remove them from the glob.

content: ["./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}"],
content: ["./src/**/*.{astro,html,js,jsx,svelte,ts,tsx,vue}"],

This simple change has brought us down to an average startup time of 11 seconds!

Astro 5 recently released and replaces Content Collections, which are notoriously slow for large sites, with Content Layer that boasts up-to-2x faster MDX build times. In our preliminary testing, we also saw a 3.82x improvement in the time taken when loading new pages post-startup. Stay tuned for when we work out some edge cases with the Astro team and ship the upgrade!

Back to home