Lazy Loading Components Implementation

Our company is engaged in the development, support and maintenance of sites of any complexity. From simple one-page sites to large-scale cluster systems built on micro services. Experience of developers is confirmed by certificates from vendors.
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:
Development stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1215
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    852
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1043
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    823
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Website development for FIXPER company
    815

Implementing Lazy Loading of Components on a Website

Lazy loading is deferred loading of components, images, or modules until the moment they're actually needed by the user. Reduces initial bundle size, speeds up Time to Interactive, and lowers traffic on mobile devices.

Two Levels of Lazy Loading

Module level — component JavaScript code loads only on first render. Implemented via dynamic import().

Resource level — images, iframes, video load only when element enters visible area. Implemented via loading="lazy" attribute or IntersectionObserver.

React.lazy and Suspense

// Without lazy loading — all code in main bundle
import HeavyChart from '@/components/HeavyChart'
import DataTable from '@/components/DataTable'

// With lazy loading — separate chunks, load on demand
import { lazy, Suspense } from 'react'

const HeavyChart = lazy(() => import('@/components/HeavyChart'))
const DataTable = lazy(() => import('@/components/DataTable'))

function Dashboard() {
  return (
    <div>
      <Suspense fallback={<ChartSkeleton />}>
        <HeavyChart data={chartData} />
      </Suspense>

      <Suspense fallback={<TableSkeleton />}>
        <DataTable rows={rows} />
      </Suspense>
    </div>
  )
}

Suspense intercepts promise thrown by lazy component during loading and shows fallback. Once chunk loads — renders component.

Named Exports with Lazy

React.lazy works only with default export. For named exports, use wrapper:

// If component exported as named export
const BarChart = lazy(() =>
  import('@/components/charts').then(module => ({
    default: module.BarChart,
  }))
)

Conditional Loading: Only When Visible

Loading heavy component immediately on page mount isn't always needed. If component is below fold, defer loading until scroll:

// 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

// Native lazy loading — supported by all modern browsers
function ProductCard({ product }: { product: Product }) {
  return (
    <div>
      <img
        src={product.image}
        alt={product.title}
        loading="lazy"
        decoding="async"
        width={400}
        height={300}
      />
    </div>
  )
}

For finer control — custom hook with IntersectionObserver:

function useLazyImage(src: string) {
  const imgRef = useRef<HTMLImageElement>(null)
  const [loaded, setLoaded] = useState(false)

  useEffect(() => {
    const img = imgRef.current
    if (!img) return

    const observer = new IntersectionObserver(
      ([entry]) => {
        if (entry.isIntersecting) {
          img.src = src
          img.onload = () => setLoaded(true)
          observer.disconnect()
        }
      },
      { rootMargin: '100px' }
    )

    observer.observe(img)
    return () => observer.disconnect()
  }, [src])

  return { imgRef, loaded }
}

Next.js: dynamic Import

import dynamic from 'next/dynamic'

// With custom loading state
const RichTextEditor = dynamic(
  () => import('@/components/RichTextEditor'),
  {
    loading: () => <EditorSkeleton />,
    ssr: false, // don't render on server — relevant for window-dependent components
  }
)

// Only on condition
const AdminPanel = dynamic(() => import('@/components/AdminPanel'), { ssr: false })

function Page({ isAdmin }: { isAdmin: boolean }) {
  return isAdmin ? <AdminPanel /> : <UserView />
}

Vite: Bundle Analysis

To understand what's worth lazy loading:

# Install plugin
npm install --save-dev rollup-plugin-visualizer

# vite.config.ts
import { visualizer } from 'rollup-plugin-visualizer'

export default {
  plugins: [
    visualizer({ open: true, gzipSize: true, filename: 'dist/stats.html' }),
  ],
}

After npm run build, interactive bundle map opens. Look for large dependencies: chart libraries, rich text editors, date pickers, map SDKs — first candidates for lazy loading.

Preload Critical Chunks

Chunks almost certainly needed can prefetch in idle time:

// Prefetch on link hover
function NavLink({ href, chunkImport, children }) {
  const handleMouseEnter = () => {
    chunkImport() // () => import('@/pages/About')
  }
  return <a href={href} onMouseEnter={handleMouseEnter}>{children}</a>
}

Timeline

Setting up React.lazy + Suspense for existing components — 0.5 day. Full bundle audit, prioritization, intersection-based loading with skeleton placeholders — 2–3 days depending on component count.