Laravel

Front-End Cost You Are Paying on the Server

Every component update is a server round trip, and you are paying for all of them.

Chapter 10 of 1311 min readOpen access

Front-End Cost You Are Paying on the Server is the chapter about traffic no user asked for. In a server-driven interface the client generates requests on its own schedule, which means your arrival rate from Chapter 2 is set by component design rather than by how many people are using the product.

Key takeaways

  • Livewire documents lazy components as loading when they scroll into the viewport and deferred components as loading immediately after the initial page load, which are different promises with different capacity effects.
  • Livewire also documents that lazy and deferred component updates are isolated from each other and load in parallel, and that bundle: true sends them to the server as a single request instead.
  • A Livewire placeholder must share the component's root element type, so a div placeholder requires a div component, and getting it wrong produces layout bugs rather than performance ones.
  • Inertia documents that a direct prop value is evaluated on every visit while a closure is evaluated only when needed, so wrapping expensive props in closures is the cheapest change in this chapter.
  • Inertia's Inertia::optional() props are never included unless explicitly requested with only, and Inertia::defer() fetches after the initial render, one request per group, with groups in parallel.

Read this after Chapter 2, because polling is an arrival rate you set in a template, and after Chapter 3, because each of these round trips runs the query layer again. Chapter 12 needs this chapter to build a representative load test, since a test that only requests pages will miss most of the traffic a Livewire application actually serves.

A dashboard has nine cards. Each is a Livewire component, and each refreshes itself every five seconds so the numbers look live.

With forty people watching the dashboard, that is a steady stream of requests arriving whether or not anybody is doing anything. Nobody wrote that traffic into a specification. It came from nine lines in nine templates.

Then someone opens the dashboard on a wall screen and leaves it there.

A component update is a full request, boot included

The mental error this chapter exists to correct is treating a component update as cheaper than a page load. It is not the same as a page load, and it is much closer to one than the interface suggests.

A Livewire update arrives as an HTTP request. It resolves the session, runs the middleware stack, hydrates the component's state, executes whatever method was called, runs whatever queries that method needs, re-renders the component's view and diffs the result. Under FPM it also boots the framework, which is the cost Chapter 8 was about.

What it saves relative to a page load is the layout, the sibling components and the client-side render. That is real. It is not the majority of the cost on most routes.

So price a component interaction as a request. Then count how many your interface fires per user per minute, because that number is your arrival rate and most teams have never computed it.

Polling is an arrival rate you chose in a template

Polling deserves its own section because it is the only traffic source in this book that is completely independent of user behaviour.

The arithmetic is Chapter 2's and it is unforgiving. Concurrent viewers multiplied by components per page divided by the polling interval gives arrivals per second, and none of those three terms is under the control of the person watching. A page with nine polling components and a five second interval generates the same load from an idle browser as from an engaged user.

Two decisions cut it, and both are design decisions rather than tuning. Poll one component that holds all the numbers, rather than nine that each hold one. And choose the interval from how often the underlying data actually changes, rather than from how live you would like it to feel.

The third option is to stop polling and push, which moves the cost to a persistent connection and a broadcast path. That is a different architecture with its own capacity model, and it is out of scope here.

Write the arrival rate into the pull request that adds a poll. One number, in a comment.

Lazy and defer are different promises

Livewire gives you two ways to keep a component out of the initial render, and their difference is about when the request happens rather than whether it happens.

Livewire's documentation states that lazy loading makes a component load when it becomes visible in the viewport, as the user scrolls to it, while deferred loading loads it immediately after the initial page load is complete. The #[Lazy] and #[Defer] attributes on the component class enforce the choice for every usage, and Route::livewire(...)->lazy() applies it to a full-page component.

Read the capacity consequence carefully, because it is the opposite of what people assume. Deferred loading does not reduce total requests at all. It moves them off the critical path, so the page appears sooner and your server does exactly as much work.

Lazy loading genuinely reduces total work, but only for content the user never scrolls to. On a short page where everything is visible, lazy and defer cost the same. Both fire. Neither saves you anything.

One documented constraint catches everybody once. The placeholder and the component must share the same element type, so a div placeholder requires a div component root. That one is a layout bug rather than a capacity bug. It is still worth knowing before you meet it.

Bundling trades parallelism for round trips

Livewire's documentation records a detail that changes how you reason about a page with many lazy components. Unlike other Livewire network requests, lazy and deferred component updates are isolated from each other when sent to the server, and load in parallel.

Isolated and parallel is fast for the user and expensive for the pool. Twelve lazy components on one page mean twelve simultaneous requests, which occupy twelve workers from Chapter 2's pool for one page view.

The bundle: true option sends multiple lazy components to the server as a single network request instead. That is one worker rather than twelve, at the cost of the slowest component setting the arrival time for all of them.

OptionRequests per page viewWorker occupancyBest when
Eager, no lazy or deferOneOne, held longerEverything on the page is needed and cheap
#[Defer], isolatedOne per component, in parallelMany, brieflyComponents differ wildly in cost and page speed matters
#[Lazy], isolatedOne per visible componentMany, brieflyMost components are below the fold
bundle: trueOne for the groupOne, held longerThe pool is your constraint rather than perceived speed

