agentsclimarketplace

Lazy loading patterns

Skill VersoXBT/claude-initial-setup/skills/performance/lazy-loading-patterns

75 skills, 14 agents, 15 commands for Claude Code. Plug-and-play starter kit covering TypeScript, Python, Go, Rust, React, Next.js, FastAPI, Django, Docker, CI/CD, security, testing, and more. Cross-AI support for Cursor, Copilot, and Codex.

Install
npx -y skills add VersoXBT/claude-initial-setup --skill lazy-loading-patterns

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 4 stars4 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Implement lazy loading and code splitting to reduce initial bundle size and improve load times. Activate whenever the user works on bundle optimization, asks about code splitting, implements dynamic imports, uses React.lazy or Suspense, sets up route-based splitting, or optimizes image loading with intersection observer.

SKILL.md

7.5 KB, as published. Nobody here has run it

Lazy Loading Patterns

Load code and assets only when needed. Reduce initial bundle size to improve Time to Interactive and First Contentful Paint. Split by route, by component, and by visibility.

When to Use

  • Initial page load is slow due to large JavaScript bundle
  • Components are below the fold or behind user interaction
  • Routes have heavy dependencies not needed on every page
  • Images or media dominate page weight
  • Third-party libraries are used on specific pages only

Core Patterns

Route-Based Code Splitting (React)

Split your bundle by route so users only download code for the page they visit.

import { lazy, Suspense } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';

// Each lazy() call creates a separate chunk
const Home = lazy(() => import('./pages/Home'));
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Settings = lazy(() => import('./pages/Settings'));
const AdminPanel = lazy(() => import('./pages/AdminPanel'));

function App() {
  return (
    <BrowserRouter>
      <Suspense fallback={<PageSkeleton />}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/dashboard" element={<Dashboard />} />
          <Route path="/settings" element={<Settings />} />
          <Route path="/admin" element={<AdminPanel />} />
        </Routes>
      </Suspense>
    </BrowserRouter>
  );
}

// Skeleton loader instead of spinner for better perceived performance
function PageSkeleton() {
  return (
    <div className="animate-pulse space-y-4 p-6">
      <div className="h-8 bg-gray-200 rounded w-1/3" />
      <div className="h-4 bg-gray-200 rounded w-2/3" />
      <div className="h-4 bg-gray-200 rounded w-1/2" />
    </div>
  );
}

Component-Level Lazy Loading

Defer loading of heavy components until they are needed.

import { lazy, Suspense, useState } from 'react';

// Heavy components loaded on demand
const MarkdownEditor = lazy(() => import('./components/MarkdownEditor'));
const ChartDashboard = lazy(() => import('./components/ChartDashboard'));
function DocumentPage({ doc }: { doc: Document }) {
  const [isEditing, setIsEditing] = useState(false);
  return (
    <div>
      <h1>{doc.title}</h1>
      {isEditing ? (
        <Suspense fallback={<div>Loading editor...</div>}>
          <MarkdownEditor content={doc.content} />
        </Suspense>
      ) : (
        <button onClick={() => setIsEditing(true)}>Edit</button>
      )}
    </div>
  );
}

Dynamic Imports (Framework-Agnostic)

Use dynamic import() to load modules on demand in any JavaScript environment.

// Load a heavy library only when needed
async function generatePDF(data: ReportData): Promise<Blob> {
  const { jsPDF } = await import('jspdf');
  const doc = new jsPDF();
  doc.text(data.title, 10, 10);
  // ... build PDF
  return doc.output('blob');
}

// Webpack magic comments for prefetching/preloading
const Settings = lazy(() =>
  import(/* webpackPrefetch: true */ './pages/Settings') // Download during idle
);
const Dashboard = lazy(() =>
  import(/* webpackPreload: true */ './pages/Dashboard') // Download immediately
);

Intersection Observer for Visibility-Based Loading

Load content when it scrolls into the viewport.

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

function useIntersectionObserver(
  options: IntersectionObserverInit = {}
): [React.RefObject<HTMLDivElement>, boolean] {
  const ref = useRef<HTMLDivElement>(null);
  const [isVisible, setIsVisible] = useState(false);

  useEffect(() => {
    const element = ref.current;
    if (!element) return;

    const observer = new IntersectionObserver(
      ([entry]) => {
        if (entry.isIntersecting) {
          setIsVisible(true);
          observer.unobserve(element); // Only trigger once
        }
      },
      { rootMargin: '200px', ...options } // Start loading 200px before visible
    );

    observer.observe(element);
    return () => observer.disconnect();
  }, []);

  return [ref, isVisible];
}

// Usage: lazy-load a heavy section
function AnalyticsSection({ userId }: { userId: string }) {
  const [ref, isVisible] = useIntersectionObserver();

  return (
    <div ref={ref}>
      {isVisible ? (
        <Suspense fallback={<SectionSkeleton />}>
          <ChartDashboard userId={userId} />
        </Suspense>
      ) : (
        <SectionSkeleton />
      )}
    </div>
  );
}

Image Lazy Loading

Defer image loading until they approach the viewport.

// Native lazy loading (simplest approach)
function ImageGallery({ images }: { images: ImageData[] }) {
  return (
    <div className="grid grid-cols-3 gap-4">
      {images.map(img => (
        <img
          key={img.id}
          src={img.url}
          alt={img.alt}
          loading="lazy"           // Native browser lazy loading
          decoding="async"         // Non-blocking decode
          width={img.width}        // Prevent layout shift
          height={img.height}
        />
      ))}
    </div>
  );
}

// Next.js: use priority={true} for above-the-fold LCP images,
// default lazy loading for everything else.

Bundle Analysis

Identify what to split by analyzing your bundle composition.

# Webpack bundle analyzer
npx webpack-bundle-analyzer dist/stats.json

# Next.js bundle analysis
ANALYZE=true npm run build

# Vite bundle visualization
npx vite-bundle-visualizer

# Check individual import costs
npx import-cost  # VS Code extension shows inline import sizes
// Identify heavy imports to split
// BEFORE: Everything in main bundle
import { Chart } from 'chart.js';          // ~200KB
import { marked } from 'marked';           // ~50KB
import { highlight } from 'highlight.js';  // ~300KB

// AFTER: Loaded on demand
const loadChart = () => import('chart.js').then(m => m.Chart);
const loadMarked = () => import('marked').then(m => m.marked);

Anti-Patterns

  • Lazy loading components that are always visible above the fold
  • Creating too many tiny chunks (excessive HTTP requests)
  • Not providing width/height on images (causes layout shift)
  • Using loading="lazy" on the hero/LCP image (delays critical content)
  • Wrapping every component in Suspense instead of grouping related components
  • Lazy loading tiny modules (< 5KB) where the overhead exceeds the savings
  • Not preloading chunks that are very likely needed next (e.g., next step in a wizard)

Quick Reference

TechniqueWhen to UseSavings
Route splittingAlways, for multi-page apps30-70% initial bundle
Component splittingHeavy components behind interactionPer-component basis
Dynamic importConditional features, large librariesLibrary size
Image lazy loadingBelow-the-fold imagesBandwidth, LCP
Intersection ObserverContent sections below viewportPer-section basis
PrefetchLinks user is likely to clickPerceived latency
ToolPurpose
React.lazy + SuspenseReact component code splitting
import()Dynamic module loading (any JS)
loading="lazy"Native browser image lazy loading
IntersectionObserverVisibility-based triggering

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.