Create Smart Lazy Rendering Components in React with Intersection Observer

Modern React applications often struggle with performance when rendering large dashboards, infinite feeds, analytics panels, media-heavy pages, or long ecommerce catalogs. Rendering everything at once increases memory usage, slows down page interaction, and hurts Core Web Vitals.

A practical solution is lazy rendering. Instead of mounting all components immediately, you only render components when they become visible inside the viewport.

React developers commonly use lazy loading for images, but the same idea works for entire React components.

In this guide, you will learn how to create smart lazy rendering components in React using the Intersection Observer API.

Why Traditional Rendering Becomes a Problem

Imagine a dashboard page containing:

  • 20 chart widgets
  • Multiple API requests
  • Heavy table components
  • Video previews
  • Interactive graphs

If React renders everything during the initial load:

  • JavaScript execution time increases
  • Main thread blocks longer
  • Initial paint becomes slower
  • Hydration time increases
  • Mobile devices struggle badly

Google Lighthouse frequently flags these problems in large React applications.

Companies like Netflix, YouTube, and Amazon aggressively delay offscreen rendering to reduce startup costs.

What is Intersection Observer?

The Intersection Observer API is a browser API that detects when an element enters or exits the viewport.

Instead of continuously listening to scroll events, the browser efficiently handles visibility tracking internally.

This makes it far more performant than traditional scroll event listeners.

Typical use cases include:

  • Infinite scrolling
  • Lazy image loading
  • Ad impression tracking
  • Animation triggers
  • Deferred component rendering

Basic Intersection Observer Example

Before integrating with React, understand the core API.

const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
console.log(“Element is visible”);
}
});
});const element = document.querySelector(“#target”);observer.observe(element);

Here:

  • entries contain visibility information
  • isIntersecting becomes true when visible
  • observe() starts tracking an element

Creating a Smart Lazy Render Component in React

Now let’s build a reusable React component that renders children only when visible.

Step 1: Create the LazyRender Component

import React, { useEffect, useRef, useState } from “react”;function LazyRender({ children, height = “200px” }) {
const containerRef = useRef(null);
const [isVisible, setIsVisible] = useState(false);useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setIsVisible(true);
observer.disconnect();
}
},
{
threshold: 0.1,
}
);if (containerRef.current) {
observer.observe(containerRef.current);
}

return () => observer.disconnect();
}, []);

return (

{isVisible ? children : null}

);
}

export default LazyRender;

How This Component Works

The component follows a simple rendering strategy:

  1. Create a placeholder container
  2. Observe viewport visibility
  3. Render children only when visible
  4. Disconnect observer after rendering

This prevents unnecessary component mounting during initial page load.

Using the LazyRender Component

import LazyRender from “./LazyRender”;
import HeavyChart from “./HeavyChart”;

function Dashboard() {
return (

Analytics Dashboard

 

);
}

import LazyRender from “./LazyRender”;
import HeavyChart from “./HeavyChart”;

function Dashboard() {
return (

Analytics Dashboard

 

);
}

The HeavyChart component now mounts only when users scroll near it.

Adding Loading Placeholders

Blank spaces create a poor user experience. Skeleton placeholders work much better.

function LazyRender({
children,
placeholder,
height = “200px”,
}) {
const containerRef = useRef(null);
const [isVisible, setIsVisible] = useState(false);

useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setIsVisible(true);
observer.disconnect();
}
},
{
threshold: 0.1,
}
);

if (containerRef.current) {
observer.observe(containerRef.current);
}

return () => observer.disconnect();
}, []);

return (

{isVisible ? children : placeholder}

);
}

function LazyRender({
children,
placeholder,
height = “200px”,
}) {
const containerRef = useRef(null);
const [isVisible, setIsVisible] = useState(false);

useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setIsVisible(true);
observer.disconnect();
}
},
{
threshold: 0.1,
}
);

if (containerRef.current) {
observer.observe(containerRef.current);
}

return () => observer.disconnect();
}, []);

