π§ Introduction:
Ever wondered why your React app takes a hit on performance metrics like LCP or TTI? The culprit is often the sheer volume of code and resources being loaded up front. That's where lazy loading becomes a game-changer. It allows you to defer the loading of components, images, and routes until theyβre actually needed β speeding up initial load and improving user experience.
In this post, weβll dive deep into:
- Component-level lazy loading with
React.lazy()
- Lazy loading routes in Next.js App Router
- Image optimization using the
next/image
component - Scroll-triggered rendering using
IntersectionObserver
- Best practices for fallback UI like skeleton loaders
βοΈ 1. Component-Level Lazy Loading in React
You can lazy load components using React.lazy()
combined with Suspense
. This defers loading until the component is actually rendered.
import React, { Suspense } from 'react';
const LazySection = React.lazy(() => import('./Section'));
export default function Home() {
return (
<Suspense fallback={<div>Loading...</div>}>
<LazySection />
</Suspense>
);
}
β Best Practices:
- Use meaningful fallbacks (like skeleton loaders).
- Avoid lazy loading above-the-fold components.
π 2. Route-Level Lazy Loading in Next.js (App Router)
With Next.js 13+ and the App Router, each route is lazy loaded by default. However, you can still enhance it with loading UI.
Example folder structure:
/app
/dashboard
page.tsx
loading.tsx
When /dashboard
is being loaded, loading.tsx
is rendered automatically.
πΌοΈ 3. Lazy Loading Images
Use the native loading="lazy"
attribute for static images or the Next.js Image component for advanced optimization.
import Image from 'next/image';
<Image
src="/banner.png"
alt="Banner"
width={1200}
height={600}
placeholder="blur"
loading="lazy"
/>
π Why it matters:
- Reduces initial page size
- Prevents layout shifts
- Boosts Core Web Vitals (LCP, CLS)
π 4. Lazy Rendering with IntersectionObserver
Use this for animations, API calls, or entire sections that should only appear when scrolled into view.
const observer = new IntersectionObserver(callback, options);
observer.observe(targetElement);
Or use libraries like react-intersection-observer
for cleaner React integration:
import { useInView } from 'react-intersection-observer';
const { ref, inView } = useInView();
return (
<div ref={ref}>
{inView ? <ExpensiveComponent /> : <Skeleton />}
</div>
);
π§± 5. Skeleton Loaders for Better UX
Using loading placeholders improves perceived performance. You can implement a basic skeleton using TailwindCSS or packages like react-loading-skeleton
.
<div className="animate-pulse bg-gray-200 h-6 w-3/4 rounded"></div>
π Conclusion
Lazy loading is more than just a performance tweak β it's a core optimization strategy. By deferring heavy resources and non-critical components, you not only improve speed but also give your users a better experience.
π Coming Next (Part 4):
Error Boundaries & Handling Unexpected Failures Gracefully
Top comments (0)