DEV Community

Ivan Rossouw
Ivan Rossouw

Posted on AI-assisted

Lazy Loading Rich Media by Removing It Until Intent

“Lazy” media can still arrive too early.

Consider an instructional page built from reusable components. A helpful video appears near the top, followed by the written guide. The player is configured to preload metadata rather than the complete file, so the implementation seems conservative. Yet the browser receives a live media source as soon as the component renders. The player also occupies valuable space in the first viewport, pushing the guide below the fold.

The deeper lesson is not about a particular preload value. It is about choosing a boundary the component actually controls: do not create the expensive media element until the reader expresses intent.

The problem hides in the DOM

A stopped player is still a player. A playback flag can prevent automatic playback, but if the video element and source are present, layout and preload behaviour remain partly in the browser’s hands.

That matters in two ways. First, every eligible page can become free to request video metadata, even for readers who never press play. Second, the player receives visual priority simply by existing near the top of the document.

The media may be useful, but usefulness does not require eager presence. The idle interface can communicate that playback is an option without crossing the heavier media boundary.

Model two honest states

Treat the component as two observable states.

In the idle state, render an accessible poster button. Do not render a video element or a source. After an explicit click, replace the teaser with the real player and allow loading to begin.

This is stronger than toggling controls on an existing player. The DOM itself now tells the truth about the component’s state: before intent there is no player; after intent there is one.

The model also produces a crisp invariant:

No player before interaction; a real player after interaction.

Simple invariants are valuable because implementation, accessibility, and testing can all align around them.

A generalized Blazor example

A small Blazor component can express the boundary directly:

@if (HasMedia)
{
    if (!isPlaying)
    {
        <button type="button"
                aria-label="@($"Play video for {Title}")"
                @onclick="Play">
            <img src="@PosterUrl" alt="" />
        </button>
    }
    else
    {
        <video controls autoplay aria-label="@Title">
            <source src="@VideoUrl" type="video/mp4" />
        </video>
    }
}

@code {
    [Parameter] public string ContentId { get; set; } = "";
    [Parameter] public string Title { get; set; } = "";
    [Parameter] public string? PosterUrl { get; set; }
    [Parameter] public string? VideoUrl { get; set; }

    private bool isPlaying;
    private string? previousContentId;

    private bool HasMedia =>
        !string.IsNullOrWhiteSpace(VideoUrl) &&
        !string.IsNullOrWhiteSpace(PosterUrl);

    private void Play() => isPlaying = true;

    protected override void OnParametersSet()
    {
        if (previousContentId != ContentId)
        {
            isPlaying = false;
            previousContentId = ContentId;
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

This is intentionally generalized. Production code may add format alternatives, error handling, focus management, analytics, or a custom player. The important property is unchanged: the source is absent from the idle branch.

The poster should be treated as presentation inside a labelled button, or given meaningful alternative text if it contributes information. Avoid exposing route fragments or technical identifiers as accessible names; use a human-readable title.

Reset state when identity changes

Reusable components can survive navigation while their parameters change. If the current media is playing and the content identity changes, stale state can leak into the next view.

Resetting the playback flag when that identity changes prevents a new page from inheriting the old page’s active player. It also restores the same predictable starting point for every item.

Missing configuration deserves an explicit branch too. When no source is configured for that content, render no player and no dead teaser. “Nothing” is often a better empty state than a control that cannot succeed.

Test the boundary readers experience

Rendered-component tests can verify behaviour without reaching into private fields.

For the initial render, assert that no video element exists and that the poster button is present. Trigger the button. Then assert that the teaser is gone and a video source exists. Add cases for missing media configuration, a content-identity change, and a human-readable accessible label.

Document order is worth preserving as well. A structural test can confirm that the heading, media card, and guide body remain in the intended sequence.

These checks prove output and interaction state. They do not prove network savings or faster rendering.

Accept the trade-off deliberately

Interaction gating introduces explicit state, content-identity reset behaviour, accessible teaser semantics, a click-triggered autoplay handoff, and more test cases.

In return, the written guide regains visual priority, people who never choose playback avoid eager video work, and the load boundary becomes easy to inspect. Whether that trade is right depends on the page. A video-first experience may reasonably prioritize immediate player availability; a text-first guide often benefits from the gate.

The poster still loads as a separate image request. This is not “zero media.” Its value depends on the poster’s cost relative to the player and video metadata.

Keep performance claims narrow

No fresh performance benchmark was run. The available evidence supports claims about rendered states: the initial markup omits the player and source, and interaction introduces them. It does not establish bytes saved, startup-time improvement, cache behaviour, or production outcomes.

That distinction matters. A sound engineering lesson can be useful without becoming an inflated performance claim.

The practical takeaway is simple: for optional rich media, lazy loading is strongest when the expensive element does not exist before user intent. Model the states explicitly, reset them when identity changes, and test the public boundary.

Top comments (0)