The bottom row is the one to choose under load and the top-but-one row is the one to choose in a demo. Knowing which situation you are in is the whole decision.

Inertia charges you for props you did not send

Inertia's model differs and its trap is sharper, because the expensive work happens before anything decides whether it is needed.

Inertia's partial reload documentation is explicit. A direct prop value is evaluated on every visit, standard or partial, while a closure is evaluated only when needed. So passing User::all() runs that query on every single visit, including partial reloads that asked for something else entirely, and wrapping it in a closure does not.

That is the cheapest change in this chapter. Wrap expensive props in closures and the query stops running for visits that never use it. One character of syntax. No behaviour change at all.

The optional helper goes further. Its documentation states that an optional prop is never included unless explicitly requested with only. That makes it right for the expensive panel a user opens sometimes.

Deferred props handle the other case, where the prop is always wanted but not immediately. Inertia documents that the callback runs in a separate request after the initial page render. Deferred props default to loading together in one request. Assigning them to named groups produces one request per group, with the groups fetched in parallel. Groups are therefore the same trade as Livewire's bundling, expressed as a grouping decision.

Blade compilation belongs in the deploy

There is one purely mechanical item at this layer, and it costs nothing to get right.

Laravel's deployment documentation states that view:cache precompiles all Blade views so they are not compiled on demand, and that php artisan optimize caches configuration, events, routes and views together. Without it, the first request to hit each view pays for compiling it.

Under FPM that shows up as an occasional slow first request per view, which is annoying and survivable. Under Octane, and after every deploy, it lands on whichever unlucky requests arrive first, which is exactly when you are least able to absorb it. Chapter 12 explains why this invalidates an unwarmed load test.

Put it in the deploy pipeline. Never in a runtime code path. It is one line. It is free.

A worked reading, composited and labelled

The details here are composited from ordinary production shapes rather than one system, and the shape is exact.

The reading that changes minds is requests per user session, split by whether a human initiated them. The first pass groups access log entries by route into three buckets: page loads, user-driven component updates, and updates nobody asked for, which is polling and automatic refresh. Most teams find the third bucket larger than the first.

The second pass takes the same three buckets and attaches the per-request query count from Chapter 1. That is where a cheap-looking component turns out to be running the same eight queries every five seconds, forever, on every open tab.

Neither pass produces a latency number, and neither needs to. The product of arrival rate and queries per arrival is a load figure you can act on, and it is the input Chapter 12 needs to build a test mix that resembles production.

The objection: this is a front-end concern

The organisational version of this objection is that component design belongs to whoever writes the templates, and server capacity belongs to whoever runs the infrastructure. That division is why this cost survives so long in so many applications.

The mechanism refuses the split. A polling interval in a Blade template is a line of configuration for your arrival rate, and an unwrapped Inertia prop is a query that runs on visits which do not use it. Both are written in the front end and paid for in the pool.

There is a review consequence I would enforce. Any pull request that adds a poll, a lazy component or a prop states the requests per minute it adds at expected concurrency, and the query count per request. Two numbers, from Chapter 1's instrumentation, in the description.

That is not process for its own sake. It is the only place in the pipeline where the person creating the load is also the person who can remove it.

Chapter summary

In a server-driven interface the client generates requests on its own schedule, so component design sets the arrival rate in Chapter 2's arithmetic. A component update is a full HTTP request with session, middleware, hydration, queries and render, plus framework boot under FPM. Polling is the purest form of it. Concurrent viewers times components per page divided by interval gives arrivals per second, with no term controlled by the user, which is why one component holding many numbers beats nine holding one each. Livewire documents lazy loading as triggering when a component scrolls into view, and deferred loading as triggering immediately after the initial page load. So defer moves work off the critical path without reducing it, while lazy saves work only for content nobody scrolls to. Placeholders must share the component's root element type. Lazy and deferred updates are isolated and parallel by default, so twelve lazy components occupy twelve workers until bundle: true collapses them into one request, at the cost of the slowest component setting the arrival time. Inertia's trap sits earlier in the request. Direct prop values are evaluated on every visit while closures are evaluated only when needed, Inertia::optional() props are never included unless requested with only, and Inertia::defer() fetches after the initial render with one request per named group and groups in parallel. view:cache moves Blade compilation into the deploy. And the review rule holding all of it is two numbers per pull request: added requests per minute at expected concurrency, and queries per request.

Everything from Chapter 3 onwards has been about work the application does. Chapter 11 is the layer underneath all of it, and it is deliberately last: The PHP Runtime, covering OPcache, preloading and the JIT that PHP ships switched off.

Sources

  1. Lazy LoadingLivewire · 2026-07-29 · Official documentation · verified
  2. Partial ReloadsInertia.js · 2026-07-29 · Official documentation · verified
  3. Deferred PropsInertia.js · 2026-07-29 · Official documentation · verified
  4. DeploymentLaravel · 2026-07-29 · Official documentation · verified