Frontend Development 12 min read

Front‑end Performance Optimization: Lazy Loading Techniques (Route, Image, and Module)

This article introduces front‑end lazy‑loading strategies—including route lazy loading, three image lazy‑loading methods (native, scroll‑event, and IntersectionObserver), and module lazy loading—explaining their principles, showing complete React/Vue code examples, and demonstrating how they reduce initial load time and improve user experience.

Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Front‑end Performance Optimization: Lazy Loading Techniques (Route, Image, and Module)

Introduction

Hello, friends! I am 小羽同学 . In this performance‑optimization series we focus on 懒加载 (lazy loading) and its various applications. Lazy loading is a crucial front‑end technique that can significantly reduce the white‑screen time of a page and is widely applicable.

Route Lazy Loading

When using frameworks such as React or Vue, especially with Vite, each .tsx and .less file may be loaded upfront, causing a long initial load time. Route lazy loading extracts each route module into separate js and css files, loading them only when the route is visited.

In React we typically use lazy and Suspense :

import React, { lazy, Suspense } from 'react';
import { useRoutes, Navigate } from 'react-router-dom';

const LazyBrowser = lazy(() => import('@/pages/LazyBrowser'));
const LazyIntersectionObserver = lazy(() => import('@/pages/LazyIntersectionObserver'));
const LazyScroll = lazy(() => import('@/pages/LazyScroll'));

export default function Router() {
  let element = useRoutes([
    { path: '/lazy-browser', element:
, children: [] },
    { path: '/lazy-intersection-observer', element:
, children: [] },
    { path: '/lazy-scroll', element:
, children: [] }
  ]);
  return
loading...
}>{element}
;
}

The Suspense fallback shows a loading component while the route code is being fetched asynchronously.

Image Lazy Loading

Three common approaches are covered:

1. Browser‑Native Lazy Loading

Simply add loading="lazy" to the <img> tag. This requires no extra library but cannot handle placeholders or error images.

import React from 'react';
import { imageList } from '@/utils/imageList';
import './index.less';

export default function LazyBrowser() {
  return (
{imageList.map(item => (
))}
);
}

2. Scroll‑Event Based Lazy Loading

This method listens to the scroll event, calculates the element’s distance to the viewport using offsetTop , clientHeight , and scrollTop , and swaps the placeholder src with the real image when the element enters the visible area.

import React, { useEffect, useRef } from 'react';
import { imageList } from '@/utils/imageList';
import './index.less';

const loadingPath = location.href + '/images/loading.gif';

export default function LazyScroll() {
  const domRef = useRef([]);
  const lazyScrollRef = useRef(null);

  useEffect(() => {
    getTop();
    lazyScrollRef.current.addEventListener('scroll', getTop);
    return () => {
      if (lazyScrollRef.current) {
        lazyScrollRef.current.removeEventListener('scroll', getTop);
      }
    };
  }, []);

  const getTop = () => {
    let clientHeight = lazyScrollRef.current.clientHeight;
    let len = domRef.current.length;
    for (let i = 0; i < len; i++) {
      let { top } = domRef.current[i].getBoundingClientRect();
      if (top - clientHeight <= 0) {
        if (domRef.current[i].src === loadingPath) {
          domRef.current[i].src = domRef.current[i].dataset.src;
        }
      }
    }
  };

  return (
{imageList.map((item, index) => (
(domRef.current[index] = e)}
          data-src={item}
          src={loadingPath}
        />
      ))}
);
}

3. IntersectionObserver Based Lazy Loading

Using the modern IntersectionObserver API we can observe when an element enters the viewport and load the image only then. A custom hook useIntersectionObserver abstracts this logic.

import { useState, useEffect, useRef, useMemo } from 'react';

const useIntersectionObserver = (domRef) => {
  const [visible, setVisible] = useState(false);
  const intersectionObserver = useMemo(() =>
    new IntersectionObserver((entries, observer) => {
      entries.forEach(item => {
        if (item.isIntersecting) {
          setVisible(true);
          observer.disconnect();
        }
      });
    })
  , []);

  useEffect(() => {
    if (domRef.current) {
      intersectionObserver.observe(domRef.current);
    }
  }, [domRef.current]);

  useEffect(() => () => {
    intersectionObserver.disconnect();
  }, []);

  return visible;
};

export default useIntersectionObserver;

Usage example:

import React, { useRef } from 'react';
import { imageList } from '@/utils/imageList';
import useIntersectionObserver from '@/hooks/useIntersectionObserver';
import './index.less';

const loadingPath = location.origin + '/images/loading.gif';

const Item = ({ url }) => {
  const itemRef = useRef();
  const visible = useIntersectionObserver(itemRef);
  return (
{visible ?
:
}
);
};

export default function LazyIntersectionObserver() {
  return (
{imageList.map(item => (
))}
);
}

Module Lazy Loading

When a page contains many independent modules that each trigger rendering or network requests, loading them all at once can cause CPU spikes, long initialization, and unnecessary traffic. By applying the same IntersectionObserver‑based lazy‑loading principle to modules, we defer both rendering and data fetching until the module becomes visible.

import React, { useEffect, useState, useRef } from 'react';
import useIntersectionObserver from '@/hooks/useIntersectionObserver';
import axios from 'axios';
import { Spin } from 'antd';

export default function LazyModuleItem() {
  const itemRef = useRef(null);
  const visible = useIntersectionObserver(itemRef);
  const [loading, setLoading] = useState(true);
  const [data, setData] = useState('');

  const init = () => {
    setLoading(true);
    axios.get('https://api.uomg.com/api/comments.163?format=text')
      .then(res => {
        setLoading(false);
        setData(res.data);
      });
  };

  useEffect(() => {
    if (visible) {
      init();
    }
  }, [visible]);

  return (
{!visible || loading ?
:
{data}
}
);
}

As the user scrolls and a module enters the viewport, an API request is triggered, reducing server concurrency pressure, rendering load, and white‑screen time.

Conclusion

This article introduced three lazy‑loading techniques—route lazy loading, image lazy loading (native, scroll‑event, IntersectionObserver), and module lazy loading—each accompanied by functional React demos. Applying these methods can noticeably improve front‑end performance and user experience.

frontendperformance optimizationVueLazy Loadingintersection observer
Rare Earth Juejin Tech Community
Written by

Rare Earth Juejin Tech Community

Juejin, a tech community that helps developers grow.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.