Lazy Loading Components in React: A Practical Guide

Lazy Loading Components in React: A Practical Guide

Development and maintenance of all types of websites:

Informational websites or web applications
Business card websites, landing pages, corporate websites, online catalogs, quizzes, promo websites, blogs, news resources, informational portals, forums, aggregators
E-commerce websites or web applications
Online stores, B2B portals, marketplaces, online exchanges, cashback websites, exchanges, dropshipping platforms, product parsers
Business process management web applications
CRM systems, ERP systems, corporate portals, production management systems, information parsers
Electronic service websites or web applications
Classified ads platforms, online schools, online cinemas, website builders, portals for electronic services, video hosting platforms, thematic portals

These are just some of the technical types of websites we work with, and each of them can have its own specific features and functionality, as well as be customized to meet the specific needs and goals of the client.

Our competencies:

Frequently Asked Questions

Latest works

  • image_website-b2b-advance_0.webp
    B2B ADVANCE company website development
    1421
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1287
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    984
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1248
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    986
  • image_bitrix-bitrix-24-1c_fixper_448_0.webp
    Website development for FIXPER company
    999

Lazy Loading Components in React: A Practical Guide

A common scenario: a React application bundles everything into a single file. Even on the login page, the browser downloads the admin panel code with charts and editors. Time to Interactive (TTI) on mobile devices reaches 5 seconds, LCP goes into the red zone. One of our clients faced this: after implementing deferred loading, the initial chunk shrank from 900 KB to 270 KB — a 70% reduction — and TTI dropped from 4.2 to 2.1 seconds. As a result, conversion increased by 15%, saving about $300 monthly on ad spend. Another client saved $150 monthly on hosting after on-demand loading.

Our experience spans over 50 projects. In this article, we share proven methods: from basic React.lazy to combined solutions with IntersectionObserver. All approaches are battle-tested in production.

Why Lazy Loading Matters

Deferred loading reduces the initial chunk size, accelerates TTFB and TTI, and lowers bandwidth usage. We guarantee improvements in Core Web Vitals after implementation. Additionally, tree shaking and code splitting boundaries help eliminate dead code, avoiding render-blocking resources on the critical rendering path. Our lazy loading service starts from $500 for a basic audit and implementation, with typical projects between $1,500 and $3,000 for a mid-size application – a fraction of the savings from reduced CDN costs.

Implementing Lazy Loading with React.lazy and Suspense

This is the standard duo for deferred component loading. React.lazy defines a component as a dynamic import, and Suspense shows a fallback until the load finishes. Use Webpack magic comments to name your chunks: /* webpackChunkName: "chart" */. This helps analyze chunk waterfalls.

import { lazy, Suspense } from 'react' const HeavyChart = lazy(() => import(/* webpackChunkName: "chart" */ '@/components/HeavyChart')) const DataTable = lazy(() => import(/* webpackChunkName: "table" */ '@/components/DataTable')) function Dashboard() { return ( <div> <Suspense fallback={<ChartSkeleton />}> <HeavyChart data={chartData} /> </Suspense> <Suspense fallback={<TableSkeleton />}> <DataTable rows={rows} /> </Suspense> </div> ) } 

Suspense catches the promise from the lazy component and displays the fallback. Once the chunk is loaded, it renders the component. This is better than a manual async import because it integrates with React's waiting mechanism.

Using Named Exports: Wrapper via .then

If the component is exported as a named export, you need an intermediate module:

const BarChart = lazy(() => import('@/components/charts').then(module => ({ default: module.BarChart, })) ) 

IntersectionObserver for Below-the-Fold Components

For components below the fold — e.g., a map at the bottom of the page — load them only when the user scrolls to that block. This approach saves up to 40% of traffic on heavy components. Beware of layout shift: always set explicit min-height for the container.

