/** * Lazy loading utilities for performance optimization */ import { lazy, ComponentType } from 'react' /** * Create a lazy-loaded component with better error handling */ export function createLazyComponent>( importFunction: () => Promise<{ default: T }>, displayName?: string ): T { const LazyComponent = lazy(importFunction) if (displayName) { ;(LazyComponent as any).displayName = `Lazy(${displayName})` } return LazyComponent as unknown as T } /** * Intersection Observer hook for lazy loading elements */ import { useEffect, useRef, useState } from 'react' interface UseIntersectionObserverOptions { threshold?: number root?: Element | null rootMargin?: string freezeOnceVisible?: boolean } export function useIntersectionObserver(options: UseIntersectionObserverOptions = {}) { const { threshold = 0, root = null, rootMargin = '0%', freezeOnceVisible = false } = options const [entry, setEntry] = useState() const [node, setNode] = useState(null) const observer = useRef(null) const frozen = entry?.isIntersecting && freezeOnceVisible const updateEntry = ([entry]: IntersectionObserverEntry[]): void => { setEntry(entry) } useEffect(() => { const hasIOSupport = !!window.IntersectionObserver if (!hasIOSupport || frozen || !node) return const observerParams = { threshold, root, rootMargin } const isNewObserver = !observer.current const hasOptionsChanged = observer.current && (observer.current.root !== root || observer.current.rootMargin !== rootMargin || observer.current.thresholds[0] !== threshold) if (isNewObserver || hasOptionsChanged) { if (observer.current) { observer.current.disconnect() } observer.current = new window.IntersectionObserver(updateEntry, observerParams) } observer.current.observe(node) return () => observer.current?.disconnect() }, [node, threshold, root, rootMargin, frozen]) const cleanup = () => { if (observer.current) { observer.current.disconnect() observer.current = null } } useEffect(() => { return cleanup }, []) return [setNode, !!entry?.isIntersecting, entry, cleanup] as const } /** * Lazy loading image component */ import Image from 'next/image' import { cn } from './utils' interface LazyImageProps { src: string alt: string width?: number height?: number className?: string fill?: boolean placeholder?: 'blur' | 'empty' blurDataURL?: string priority?: boolean sizes?: string } export function LazyImage({ src, alt, width, height, className, fill = false, placeholder = 'empty', blurDataURL, priority = false, sizes, ...props }: LazyImageProps) { const [setRef, isVisible] = useIntersectionObserver({ threshold: 0.1, freezeOnceVisible: true, }) return (
{(isVisible || priority) && ( {alt} )}
) } /** * Lazy loading wrapper for any content */ interface LazyContentProps { children: React.ReactNode className?: string fallback?: React.ReactNode threshold?: number rootMargin?: string } export function LazyContent({ children, className, fallback = null, threshold = 0.1, rootMargin = '50px', }: LazyContentProps) { const [setRef, isVisible] = useIntersectionObserver({ threshold, rootMargin, freezeOnceVisible: true, }) return (
{isVisible ? children : fallback}
) } /** * Preload images for better performance */ export function preloadImage(src: string): Promise { return new Promise((resolve, reject) => { const img = new window.Image() img.onload = () => resolve() img.onerror = reject img.src = src }) } /** * Preload multiple images */ export async function preloadImages(sources: string[]): Promise { return Promise.all(sources.map(preloadImage)) }