A guest in someone else’s bundle
A widget grafted into a host app inherits the host’s existing payload and its performance budget, so the number that matters is your marginal cost to a page you did not write — and code splitting is how you keep that number near zero until the surface is opened.
Your surface has a size. That number is not the number anybody will judge you on.
The page your panel is going into already has a bundle, an LCP, a CLS and an INP, and every one of those is attributed to the host’s origin. The host team has a budget they set before you existed. What they are buying from you is not a package. It is a delta — the difference between their page with your surface and their page without it. Quote them anything else and you are answering a question they did not ask.
The number is a subtraction
This course’s budget reference already states the rule for the marginal-payload row, and it is worth quoting back at yourself before every integration: the number that counts is “the delta to the host’s initial chunk, not the size of your package.” It is measured by running a bundle analyzer against the host build with and without your surface.
The subtraction matters because it can go either way from what you expect. Your surface might weigh a lot and add almost nothing, because the host already ships React, already ships a markdown parser, already ships the icon set. Or it might be small and add all of it, because the host ships none of those and your peer dependencies are suddenly real dependencies. You cannot know which without running the host build twice.
That is also why the same surface has a different number in every portfolio company, and why a single figure on your README is a liability. What you can carry between integrations is the method, not the result.
Where people get burned
There is no published standard for what this delta should be. The budget reference says so explicitly and tells you not to invent one. The threshold is whatever the host team agrees to in writing, expressed as compressed transfer size for the initial chunk. A borrowed number from a blog post about somebody else’s widget is not a budget, and agreeing to it commits you to a target you have no reason to believe is right for this page.
Deferring is not deleting
The tool for getting the delta near zero is code splitting, and the thing to be precise about is what it does. From react.dev’s lazy reference: it “lets you defer loading component’s code until it is rendered for the first time.” Defer. The bytes still exist, they still get downloaded when the panel opens, and they still get parsed. What changes is that they are no longer in the chunk the host’s first paint is waiting behind.
The shape is four lines, and every one of them matters:
import { lazy, Suspense } from 'react'
// Top level of the module. Not inside a component.
const AgenticPanel = lazy(() => import('./agentic-panel'))
// The host's initial chunk pays nothing for AgenticPanel
// until this actually renders.
<Suspense fallback={null}>
{open ? <AgenticPanel /> : null}
</Suspense>React’s reference names the two constraints directly. You “need to wrap the lazy component or any of its parents into a <Suspense> boundary to specify what should be displayed while it is loading.” And the mechanism “relies on dynamic import(), which might require support from your bundler or framework” — which in a host build you did not write is not a formality. It is the first thing to check, because a host on an older or unusual build setup can turn your dynamic import back into a static one without telling you.
Where people get burned
The trap in that snippet is the comment. React states it plainly: “Always declare lazy components at the top level of your module, not inside other components. Declaring lazy components inside other components will cause all state to be reset on re-renders.” On a streaming surface that failure is spectacular and confusing — the transcript clears itself mid-answer — and it will read as a state-management bug for as long as it takes somebody to notice where the lazy call lives.
Why “near zero” has to mean the initial chunk
Keeping your code out of the host’s initial chunk buys something specific, and it is a metric from earlier in this course rather than a vague good. The LCP article (web.dev, updated 4 September 2025) measures the render time of the largest content element in the viewport. If your surface’s JavaScript is downloaded, parsed and evaluated before the host’s largest element paints, it is competing for the main thread during exactly the window LCP is watching. Move it behind a dynamic import and it is not in that window at all.
That is the honest scope of the win. It does not make your surface cheaper. It moves your cost from a moment when the host is being graded to a moment when the user has deliberately asked for you. Both of those are still your cost, and the second one has its own budget: the panel that takes two seconds to appear after a click has traded an LCP problem for an interaction problem.
Check your recall
Answer from memory — no scrolling back.
Retrieval check
The host team asks for one number that covers your surface for every future release. What do you tell them?
Check your answer
That the number is not portable, and offer them a method plus a threshold instead. The delta depends on what the host already ships, so the same build of your surface genuinely has a different marginal cost in each of their products. What travels is the measurement procedure — analyzer against their build, with and without, initial chunk only, compressed — and a written threshold for the delta that you both agree to and that their CI can check.
The version of this that fails is agreeing to a single figure to end the meeting. You will meet it in the product where the host already ships everything you depend on, and miss it in the product where they ship none of it, and neither outcome will have told anyone anything about whether your surface got heavier.
Hands on
State your cost as a delta
Done when: MEASUREMENTS.md records the marginal payload as a before/after pair of initial-chunk sizes from the same host build, compressed, plus the first-open cost of the deferred chunk — with the build mode and analyzer named next to both.
- Pick the host. If no portfolio product is available to you yet, use this repo as the host and the flight chatbot panel as the guest — the arithmetic is the same and the habit is the point.
- Build the host without your surface and record the initial chunk size, compressed. Name the analyzer and the build mode in the file. A development build is not a measurement.
- Add the surface with a static import, rebuild, record the initial chunk size again. Subtract. That is your naive delta, and it is the number you would be reporting if you did nothing else.
- Convert the import to
lazywith a<Suspense>boundary, keeping thelazycall at module top level. Rebuild and record the initial chunk a third time. - Record the size of the deferred chunk separately, and label it as first-open cost rather than folding it into the delta. This is the honesty step: the bytes did not disappear and the file should not imply they did.
- Open the panel with the network throttled and watch what the user sees between the click and the panel appearing. If the
fallbackisnulland the chunk takes a moment, you have shipped a button that appears to do nothing. - Bring the three initial-chunk numbers and the deferred-chunk size into the chat. I will push back if the three builds were not the same build mode, or if the deferred chunk has been quietly counted as a saving.
What this does not cover
Everything here is about bytes arriving. Nothing here is about what happens once they have arrived and the surface is running — and a panel with a perfect marginal payload can still be the reason the host’s INP is poor, because a long transcript costs the main thread on every frame regardless of how it got there.
That is the rest of this module. The virtualizing-a-long-conversation lesson takes DOM size, which is the presentation-delay half of the problem your profile most likely already named. The cost-of-parsing-every-token lesson takes the work you repeat per chunk. The render frequency underneath all of it — how often React is asked to do anything at all — belongs to the React module and its keeping-the-stream-out-of-state lesson, which attacks input delay rather than presentation delay.
And the panel’s own loading behaviour past the size question — when to open the connection, what to show while the model thinks, how to recover a stream after a refresh — is the Streaming interfaces course. This one stops at the number.
Read this next — primary source
lazyreact.dev — free; page current at 5 September 2026. Meta documenting its own library.
This lesson takes the definition, the Suspense requirement and the top-level declaration rule. The full reference is worth reading for the parts a lesson has to compress: what happens on a failed import, how lazy interacts with Suspense boundaries you did not place yourself, and the specific reason declaring lazy inside a component resets state. That last one is a correctness bug wearing a performance costume, and it is easier to recognise if you have read why it happens.
Stuck, curious, or think this lesson is wrong? Ask your teaching agent. The lessons are the scaffold; the conversation is where the learning gets unstuck.