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: truesends them to the server as a single request instead.- A Livewire placeholder must share the component's root element type, so a
divplaceholder requires adivcomponent, 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 withonly, andInertia::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.
| Option | Requests per page view | Worker occupancy | Best when |
|---|---|---|---|
| Eager, no lazy or defer | One | One, held longer | Everything on the page is needed and cheap |
#[Defer], isolated | One per component, in parallel | Many, briefly | Components differ wildly in cost and page speed matters |
#[Lazy], isolated | One per visible component | Many, briefly | Most components are below the fold |
bundle: true | One for the group | One, held longer | The 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
- Lazy LoadingLivewire · 2026-07-29 · Official documentation · verified
- Partial ReloadsInertia.js · 2026-07-29 · Official documentation · verified
- Deferred PropsInertia.js · 2026-07-29 · Official documentation · verified
- DeploymentLaravel · 2026-07-29 · Official documentation · verified