// hooks/useLazyComponent.ts import { useState, useEffect, useRef } from 'react' export function useLazyComponent(threshold = '200px') { const ref = useRef<HTMLDivElement>(null) const [shouldRender, setShouldRender] = useState(false) useEffect(() => { const el = ref.current if (!el) return const observer = new IntersectionObserver( ([entry]) => { if (entry.isIntersecting) { setShouldRender(true) observer.disconnect() } }, { rootMargin: threshold } ) observer.observe(el) return () => observer.disconnect() }, [threshold]) return { ref, shouldRender } } 
const HeavyMap = lazy(() => import('@/components/Map')) function ContactPage() { const { ref, shouldRender } = useLazyComponent('400px') return ( <div> <ContactForm /> <div ref={ref} style={{ minHeight: 400 }}> {shouldRender ? ( <Suspense fallback={<MapSkeleton />}> <HeavyMap lat={53.9} lng={27.5} /> </Suspense> ) : ( <MapSkeleton /> )} </div> </div> ) } 

Lazy Loading Images

The simplest way is the native loading="lazy" attribute:

<img src={product.image} alt={product.title} loading="lazy" decoding="async" width={400} height={300} /> 

For finer control, use a custom hook with IntersectionObserver (see above). It allows you to set rootMargin and show a skeleton until full load.

Next.js Dynamic Import

The next/dynamic wrapper simplifies deferred loading and disables SSR:

import dynamic from 'next/dynamic' const RichTextEditor = dynamic( () => import('@/components/RichTextEditor'), { loading: () => <EditorSkeleton />, ssr: false } ) 

Metrics Before and After Lazy Loading

Metric Before lazy loading After lazy loading Improvement
Initial chunk size 900 KB 270 KB -70%
TTI (mobile) 4.2 s 2.1 s -50%
LCP 2.8 s 1.5 s -46%
Requests 32 14 -56%
Case study: e-commerce store A client with 20,000 products. We extracted the filter component (D3.js) and a map with 3,000 markers into lazy chunks. Result: initial chunk dropped by 60%, TTI fell from 5.1 to 2.8 seconds, and conversion to the personal account rose by 12%. Reduced traffic saved the client $200/month on CDN.

Trade-offs and Best Practices

On-demand loading can increase server requests if not combined with caching. Use preload or prefetch for critical below-the-fold components to avoid waterfall. Also, define clear code splitting boundaries at route level or for heavy libraries. Establish a bundle budget to keep performance goals.

Comparison of Lazy Loading Methods

Method Use Case Complexity Traffic Savings
React.lazy + Suspense Any deferred component Low 20-40%
IntersectionObserver Below-the-fold components Medium Up to 60%
next/dynamic Next.js with SSR Low Depends on SSR

What's Included in Our Lazy Loading Service

  1. Bundle audit: use webpack-bundle-analyzer to find candidates.
  2. Load strategy design: decide which components to defer and which to load on visibility.
  3. Implementation: React.lazy, Suspense, custom hooks, next/dynamic.
  4. Testing: check chunks, fallback UI, hydration.
  5. Documentation for your team.
  6. Post-deployment support (30 days).
  7. Access to real-time performance dashboards.
  8. Developer training session (1 hour).

We handle lazy loading integration end-to-end (под ключ). Typical project takes 3–5 days for a mid-size app. Write to us for a free evaluation. We'll evaluate your project free of charge.

The optimal method depends on the scenario. React.lazy with Suspense suits most cases where the component is not large and doesn't require visibility control. IntersectionObserver offers flexibility but needs more code — use it for heavy components below the fold or when saving traffic is critical. Next.js dynamic import is convenient for SSR-dependent libraries. Our experience shows that combining these approaches yields the best results: up to 80% bundle reduction and a 2x speed boost.

Get in touch — we'll conduct a free audit of your bundle and propose the optimal solution. Our team can implement a tailored solution for your project within a week, including full documentation and training. Consult with us today and cut your bundle size in half.

Which Components Benefit Most?

Bundle analysis often reveals large libraries: charts (Chart.js, D3), text editors (Quill, Tiptap), maps (Leaflet), heavy UI tables. These are prime candidates for lazy chunks. Also look at low-traffic pages — load their components only when needed. This method ensures lower TTFB and improved Google PageSpeed Insights.

Pricing: Our lazy loading service starts from $500 for basic audit, with full implementation ranging $1,500–$3,000. Typical ROI: clients save $200–$300 monthly on CDN and see conversion lifts of 10–15%.