DEV Community

Uncle Pushui
Uncle Pushui

Posted on

Why Large Frontend Apps Keep Getting Slower Even After the Usual Optimizations

Large frontend performance is often more of an architecture problem than a bundle problem.

Most large frontend teams are not ignoring performance basics.

They already split bundles, lazy-load routes, compress assets, optimize images, cache requests, and add SSR where it clearly helps.

And yet the app still gets heavier.

A dashboard gets faster, and another one becomes the new bottleneck. A bundle goes down, then creeps back up a few sprints later. Caching improves one flow, while invalidation gets messy somewhere else.

If that sounds familiar, the issue may not be that your team needs one more round of optimization. It may be that the frontend is structurally designed to keep accumulating weight as it grows.

That is the argument of this article.

Key takeaways

  • weak boundaries tend to produce frontend monolith growth
  • repeated CRUD and resource page construction creates hidden system weight
  • query caching works best as infrastructure, not as a team-by-team habit
  • SSR becomes more useful when it includes admin flows, not just public pages

The real problem is not just slowness. It is slowness that keeps coming back.

A lot of teams approach performance in a familiar loop:

  1. shrink the bundle
  2. optimize the slow screen
  3. cache a few expensive requests
  4. move selected routes to SSR
  5. repeat when the next bottleneck appears

That approach works for a while.

What it does not explain is why large business applications so often slide back into the same state. In many cases, the reason is structural. The architecture keeps creating new weight faster than the team can remove it.

You can usually see that happening through four patterns:

  • the frontend keeps drifting toward a monolith
  • similar CRUD and resource pages are rebuilt over and over
  • query and cache behavior varies from team to team
  • SSR exists, but only as a special-case feature

When those patterns are present, performance work becomes reactive by default.


1. Weak boundaries quietly turn the frontend into one growing shell

A codebase can look modular and still behave like a monolith.

It may have feature folders, shared utilities, route splitting, and a decent naming convention. But that often amounts to directory modularity, not real architectural separation.

The harder question is whether the system has durable boundaries:

  • business boundaries
  • bundle boundaries
  • runtime boundaries
  • delivery-target boundaries

Without them, every new feature adds a little more shared dependency surface, a little more startup cost, and a little more cross-cutting complexity. No single change looks fatal. The problem is the accumulation.

That is why large-app performance is often misdiagnosed as “one more bundle problem.” In reality, it is frequently a boundary problem. Once the frontend loses the ability to resist monolith growth, every optimization becomes harder to keep.


2. Rebuilding the same resource surfaces again and again also makes the app heavier

Another source of weight is repetition at the page layer.

Large business systems tend to contain many variations of the same basic surfaces:

  • list pages
  • create and edit forms
  • filters
  • detail views
  • row actions
  • resource-oriented admin flows

When teams hand-build these again and again, the cost is not just duplicated effort. It is also duplicated structure:

  • more page code
  • more repeated view logic
  • more initialization work per screen
  • more inconsistency across similar resources
  • more rework when backend fields change

This is where contract-driven UI becomes interesting.

The point is not that everything should be generated. That is rarely the right target. The more practical goal is to let backend contracts shape the routine parts of the frontend, while keeping custom work for the genuinely product-specific pieces.

Done well, that has a few compounding benefits: thinner page layers, less drift between backend and frontend, and fewer repeated decisions around the same CRUD patterns.

At scale, that is not just a DX win. It changes the weight profile of the application.


3. Query caching works best as infrastructure, not as a team habit

A surprising number of “slow frontend” complaints are really data-path problems.

You see them in patterns like these:

  • too many requests on first entry
  • unnecessary refetches when returning to a screen
  • multiple components requesting the same data independently
  • inconsistent invalidation rules
  • awkward SSR prefetch and hydration behavior

Small apps can get away with this for a while. Large apps usually cannot.

That is why query caching should be treated as infrastructure. The specific library matters less than the role it plays in the system.

What matters is whether server-data behavior is:

  • consistent
  • reusable
  • centrally modeled
  • compatible with SSR and hydration
  • understandable across teams

If caching arrives late, each team tends to wrap it in its own way. Cache keys drift. Invalidation becomes uneven. SSR support is added later and never feels fully integrated.

When query behavior is part of the runtime model, the frontend is much more likely to get shared semantics, predictable reuse, and cleaner transitions between server-rendered and client-resumed states.

In large systems, that kind of coherence matters as much as bundle size.


4. SSR becomes more valuable when it includes admin flows, not just public pages

SSR is still often discussed as a public-web technique:

  • SEO pages
  • marketing pages
  • content pages
  • landing pages

But some of the heaviest first-load experiences in real applications are internal:

  • admin dashboards
  • workbenches
  • reporting screens
  • entry pages that need identity, permissions, menus, layout state, and initial data before they become useful

If all of that is handled purely on the client, users often sit through a long startup chain before they see the first meaningful screen.

That is exactly where SSR can help.

But SSR is most effective when it is not bolted on as a special case. It works best when it lines up with the rest of the architecture:

  • modular frontend boundaries
  • shared contract surfaces
  • query and cache infrastructure
  • coherent hydration paths

That matters not only for public web apps, but also for serious back-office systems where first-use responsiveness shapes the overall feel of the product.


Why Cabloy is a useful case study

Cabloy is interesting here not because it “solves performance automatically,” but because it brings several of these ideas together in one place.

In Cabloy Basic:

  1. Frontend structure is explicitly modular. Zova organizes capabilities through modules and suites instead of treating the UI as one flat application surface.
  2. Backend contracts can shape routine frontend surfaces. DTO, entity, and OpenAPI metadata can feed SDK generation and schema-aware UI helpers for forms, tables, filters, detail views, and actions.
  3. TanStack Query sits in the foundation. Zova’s model-state layer builds on it through framework-native abstractions.
  4. SSR exists for both Admin and Web flavors. That treats first-load quality as a whole-system concern rather than something reserved for public pages.

The takeaway is not that every team should adopt Cabloy. The more portable lesson is that large frontend performance improves when these concerns are treated as design choices early, not cleanup tasks later.


A better question to ask

If your team has already done the obvious optimizations and the app still feels heavy, ask this instead:

Is our architecture structurally designed to keep getting heavier over time?

That question usually reveals more than another isolated bundle audit.

A strong answer tends to include four things:

  1. real modular boundaries
  2. thinner routine UI through shared contracts
  3. query caching as infrastructure
  4. SSR integrated into the system model, including admin flows where it matters

If those pieces are missing, performance wins tend to be temporary. If they are present, the frontend has a much better chance of staying fast as it grows.

That is the real challenge in frontend performance at scale:

not making one page faster today, but building a system that does not keep getting slower by default.

Top comments (0)