Load non-critical code when content approaches the viewport
Use viewport-aware loading to fetch components, embeds, and feature code shortly before they become visible instead of shipping them on first load.
- Use Intersection Observer to start loading code before an offscreen section becomes visible
- Reserve space with placeholders or skeletons so deferred sections do not cause layout shift
- Tune `rootMargin` so heavy content loads early enough without competing with above-the-fold work
- Use this for charts, reviews, related products, embeds, and offscreen dashboards
Rule Details
Import-on-visibility starts loading code when an offscreen section is close enough to matter. Patterns.dev's import-on-visibility pattern (opens in new tab) and Intersection Observer (opens in new tab) make it a practical way to keep the initial page lean without making later sections feel unfinished.
Code Examples
Plain JavaScript with Intersection Observer
const mountPoint = document.querySelector('#reviews')
let loaded = false
const observer = new IntersectionObserver(
async ([entry]) => {
if (!entry.isIntersecting || loaded) return
loaded = true
observer.disconnect()
const { mountReviews } = await import('./reviews.js')
mountReviews(mountPoint)
},
{ rootMargin: '400px 0px' }
)
observer.observe(mountPoint)React Example
import { useEffect, useRef, useState } from 'react'
export function DeferredChart() {
const containerRef = useRef<HTMLDivElement | null>(null)
const [Chart, setChart] = useState<null | React.ComponentType>(null)
useEffect(() => {
const node = containerRef.current
if (!node) return
const observer = new IntersectionObserver(
async ([entry]) => {
if (!entry.isIntersecting) return
observer.disconnect()
const mod = await import('./sales-chart')
setChart(() => mod.SalesChart)
},
{ rootMargin: '500px 0px' }
)
observer.observe(node)
return () => observer.disconnect()
}, [])
return (
<div ref={containerRef} style={{ minHeight: 320 }}>
{Chart ? <Chart /> : <ChartSkeleton />}
</div>
)
}Why It Matters
- Reduced first-load cost: Offscreen components do not compete with critical content during the initial route load.
- Better scroll journeys: Heavy sections can load just before the user reaches them instead of blocking first paint.
- Controlled timing:
rootMarginlets you decide how early to fetch based on the weight of the deferred component, which is why this pattern complements lazy loading instead of replacing it. - Better fit for long pages: Marketing pages, dashboards, feeds, and article templates often contain expensive sections well below the fold.
Implementation Guidance
- Use a placeholder or skeleton with reserved height so the section does not shift layout when it loads.
- Start with a
rootMarginbetween300pxand1000pxfor heavier modules, then tune it based on asset weight and scroll speed. - Disconnect the observer after loading so the import runs once.
- Keep accessibility intact: headings, landmarks, and focus order should still make sense before the module hydrates, and PageSpeed Insights (opens in new tab) is a good way to verify the route-level impact afterward.
Verification
Use the Chrome DevTools Performance panel (opens in new tab) or a throttled waterfall trace to confirm the deferred code leaves the initial path and then arrives early enough during scroll.
- Confirm the deferred section's code is not part of the initial route bundle or first-load request set.
- Scroll through the page on a throttled mobile profile and verify the module request starts before the section enters the viewport.
- Check that the placeholder reserves space so the section loads without causing CLS.
- Tune
rootMarginuntil the section appears ready when reached, typically somewhere between300pxand1000pxbefore visibility for heavier modules. - Re-measure the route and confirm initial JS cost, main-thread work, or page weight improves without introducing visible loading jank later in the scroll.
Use with AI
Copy these prompts to use with your AI assistant, or install the MCP server to use directly from Claude, Cursor, or Windsurf.
Check
Verify implementation
Identify components, widgets, or modules that load on first render even though they only appear after the user scrolls. Flag which ones can switch to visibility-based loading.
Fix
Auto-fix issues
Use Intersection Observer or an equivalent framework primitive to load the offscreen component or dependency when it approaches the viewport, while preserving layout stability and a clear placeholder.
Explain
Learn more
Explain import-on-visibility, how it differs from import-on-interaction, and how viewport-based loading helps large pages stay fast.
Review
Code review
Inspect scroll-triggered sections, embeds, charts, recommendation modules, and long landing pages. Flag code that is bundled eagerly even though the corresponding UI stays offscreen until later, and verify the deferred version still loads before the user reaches it.