Deferred loading patterns to optimize initial performance
Built-in browser lazy loading with loading attribute
<!-- Images lazy loading --> <img src="hero.jpg" alt="Hero image" loading="lazy" width="800" height="600" /> <!-- Iframe lazy loading --> <iframe src="https://youtube.com/embed/video-id" loading="lazy" width="560" height="315"> </iframe> <!-- Eager loading for above-the-fold --> <img src="above-fold.jpg" alt="Above fold image" loading="eager" fetchpriority="high" width="1200" height="400" />
JavaScript API for advanced lazy loading control
// Custom lazy loading hook
function useLazyLoading(rootMargin = '50px') {
const [isLoading, setIsLoading] = useState(true)
const [isVisible, setIsVisible] = useState(false)
const elementRef = useRef(null)
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setIsVisible(true)
setIsLoading(false)
observer.disconnect()
}
},
{ rootMargin }
)
if (elementRef.current) {
observer.observe(elementRef.current)
}
return () => observer.disconnect()
}, [rootMargin])
return { isLoading, isVisible, elementRef }
}
// Component usage
function LazyImage({ src, alt, ...props }) {
const { isVisible, elementRef } = useLazyLoading('100px')
return (
<div ref={elementRef} {...props}>
{isVisible ? (
<img src={src} alt={alt} />
) : (
<div className="placeholder">Loading...</div>
)}
</div>
)
}Code splitting and dynamic imports for React components
import { lazy, Suspense } from 'react'
// Dynamic import
const HeavyChart = lazy(() =>
import('./components/HeavyChart')
)
const Dashboard = lazy(() =>
import('./pages/Dashboard').then(module => ({
default: module.Dashboard
}))
)
// Usage with Suspense
function App() {
return (
<Suspense fallback={<ChartSkeleton />}>
<HeavyChart data={data} />
</Suspense>
)
}import dynamic from 'next/dynamic'
// Client-side only component
const ClientOnlyComponent = dynamic(
() => import('./ClientComponent'),
{ ssr: false }
)
// With custom loading
const Chart = dynamic(
() => import('./Chart'),
{
loading: () => <ChartSkeleton />,
ssr: false
}
)
// Conditional loading
const AdminPanel = dynamic(
() => import('./AdminPanel'),
{
loading: () => <div>Loading admin...</div>
}
)Splitting code by routes for optimal initial loading
// React Router with lazy loading
import { lazy, Suspense } from 'react'
import { Routes, Route } from 'react-router-dom'
const Home = lazy(() => import('./pages/Home'))
const About = lazy(() => import('./pages/About'))
const Dashboard = lazy(() => import('./pages/Dashboard'))
const Profile = lazy(() => import('./pages/Profile'))
function App() {
return (
<Suspense fallback={<PageSkeleton />}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/profile" element={<Profile />} />
</Routes>
</Suspense>
)
}
// Next.js App Router (automatic code splitting)
// app/dashboard/page.tsx - Automatically split
export default function DashboardPage() {
return <Dashboard />
}
// Manual optimization for heavy components
const HeavyDashboard = dynamic(
() => import('./components/HeavyDashboard'),
{
loading: () => <DashboardSkeleton />,
ssr: false
}
)Deferred loading of content and data
function useInfiniteScroll() {
const [items, setItems] = useState([])
const [loading, setLoading] = useState(false)
const [hasMore, setHasMore] = useState(true)
const loadMore = useCallback(async () => {
if (loading || !hasMore) return
setLoading(true)
try {
const newItems = await fetchItems(items.length)
setItems(prev => [...prev, ...newItems])
setHasMore(newItems.length > 0)
} finally {
setLoading(false)
}
}, [items.length, loading, hasMore])
const { elementRef } = useLazyLoading('200px')
useEffect(() => {
if (elementRef.current) loadMore()
}, [elementRef.current, loadMore])
return { items, loading, hasMore, elementRef }
}import { VariableSizeList as List } from 'react-window'
function VirtualList({ items }) {
const Row = ({ index, style }) => (
<div style={style}>
<ItemComponent item={items[index]} />
</div>
)
return (
<List
height={600}
itemCount={items.length}
itemSize={() => 80}
width="100%"
>
{Row}
</List>
)
}
// With lazy loading
function LazyVirtualList() {
const { items, loadMore, hasMore } = useInfiniteScroll()
return (
<InfiniteLoader
isItemLoaded={(index) => !!items[index]}
itemCount={hasMore ? items.length + 1 : items.length}
loadMoreItems={loadMore}
>
{({ onItemsRendered, ref }) => (
<List
ref={ref}
onItemsRendered={onItemsRendered}
// ... other props
/>
)}
</InfiniteLoader>
)
}User experience patterns during lazy loading
Measuring the impact of lazy loading strategies
First Contentful Paint
Critical content first
Initial Bundle Size
Code splitting
Largest Contentful Paint
Image optimization
Guidelines for effective lazy loading implementation