return (

{isVisible ? children : placeholder}

);
}

Usage

<LazyRender
height=”400px”
placeholder={

}
>

Preloading Before Visibility

You can render slightly before the component becomes visible.

This creates smoother scrolling experiences.

{
rootMargin: “200px”;
}

Updated observer:

const observer = new IntersectionObserver(callback, {
threshold: 0.1,
rootMargin: “200px”,
});

This starts rendering 200px before the element enters the viewport.

Optimizing Large Lists

Lazy rendering becomes extremely valuable in:

  • Social feeds
  • Product listings
  • Infinite scroll pages
  • Video platforms
  • Large documentation sites

Without lazy rendering, React may mount hundreds of components unnecessarily.

Even if hidden below the fold, React still performs:

  • Virtual DOM creation
  • Hook initialization
  • State setup
  • Event binding
  • Memory allocation

Combining with React.lazy

You can combine viewport rendering with code splitting.

import React, { lazy, Suspense } from “react”;

const HeavyChart = lazy(() => import(“./HeavyChart”));

Then:

<LazyRender
placeholder={

Loading chart…

}
>

Loading module…

}>

This creates two levels of optimization:

  • Component mounts only when visible
  • JavaScript bundle loads only when required

Preventing Observer Memory Leaks

One common mistake is forgetting to clean up.

Always disconnect observers inside cleanup functions.

Without cleanup:

  • Observers remain active
  • Memory usage increases
  • Performance degrades over time

When Lazy Rendering Should NOT Be Used

Do not lazy render:

  • Critical above-the-fold content
  • Primary navigation
  • SEO-critical server-rendered content
  • Accessibility-critical elements

Search engines and users should immediately access important content.

SSR and Next.js Considerations

In Next.js applications, lazy rendering works best for client-side interactive sections.

For example:

  • Analytics widgets
  • Comments sections
  • Recommendation engines
  • Charts
  • Media galleries

Be careful with SEO pages because delayed rendering may affect indexing if important content appears too late.

Real Performance Improvements

In production React applications, lazy rendering often improves:

  • Largest Contentful Paint (LCP)
  • Time to Interactive (TTI)
  • Total Blocking Time (TBT)
  • JavaScript execution time

A SaaS analytics dashboard with 35 widgets reduced initial render time from 4.8 seconds to 1.9 seconds after implementing viewport-based lazy rendering.

The biggest improvement came from preventing hidden chart libraries from mounting immediately.

Advanced Enhancement: Render Once Strategy

Sometimes components should remain mounted after visibility.

That is exactly what our implementation already does.

Once visible:

  • State remains preserved
  • Component stays mounted
  • Scrolling back does not recreate it

This avoids unnecessary remounting costs.

Alternative Libraries

Popular libraries include:

  • react-intersection-observer
  • react-lazyload
  • react-virtualized
  • react-window

However, building your own lightweight component often gives better control and a smaller bundle size.

The Intersection Observer is one of the most practical browser APIs for improving React performance.

Instead of rendering everything immediately, smart lazy rendering lets your application focus only on what users actually see.

This approach becomes especially powerful in large dashboards, e-commerce platforms, analytics systems, and content-heavy applications.

When combined with code splitting, memoization, and virtualization, lazy rendering can dramatically improve user experience without adding heavy dependencies.

LinkedIn
Facebook
Twitter
WhatsApp
Email
Print

About Author

Bikash Mahata

Bikash Mahata is a Frontend Architect with 18+ years of experience building scalable enterprise applications across banking and enterprise technology environments. He specialises in ReactJS, TypeScript, micro-frontend architecture, frontend performance optimisation, and scalable UI systems. Over the years, he has led frontend modernisation initiatives, designed reusable component platforms, and contributed to enterprise-scale frontend architecture focused on maintainability, scalability, and long-term product growth. His work combines practical engineering experience with modern frontend architecture principles to build reliable, performance-focused digital platforms.

1 thought on “Create Smart Lazy Rendering Components in React with Intersection Observer”

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top