cd /news/developer-tools/your-12-minute-frontend-build-is-now… · home topics developer-tools article
[ARTICLE · art-87319] src=dev.to ↗ pub= topic=developer-tools verified=true sentiment=· neutral

Your 12-Minute Frontend Build Is Now Your AI Agent's Bottleneck

Nx 23 introduces consumer and provider generators for micro frontends in a monorepo, enabling build fan-out, bundle decoupling, and reduced context cost for AI coding agents. The approach keeps one repo while splitting the build, addressing the bottleneck where a 12-minute frontend build invalidates the entire app for small changes.

read7 min views1 publishedAug 5, 2026

Your coding agent just opened five PRs in a morning. Each one changes a handful of files in one product area. Each one triggers a twelve-minute build of the entire frontend: search, account, admin, marketing pages, all of it. That is an hour of CI to validate maybe two hundred lines, and the review queue is now the slowest part of a workflow you adopted to go faster.

The same twelve minutes used to be an annoyance you absorbed a few times a day. At agent throughput it is the bottleneck.

It gets worse before it gets better. You ask the agent "where does checkout read the pricing config from?" and it opens forty files across four product areas before answering, because as far as the tooling is concerned there is exactly one application and it is enormous. Then it changes six words of copy in the checkout confirmation screen, and every user on the site redownloads a main bundle whose hash moved for reasons that have nothing to do with them.

One build unit, one bundle graph, one context blob. Micro frontends in a monorepo split all three, and the monorepo part is what keeps it from turning into a distributed systems problem you did not sign up for.

Build fan-out. Your CI has no idea that checkout and search do not touch each other. Any commit invalidates the whole build. Build time is a function of repo size, not change size.

Bundle coupling. Webpack and Vite chunk splitting help with what gets loaded, but not with what gets invalidated. If checkout and search compile into one build output, a checkout change can shift chunk hashes across the app and evict cached bytes users already had.

Delivery coupling. Two teams shipping to the same artifact means every deploy is a merge queue negotiation. The team that is ready waits on the team that is not.

Context cost. This is the newest one and the least discussed. An AI coding agent working in a repo has a finite attention budget. Give it one 1,200-file application with no enforced internal boundaries and it will read broadly, guess about ownership, and occasionally reach into a module it had no business touching. Give it apps/checkout

plus three shared libs and it reads less, guesses less, and produces a diff you can actually review.

The mistake people make is equating micro frontends with repo-per-team. That buys you deploy independence and charges you version drift, six duplicate CI configs, and cross-repo refactors that nobody ever does.

Keep one repo. Split the build.

With Nx, scaffolding the pieces is one command. Nx 23 renamed the generators: the app that loads federated modules is a consumer (previously host

), the app that exposes them is a provider (previously remote

).

nx g @nx/react:consumer apps/shell --bundler=rspack --providerNames=checkout,search
nx g @nx/react:provider apps/account --consumer=shell

The old @nx/react:host

and @nx/react:remote

generators still ship, so existing workspaces keep working, but new work should use consumer/provider. With the new generators, providers are registered at runtime from an inline list in the consumer's src/mf.ts

rather than being frozen into build config.

Each provider gets its own build target and its own deployable output. Underneath, the Module Federation build plugin config looks like this:

import { createModuleFederationConfig } from '@module-federation/enhanced/rspack';

export default createModuleFederationConfig({
  name: 'shell',
  remotes: {
    checkout: 'checkout@https://cdn.example.com/checkout/mf-manifest.json',
  },
  shared: {
    react: { singleton: true },
    'react-dom': { singleton: true },
  },
});

singleton: true

on React is not optional. Without it you ship two React copies, hooks break across the boundary, and you spend an afternoon on an error message about invalid hook calls that has nothing to do with your hooks.

For remotes that are not known at build time (a plugin surface, a tenant-specific module, an A/B variant), the runtime API takes over:

import { init, loadRemote } from '@module-federation/runtime';

