Skip to main content
Beta: Front-End Checklist is currently in beta. Some issues are still being fixed. Thanks for your patience.

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.

Utilities
Quick take
Typical fix time 20 min
  • 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
Why it matters: Many pages ship JavaScript for offscreen sections that a user may never reach. Import-on-visibility keeps the initial route lighter while still loading content early enough to feel ready when users scroll to it.

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: rootMargin lets 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 rootMargin between 300px and 1000px for 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.

  1. Confirm the deferred section's code is not part of the initial route bundle or first-load request set.
  2. Scroll through the page on a throttled mobile profile and verify the module request starts before the section enters the viewport.
  3. Check that the placeholder reserves space so the section loads without causing CLS.
  4. Tune rootMargin until the section appears ready when reached, typically somewhere between 300px and 1000px before visibility for heavier modules.
  5. 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.

Sources

References used to support the guidance in this rule.

Further Reading

Tools and supplementary material for exploring the topic in more depth.

PageSpeed Insights
pagespeed.web.devTool

Rules that often go hand-in-hand with this one.

Implement lazy loading for offscreen content

Images and heavy resources below the fold are lazy loaded to improve initial performance.

Performance
Use resource hints for faster loading

Implement preload, prefetch, and preconnect hints to optimize resource loading priority.

Performance
Load non-critical code on user interaction

Defer JavaScript modules, widgets, and third-party code until the user signals intent through a click, focus, hover, or similar interaction.

Performance
Disable lazy loading for above-the-fold content

Detects lazy loading on likely above-fold images to improve Largest Contentful Paint (LCP)

Performance

Was this rule helpful?

Your feedback helps improve rule quality. This stays internal for now.

Loading feedback...
0 / 385