Sign-ups are closed. Menestrel now powers the sites built by Agora Studio, a web agency based in France.

Astro build times: what actually matters when a client publishes

The gap between a client clicking Publish and their page being live is mostly build time. Where it really goes, which optimisations pay, and why incremental builds are not the answer people expect.

On a static site, the delay between a client clicking Publish and their change being visible is almost entirely build time. Everything else, the content fetch, the deploy, the cache invalidation, is seconds. The build is minutes.

That gap is the single biggest usability cost of the static model, so it is worth understanding where the time actually goes rather than optimising by folklore.

Where the time goes on a real brochure site

A typical Astro brochure site, two hundred pages, three hundred images, deployed on a hosted CI runner:

Phase Typical Notes
Cold start of the runner 5 to 20 s Nothing you control
Dependency install 15 to 90 s The biggest variable
Content fetch 1 to 10 s Or minutes if the source is slow
Type generation and sync 2 to 5 s
Page rendering 5 to 30 s Scales with page count
Image processing 0 to 240 s The dominant term when it happens
Bundling assets 5 to 20 s
Upload and deploy 5 to 30 s

Two lines dominate, and neither is Astro’s rendering.

The first: dependency install

This is the least interesting and most reliably wasteful part of a build. A cold npm install on a project with a UI library and a few integrations is routinely a minute and a half. It happens on every build, and it produces exactly the same result every time.

What helps, in order of effect:

A lockfile that is actually respected. npm ci, pnpm install --frozen-lockfile. Resolution is the slow part, and a lockfile skips it.

The host’s dependency cache, verified. Every CI platform caches node_modules or the package manager store. Most projects assume it is working. Read one build log and check: a warm cache install is under fifteen seconds, a cold one is over a minute. If you are seeing the latter every time, something is wrong with your cache key.

Fewer dependencies. Unglamorous and effective. A brochure site does not need a component library.

The second: image processing

This is the one that turns a forty-second build into a four-minute one, and it is where most people’s intuition is wrong.

If your images are processed during the build, that work repeats on every deploy. Change one sentence on a site with three hundred photos, and the build downloads, decodes, resizes and re-encodes three hundred images to produce a page where a sentence changed. Astro caches aggressively, which helps on a warm runner and does nothing on a fresh CI container, which is exactly where your client’s publish runs.

If images are processed at upload, once, the build receives URLs and dimensions and does no image work at all. A deploy on a site with two thousand photos costs the same as one with none.

This is the single biggest architectural lever on publish latency, and it is decided by your CMS rather than by your Astro config. It is the reason our processing happens on upload: format conversion, EXIF handling, resizing into a fixed set of widths, AVIF and WebP derivatives, all done once when the client picks the file.

Why incremental builds are not the answer you want

“Just use incremental builds” is the standard reply, and it deserves a careful answer.

Incremental static regeneration, in the various forms hosts offer it, rebuilds only what changed. For a site with fifty thousand pages it is transformative. For a brochure site with two hundred, it is close to irrelevant, because rendering two hundred pages was never the bottleneck. Look at the table again: page rendering is 5 to 30 seconds out of a build that might be four minutes.

Incremental builds attack the smallest term. Dependency install and image processing, which dominate, are untouched.

There is a second, more subtle problem: incremental builds require the build system to know precisely what changed and what depends on it. On a content-driven site, a single edit can affect an index page, a sitemap, an RSS feed and a navigation menu. Getting that dependency graph wrong produces a site where some pages are stale, which is worse than a slower build that is correct.

Astro’s Content Layer does support digests for skipping unchanged entries, and that genuinely helps when you have thousands of entries. It is not what makes a two-hundred-page build fast, and it will not rescue a build spending three minutes on images.

What a realistic budget looks like

For a brochure site, aiming at one to three minutes from click to live page is honest and achievable. Under a minute is possible with a warm cache and no image work. Over five minutes means something specific is wrong, and it is almost always images or install.

The number matters because it sets the interface. At one to three minutes, showing progress and ending at “live at 2:32 pm” is a good experience: the editor sees something happening and gets a definitive answer. At ten minutes it is not, and no amount of interface design fixes it.

Grouping, and why publishing is not always instant

There is a deliberate delay we add, and it is worth explaining because it looks like slowness.

An editor rarely publishes once. They fix a title, then a price, then notice a typo. Three publishes in ninety seconds. Triggering three builds means two are wasted, they queue behind each other, and on some hosts they race in a way where the older one can win and quietly undo the newest change.

So publishes within a short window are grouped into a single build. The cost is that the very first click is not always instantaneous. The benefit is that the editor’s three corrections arrive together, once, in the right order.

How to measure yours instead of guessing

Most build optimisation is done by folklore. Ten minutes of measurement beats an afternoon of guessing.

Every host prints timestamped build logs. Open the most recent one and write down four numbers: when the install started and ended, when the content fetch happened, when image processing started and ended, and when the upload began. That gives you the shape of your build, and it is almost always surprising.

The comparison that matters most is a cold build against a warm one. Trigger a build with the cache cleared, then trigger another immediately. If the difference is over a minute, your dependency cache is doing heavy lifting that will not be there on the day it matters, because cache keys change whenever your lockfile does.

Locally, the same shape shows up with:

# Where the time actually goes, per phase
time npx astro build

# Whether images are the problem: build twice, second run is warm
rm -rf node_modules/.astro && time npx astro build
time npx astro build

If the second run is dramatically faster, you are paying for image processing on every cold CI build, and the fix is architectural rather than a config flag.

The part that is not build time at all

Two things sit outside the build and are worth ruling out before optimising it.

Queue time. On shared CI, a build can wait before it starts. It shows in the host’s dashboard as a duration that does not match the log timestamps. If your builds are fast but publishing feels slow, look here first.

CDN propagation. After a deployment is promoted, edge caches need to pick it up. On modern hosts this is near-instant for HTML, but an aggressively cached asset with a long max-age and no fingerprint in its name can stay stale for a long time. Fingerprinted filenames make this a non-issue, and Astro produces them by default for bundled assets. The place it bites is hand-managed files in public/.

What you can actually do about it

In order of return on effort:

  1. Move image processing out of the build. Biggest single win, and it is a CMS choice.
  2. Verify your dependency cache is warm. Read one build log. Many people discover it never worked.
  3. Use a frozen lockfile. One line in your CI config.
  4. Fetch content from something fast and immutable. A CDN-hosted snapshot rather than a live API means the fetch is milliseconds and cannot be slow because a third party is having a bad afternoon.
  5. Drop dependencies you do not use.
  6. Only then consider incremental builds, and only if your page count is genuinely large.

The honest limit

None of this makes a static site instant. If your client needs a change visible in ten seconds, the static model is wrong for them and server rendering is right, and no build optimisation closes that gap.

What the static model buys in exchange is a site that costs almost nothing to host, absorbs traffic spikes without anyone intervening, and has no runtime dependency on the CMS: if we are down, published sites stay up and builds still succeed, because the snapshot is on a CDN rather than in our API.

That trade is good for a brochure site and bad for a newsroom. Knowing which one you are building is the actual decision, and the four families of CMS covers how it falls out.

Back to the blog