init({
  name: 'shell',
  remotes: [{ name: 'promo', entry: 'https://cdn.example.com/promo/remoteEntry.js' }],
});

const Promo = await loadRemote('promo/Banner');

Cache invalidation gets a boundary. Each remote publishes its own entry and its own chunks. A copy change in checkout produces new checkout bytes. Search bytes keep their hashes and stay in the user's cache. On a large app this is the difference between shipping a small delta and making every user re-download a vendor bundle because one hash cascaded.

** follows navigation.** A remote is fetched when the route that needs it is entered. Users who never open admin never pay for admin. You can approximate this with route-level lazy imports in a monolith, but you cannot approximate the invalidation boundary, and lazy imports quietly re-couple every time someone adds a top-level import for a type.

Local dev stops rebuilding what you are not editing. nx serve shell

starts the whole composed app with the remotes built and served statically. Only the remote you are actively editing needs a real dev server, and since Nx 21 you get that by serving the remote itself:

nx serve checkout

On Angular, or on webpack Module Federation without inferred tasks, the equivalent is nx serve shell --devRemotes=checkout

.

Now be honest about the costs, because they are real:

Two mechanisms, both boring, both effective.

Affected-only work. Nx computes a project graph from real imports, so it knows checkout and search are unrelated:

nx affected -t build
nx affected -t test lint

An agent that opens a PR touching only apps/checkout

triggers a build of checkout and its dependents. Not the repo. Those five PRs from the opening now cost five checkout builds instead of an hour of full-frontend CI, and they run in parallel because they no longer contend for the same build. The twelve minutes did not get optimized away, it stopped being charged for work that never changed.

Boundaries the agent cannot argue with. Tag every project and let ESLint enforce who may import whom:

'@nx/enforce-module-boundaries': [
  'error',
  {
    allow: [],
    depConstraints: [
      { sourceTag: 'scope:shared', onlyDependOnLibsWithTags: ['scope:shared'] },
      { sourceTag: 'scope:checkout', onlyDependOnLibsWithTags: ['scope:shared', 'scope:checkout'] },
      { sourceTag: 'scope:search', onlyDependOnLibsWithTags: ['scope:shared', 'scope:search'] },
    ],
  },
],

This is the part that changes how it feels to work with an agent. A prompt that says "do not import from other teams' code" is a suggestion the model may or may not follow. A lint rule that fails CI is a fact. When an agent takes the shortcut of reaching into apps/search/src/internal/pricing.ts

from checkout, the build tells it no, and it fixes the mistake in the same session instead of you finding it in review three days later.

The context effect compounds. Scoping an agent to one remote plus the shared libs means the files it reads are the files that matter. Smaller context, fewer distractor files, more of the budget spent on the actual change. The same architecture decision that gave you independent deploys gave you a natural unit of work for an agent, which is not a coincidence: both are asking for the same thing, which is a piece of the system you can reason about without the rest.

Do not carve up the whole app. Pick the boundary that hurts most, usually the one where two teams collide in the merge queue, and extract exactly that one.

libs/shared

, tagged scope:shared

.enforce-module-boundaries

in warn mode, fix the violations it finds, then flip it to error.nx affected

. Measure the build time delta before extracting a third remote.If step 4 does not show a meaningful improvement, stop. You have a dependency-graph problem, not an architecture problem, and adding remotes will not fix it.

singleton: true

in shared

, or hooks break across the remote boundary at runtime.nx affected -t build

turns build time into a function of change size instead of repo size, which is what makes multi-PR agent work practical rather than theoretical.@nx/enforce-module-boundaries

with tags converts your architecture from a convention into a CI failure, and an AI agent respects a failing build far more reliably than a prompt.Docs worth reading before you start: Nx Module Federation and Module Federation Core.

── more in #developer-tools 4 stories · sorted by recency
── more on @nx 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/your-12-minute-front…] indexed:0 read:7min 2026-08-05 ·