+1 (478) 654-9062

Slow Rebuilds and Fat Bundles: Working on the Meteor Build Pipeline

Every assessment we do includes a question that sounds like a developer-comfort issue and is not: how long does a rebuild take?

On a Meteor app that has been in production since 2016, the answer is often 60 to 120 seconds, sometimes worse. Teams describe this as annoying. It is more expensive than that. A two-minute edit-refresh loop changes how people work: they batch changes instead of testing one at a time, they stop writing the small exploratory commit, and they avoid the refactors that would have made the codebase cheaper to own. Slow builds are a tax on every other piece of work you were planning, including the Meteor 3 migration.

The same pipeline produces the other symptom clients raise: a client bundle in the multi-megabyte range, and a first paint that is slow on anything but office wifi.

Both are fixable, and neither requires rewriting anything. Start by measuring.

Measure the build before you change it

Meteor's build tool is instrumented. Run a build with profiling on:

METEOR_PROFILE=100 meteor --settings settings.json

You get a tree of build phases with millisecond costs, filtered to steps above the threshold you passed. Read it before forming a theory. In practice the time lands in a small number of places:

  • Linker and minifier work, especially in --production builds, which is where terser-style minification dominates.
  • Compiler plugins running over more files than they need to — a LESS or Sass plugin walking directories you forgot about, a TypeScript or Babel pass over generated code.
  • Rebuild scope: a file watcher that invalidates far more than the file you touched, usually because an import graph runs through a large barrel file.
  • Sheer file count, including things that should never have been in the build: .git leftovers, fixture data, a docs/ tree, an old public/ directory holding 400 MB of images.

That last one is the cheapest win available and it is embarrassing how often it is the answer. .meteorignore works like .gitignore and keeps paths out of the build entirely. Excluding stale directories has taken 40 seconds off a rebuild for us more than once, with no code change at all.

A few other measurable levers, in rough order of effort:

  1. Check what is in .meteor/packages that nobody uses. Atmosphere packages carry build-time cost, not just runtime cost. Apps accumulate them; nobody removes them. Remove one, rebuild, keep the measurement.
  2. Break up barrel files. A single imports/api/index.js that re-exports everything means any change invalidates most of the graph. Importing modules directly is unglamorous and it narrows rebuild scope.
  3. Separate dev from production minification. Development rebuilds should not be paying minifier cost at all; if they are, something is misconfigured. For production builds, community minifier packages that swap terser for a faster engine exist and can cut CI time meaningfully — check which ones are actively maintained against your Meteor version before adopting, because that list changes.
  4. Give the build machine more to work with. Isobuild is heavily single-threaded and memory-hungry. A CI container with 2 GB of RAM will thrash on a large app; raising --max-old-space-size and the container limit sometimes turns a 9-minute CI build into a 4-minute one. Not elegant, but it is a config change, not a refactor.

Client bundle size: find it, then split it

For bundle size, get the picture first:

meteor --production --extra-packages bundle-visualizer

That renders a treemap of what actually shipped to the browser. The usual offenders on an older Meteor app:

  • Moment.js with all locales, or lodash imported wholesale.
  • A charting or PDF library loaded on every page for one admin screen.
  • Both a jQuery-era UI kit and its React replacement, because the migration stalled halfway.
  • Blaze templates for screens that were retired years ago and never deleted.

The structural fix is dynamic import. Meteor supports import() with code splitting, and the imports/ convention plus lazy loading lets you move a heavy dependency out of the initial payload:

async function openReportBuilder() {
  const { ReportBuilder } = await import('./reports/ReportBuilder.js');
  return ReportBuilder;
}

Route-level splitting on the three or four heaviest screens typically does more for first load than any amount of minifier tuning. Deleting dead templates does the rest. Both are safe, incremental changes you can ship on a normal Tuesday.

Where the Vite-based client build fits

The more interesting development is that Meteor's client build is no longer only Isobuild. Recent 3.x work has been integrating Vite for the client side — first as a community package, then increasingly as a supported path — with Isobuild still handling the server bundle and the Atmosphere package system. The appeal is real: Vite's dev server does near-instant hot module replacement, and the gap against a 90-second Meteor rebuild is not a 20 percent improvement, it is a different way of working.

Our candid read, as of early 2026:

  • Check the release notes for your exact Meteor version rather than trusting any blog post, including this one. This area has moved quickly and the setup steps differ between 3.x minors.
  • It is a build-tooling change, not a framework change. Your methods, publications, and collections are untouched. That makes it a much smaller bet than it sounds like, and it is reversible.
  • Atmosphere packages are where friction lives. Packages that inject client assets or rely on Isobuild ordering are the things most likely to need attention.
  • Do it after the async migration, not during. Two moving pipelines at once means you cannot attribute a regression to either. Async conversion first, platform upgrade second, build tooling third.
  • If the app is on a migration-off-Meteor path, weigh it against that plan. If the React front end is moving to Next.js over the next year anyway, effort spent on the Meteor client build may be effort spent twice. If you are staying, it pays back every day.

What we actually recommend

For most apps, the order is boring and effective: profile the build, fix .meteorignore and dead packages, visualize the bundle, split the two or three heaviest routes, delete the retired screens. That is usually a few days of work and it frequently halves both numbers.

Then, if you are staying on Meteor, evaluate the Vite path deliberately — after the platform work, with the measurements you took at the start as your baseline. Keep the numbers. A rebuild time and a bundle size are two of the few honest, cheap health metrics a Meteor codebase gives you for free, and they are the ones most likely to be quietly getting worse while nobody is looking.