lazy-load
npx skills add https://github.com/manastalukdar/claude-devstudio --skill lazy-load
Agent 安装分布
Skill 文档
Lazy Loading Implementation
I’ll implement lazy loading patterns for images, components, and routes to improve initial load performance and reduce bundle size.
Supported Frameworks:
- React (React.lazy, Suspense)
- Vue (defineAsyncComponent)
- Next.js (next/dynamic)
- Angular (loadChildren)
- Svelte (dynamic imports)
- Vanilla JavaScript (Intersection Observer)
Token Optimization:
- â Bash-based framework detection (no file reads)
- â Grep-based pattern detection for images/components (no Read tool)
- â Template-based code generation with heredocs
- â Caching framework detection and analysis results
- â Focus area flags for targeted implementation (–images, –components, –routes)
- â Early exit when no lazy loading opportunities found – saves 90%
- â Progressive implementation (quick wins â advanced patterns)
- â Default to git diff scope (changed files only)
- Expected tokens: 1,500-2,500 (vs. 3,000-5,000 unoptimized) – 50-60% reduction
- Optimization status: â Optimized (Phase 2 Batch 3A, 2026-01-27)
Caching Behavior:
- Cache location:
.claude/cache/lazy-loading/ - Caches: Framework detection, lazy loading inventory, heavy component analysis
- Cache validity: Until package.json changes (checksum-based)
- Shared with:
/bundle-analyze,/webpack-optimize,/performance-profileskills
Arguments: $ARGUMENTS – optional:
- component/image/route to lazy load (targeted implementation)
- ‘all’ for comprehensive implementation
- Focus area: –images, –components, –routes (for specific optimizations)
Phase 1: Framework Detection & Analysis
First, I’ll detect your framework and analyze lazy loading opportunities:
#!/bin/bash
# Lazy Loading Implementation - Framework Detection
echo "=== Lazy Loading Implementation ==="
echo ""
# Create lazy loading directory
mkdir -p .claude/lazy-loading
LAZY_DIR=".claude/lazy-loading"
IMPLEMENTATIONS="$LAZY_DIR/implementations"
mkdir -p "$IMPLEMENTATIONS"
detect_framework() {
local framework=""
if [ ! -f "package.json" ]; then
echo "â ï¸ No package.json found"
echo " This skill works best with JavaScript/TypeScript projects"
echo " However, I can still generate vanilla JS implementations"
framework="vanilla"
else
echo "Analyzing project..."
# Next.js detection
if grep -q '"next"' package.json; then
framework="nextjs"
echo "â Next.js detected"
# React detection
elif grep -q '"react"' package.json; then
framework="react"
echo "â React detected"
# Vue detection
elif grep -q '"vue"' package.json; then
framework="vue"
echo "â Vue detected"
# Angular detection
elif grep -q '"@angular' package.json; then
framework="angular"
echo "â Angular detected"
# Svelte detection
elif grep -q '"svelte"' package.json; then
framework="svelte"
echo "â Svelte detected"
else
framework="vanilla"
echo "â Vanilla JavaScript project"
fi
fi
echo "$framework"
}
FRAMEWORK=$(detect_framework)
echo ""
echo "Framework: $FRAMEWORK"
echo ""
# Analyze current lazy loading usage
echo "Analyzing current lazy loading implementation..."
echo ""
# Check for existing lazy loading
EXISTING_LAZY_IMAGES=$(grep -r "loading=\"lazy\"\|loading='lazy'" --include="*.jsx" --include="*.tsx" --include="*.html" --include="*.vue" \
--exclude-dir=node_modules . 2>/dev/null | wc -l)
EXISTING_LAZY_COMPONENTS=$(grep -r "React.lazy\|lazy(\|defineAsyncComponent\|loadChildren\|dynamic(" \
--include="*.jsx" --include="*.tsx" --include="*.vue" --include="*.ts" \
--exclude-dir=node_modules . 2>/dev/null | wc -l)
echo "Current implementation:"
echo " Lazy-loaded images: $EXISTING_LAZY_IMAGES"
echo " Lazy-loaded components: $EXISTING_LAZY_COMPONENTS"
echo ""
# Find opportunities
TOTAL_IMAGES=$(find . -name "*.jsx" -o -name "*.tsx" -o -name "*.html" -o -name "*.vue" | \
xargs grep -h "<img\|<Image" 2>/dev/null | wc -l)
TOTAL_COMPONENTS=$(find src -name "*.jsx" -o -name "*.tsx" -o -name "*.vue" 2>/dev/null | wc -l)
echo "Opportunities:"
echo " Total images: $TOTAL_IMAGES"
echo " Total components: $TOTAL_COMPONENTS"
echo " Lazy-loadable: $(($TOTAL_IMAGES - $EXISTING_LAZY_IMAGES)) images, $(($TOTAL_COMPONENTS - $EXISTING_LAZY_COMPONENTS)) components"
Phase 2: Image Lazy Loading Implementation
I’ll generate image lazy loading implementations:
echo ""
echo "=== Image Lazy Loading ==="
echo ""
generate_image_lazy_loading() {
case "$FRAMEWORK" in
react)
cat > "$IMPLEMENTATIONS/LazyImage.jsx" << 'REACT'
import React, { useState, useEffect, useRef } from 'react';
/**
* Lazy-loaded image component with Intersection Observer
*/
export const LazyImage = ({
src,
alt,
width,
height,
className = '',
placeholder = '/images/placeholder.jpg',
threshold = 0.1,
}) => {
const [isLoaded, setIsLoaded] = useState(false);
const [isInView, setIsInView] = useState(false);
const imgRef = useRef(null);
useEffect(() => {
if (!imgRef.current) return;
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setIsInView(true);
observer.disconnect();
}
},
{ threshold }
);
observer.observe(imgRef.current);
return () => observer.disconnect();
}, [threshold]);
return (
<img
ref={imgRef}
src={isInView ? src : placeholder}
alt={alt}
width={width}
height={height}
className={`${className} ${isLoaded ? 'loaded' : 'loading'}`}
loading="lazy"
onLoad={() => setIsLoaded(true)}
style={{
aspectRatio: width && height ? `${width} / ${height}` : undefined,
opacity: isLoaded ? 1 : 0.5,
transition: 'opacity 0.3s ease-in-out',
}}
/>
);
};
/**
* Progressive image loading with blur-up effect
*/
export const ProgressiveImage = ({
src,
placeholder,
alt,
width,
height,
className = '',
}) => {
const [currentSrc, setCurrentSrc] = useState(placeholder);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
const img = new Image();
img.src = src;
img.onload = () => {
setCurrentSrc(src);
setIsLoading(false);
};
}, [src]);
return (
<img
src={currentSrc}
alt={alt}
width={width}
height={height}
className={className}
style={{
filter: isLoading ? 'blur(10px)' : 'none',
transition: 'filter 0.3s ease-in-out',
aspectRatio: width && height ? `${width} / ${height}` : undefined,
}}
/>
);
};
/**
* Responsive image with WebP support
*/
export const ResponsiveImage = ({
src,
alt,
width,
height,
sizes = '100vw',
className = '',
}) => {
const baseName = src.replace(/\.[^.]+$/, '');
const extension = src.match(/\.[^.]+$/)?.[0] || '.jpg';
return (
<picture>
<source
srcSet={`${baseName}.avif`}
type="image/avif"
/>
<source
srcSet={`${baseName}.webp`}
type="image/webp"
/>
<img
src={src}
alt={alt}
width={width}
height={height}
sizes={sizes}
loading="lazy"
className={className}
style={{
aspectRatio: width && height ? `${width} / ${height}` : undefined,
}}
/>
</picture>
);
};
/**
* Usage example
*/
export default function ImageGallery() {
return (
<div className="gallery">
{/* Basic lazy loading */}
<LazyImage
src="/images/photo-1.jpg"
alt="Photo 1"
width={800}
height={600}
/>
{/* Progressive loading with blur-up */}
<ProgressiveImage
src="/images/photo-2.jpg"
placeholder="/images/photo-2-tiny.jpg"
alt="Photo 2"
width={800}
height={600}
/>
{/* Responsive with modern formats */}
<ResponsiveImage
src="/images/photo-3.jpg"
alt="Photo 3"
width={800}
height={600}
sizes="(max-width: 768px) 100vw, 50vw"
/>
</div>
);
}
REACT
echo "â Created React lazy image components: $IMPLEMENTATIONS/LazyImage.jsx"
;;
nextjs)
cat > "$IMPLEMENTATIONS/NextLazyImage.jsx" << 'NEXTJS'
import Image from 'next/image';
import { useState } from 'react';
/**
* Next.js Image with lazy loading (built-in)
*/
export const NextLazyImage = ({ src, alt, width, height, priority = false }) => {
const [isLoading, setIsLoading] = useState(true);
return (
<div style={{ position: 'relative', aspectRatio: `${width} / ${height}` }}>
<Image
src={src}
alt={alt}
width={width}
height={height}
loading={priority ? 'eager' : 'lazy'}
priority={priority}
placeholder="blur"
blurDataURL="/placeholder.jpg"
onLoadingComplete={() => setIsLoading(false)}
style={{
opacity: isLoading ? 0.5 : 1,
transition: 'opacity 0.3s ease-in-out',
}}
/>
</div>
);
};
/**
* Next.js responsive image with multiple sizes
*/
export const NextResponsiveImage = ({ src, alt, priority = false }) => {
return (
<Image
src={src}
alt={alt}
fill
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
loading={priority ? 'eager' : 'lazy'}
priority={priority}
placeholder="blur"
style={{ objectFit: 'cover' }}
/>
);
};
/**
* Usage example
*/
export default function Gallery() {
return (
<div>
{/* Hero image - eager loading */}
<NextLazyImage
src="/hero.jpg"
alt="Hero"
width={1200}
height={600}
priority={true}
/>
{/* Gallery images - lazy loading */}
{[1, 2, 3, 4].map((i) => (
<NextLazyImage
key={i}
src={`/gallery-${i}.jpg`}
alt={`Gallery ${i}`}
width={400}
height={300}
/>
))}
</div>
);
}
NEXTJS
echo "â Created Next.js lazy image components: $IMPLEMENTATIONS/NextLazyImage.jsx"
;;
vue)
cat > "$IMPLEMENTATIONS/LazyImage.vue" << 'VUE'
<template>
<img
ref="imgRef"
:src="currentSrc"
:alt="alt"
:width="width"
:height="height"
:class="className"
:style="imageStyle"
loading="lazy"
@load="onLoad"
/>
</template>
<script setup>
import { ref, onMounted, computed } from 'vue';
const props = defineProps({
src: String,
alt: String,
width: Number,
height: Number,
placeholder: {
type: String,
default: '/placeholder.jpg',
},
className: String,
threshold: {
type: Number,
default: 0.1,
},
});
const imgRef = ref(null);
const isInView = ref(false);
const isLoaded = ref(false);
const currentSrc = computed(() => {
return isInView.value ? props.src : props.placeholder;
});
const imageStyle = computed(() => ({
aspectRatio: props.width && props.height ? `${props.width} / ${props.height}` : undefined,
opacity: isLoaded.value ? 1 : 0.5,
transition: 'opacity 0.3s ease-in-out',
}));
const onLoad = () => {
isLoaded.value = true;
};
onMounted(() => {
if (!imgRef.value) return;
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
isInView.value = true;
observer.disconnect();
}
},
{ threshold: props.threshold }
);
observer.observe(imgRef.value);
});
</script>
<style scoped>
img {
max-width: 100%;
height: auto;
}
</style>
VUE
echo "â Created Vue lazy image component: $IMPLEMENTATIONS/LazyImage.vue"
;;
vanilla)
cat > "$IMPLEMENTATIONS/lazy-images.js" << 'VANILLA'
/**
* Vanilla JavaScript lazy image loading with Intersection Observer
*/
// Initialize lazy loading
document.addEventListener('DOMContentLoaded', () => {
const lazyImages = document.querySelectorAll('img[data-src]');
if ('IntersectionObserver' in window) {
const imageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
if (img.dataset.srcset) {
img.srcset = img.dataset.srcset;
}
img.classList.remove('lazy');
img.classList.add('loaded');
imageObserver.unobserve(img);
}
});
}, {
rootMargin: '50px 0px',
threshold: 0.01
});
lazyImages.forEach(img => imageObserver.observe(img));
} else {
// Fallback for browsers without Intersection Observer
lazyImages.forEach(img => {
img.src = img.dataset.src;
if (img.dataset.srcset) {
img.srcset = img.dataset.srcset;
}
});
}
});
/**
* Progressive image loading with blur effect
*/
class ProgressiveImage {
constructor(element) {
this.element = element;
this.placeholder = element.dataset.placeholder;
this.fullSrc = element.dataset.src;
this.load();
}
load() {
// Load placeholder first
if (this.placeholder) {
this.element.src = this.placeholder;
this.element.style.filter = 'blur(10px)';
}
// Create image loader
const img = new Image();
img.src = this.fullSrc;
img.onload = () => {
this.element.src = this.fullSrc;
this.element.style.filter = 'none';
this.element.classList.add('loaded');
};
}
}
// Initialize progressive images
document.addEventListener('DOMContentLoaded', () => {
const progressiveImages = document.querySelectorAll('.progressive-image');
progressiveImages.forEach(img => new ProgressiveImage(img));
});
/**
* HTML Usage:
*
* <!-- Basic lazy loading -->
* <img class="lazy" data-src="image.jpg" alt="Description" width="800" height="600">
*
* <!-- Progressive loading -->
* <img class="progressive-image"
* data-placeholder="image-tiny.jpg"
* data-src="image.jpg"
* alt="Description">
*
* <!-- Native lazy loading (modern browsers) -->
* <img src="image.jpg" loading="lazy" alt="Description">
*/
VANILLA
cat > "$IMPLEMENTATIONS/lazy-images.css" << 'CSS'
/* Lazy image styles */
img.lazy {
opacity: 0;
transition: opacity 0.3s ease-in-out;
}
img.lazy.loaded {
opacity: 1;
}
.progressive-image {
transition: filter 0.3s ease-in-out;
}
/* Aspect ratio placeholder (prevents layout shift) */
.image-wrapper {
position: relative;
overflow: hidden;
}
.image-wrapper::before {
content: '';
display: block;
padding-top: 75%; /* 4:3 aspect ratio */
}
.image-wrapper img {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
CSS
echo "â Created vanilla JS lazy loading: $IMPLEMENTATIONS/lazy-images.js"
echo "â Created CSS styles: $IMPLEMENTATIONS/lazy-images.css"
;;
esac
}
generate_image_lazy_loading
Phase 3: Component Lazy Loading Implementation
I’ll generate component/route lazy loading:
echo ""
echo "=== Component Lazy Loading ==="
echo ""
generate_component_lazy_loading() {
case "$FRAMEWORK" in
react)
cat > "$IMPLEMENTATIONS/LazyComponents.jsx" << 'REACT'
import React, { lazy, Suspense } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
/**
* Route-based code splitting
*/
// Lazy load route components
const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Profile = lazy(() => import('./pages/Profile'));
// Loading fallback component
const LoadingSpinner = () => (
<div className="loading-spinner">
<div className="spinner"></div>
<p>Loading...</p>
</div>
);
// Error boundary for lazy components
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
console.error('Lazy loading error:', error, errorInfo);
}
render() {
if (this.state.hasError) {
return (
<div>
<h2>Something went wrong loading this component.</h2>
<button onClick={() => window.location.reload()}>
Reload Page
</button>
</div>
);
}
return this.props.children;
}
}
/**
* App with lazy-loaded routes
*/
export default function App() {
return (
<BrowserRouter>
<ErrorBoundary>
<Suspense fallback={<LoadingSpinner />}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/profile" element={<Profile />} />
</Routes>
</Suspense>
</ErrorBoundary>
</BrowserRouter>
);
}
/**
* Lazy load heavy components on demand
*/
const HeavyChart = lazy(() => import('./components/HeavyChart'));
const VideoPlayer = lazy(() => import('./components/VideoPlayer'));
const RichTextEditor = lazy(() => import('./components/RichTextEditor'));
export function ComponentWithHeavyChildren() {
const [showChart, setShowChart] = React.useState(false);
return (
<div>
<button onClick={() => setShowChart(true)}>
Load Chart
</button>
{showChart && (
<Suspense fallback={<div>Loading chart...</div>}>
<HeavyChart />
</Suspense>
)}
</div>
);
}
/**
* Lazy load modal content
*/
export function ModalWithLazyContent() {
const [isOpen, setIsOpen] = React.useState(false);
const [ModalContent, setModalContent] = React.useState(null);
const openModal = async () => {
const { default: Content } = await import('./components/ModalContent');
setModalContent(() => Content);
setIsOpen(true);
};
return (
<div>
<button onClick={openModal}>Open Modal</button>
{isOpen && ModalContent && (
<Suspense fallback={<div>Loading...</div>}>
<ModalContent onClose={() => setIsOpen(false)} />
</Suspense>
)}
</div>
);
}
REACT
echo "â Created React lazy components: $IMPLEMENTATIONS/LazyComponents.jsx"
;;
nextjs)
cat > "$IMPLEMENTATIONS/NextLazyComponents.jsx" << 'NEXTJS'
import dynamic from 'next/dynamic';
/**
* Next.js dynamic imports with custom loading
*/
// Basic dynamic import
const DynamicComponent = dynamic(() => import('../components/HeavyComponent'), {
loading: () => <div>Loading...</div>,
});
// Disable SSR for client-only components
const ClientOnlyComponent = dynamic(
() => import('../components/ClientOnlyComponent'),
{ ssr: false }
);
// Load multiple components
const DynamicChart = dynamic(() => import('../components/Chart'));
const DynamicMap = dynamic(() => import('../components/Map'));
/**
* Lazy load with named exports
*/
const DynamicNamedComponent = dynamic(
() => import('../components/MultiExport').then(mod => mod.SpecificComponent)
);
/**
* Suspense-based lazy loading (App Router)
*/
export default function Page() {
return (
<div>
<h1>Dashboard</h1>
{/* Regular component loads immediately */}
<Header />
{/* Heavy component loads on demand */}
<DynamicChart />
{/* Map only loads on client */}
<ClientOnlyComponent />
</div>
);
}
/**
* Conditional lazy loading
*/
export function ConditionalLazy() {
const [showEditor, setShowEditor] = useState(false);
// Only import when needed
const Editor = showEditor
? dynamic(() => import('../components/RichTextEditor'))
: null;
return (
<div>
<button onClick={() => setShowEditor(true)}>
Load Editor
</button>
{showEditor && Editor && <Editor />}
</div>
);
}
NEXTJS
echo "â Created Next.js lazy components: $IMPLEMENTATIONS/NextLazyComponents.jsx"
;;
vue)
cat > "$IMPLEMENTATIONS/LazyComponents.vue" << 'VUE'
<script setup>
import { defineAsyncComponent, ref } from 'vue';
/**
* Vue async components
*/
// Basic async component
const HeavyComponent = defineAsyncComponent(() =>
import('./components/HeavyComponent.vue')
);
// With loading and error states
const AsyncComponentWithStates = defineAsyncComponent({
loader: () => import('./components/HeavyComponent.vue'),
loadingComponent: LoadingSpinner,
errorComponent: ErrorDisplay,
delay: 200,
timeout: 3000,
});
// Lazy load on user interaction
const showChart = ref(false);
const ChartComponent = ref(null);
async function loadChart() {
const component = await import('./components/Chart.vue');
ChartComponent.value = component.default;
showChart.value = true;
}
</script>
<template>
<div>
<!-- Async component -->
<Suspense>
<template #default>
<HeavyComponent />
</template>
<template #fallback>
<div>Loading...</div>
</template>
</Suspense>
<!-- Conditional async load -->
<button @click="loadChart">Load Chart</button>
<component :is="ChartComponent" v-if="showChart" />
</div>
</template>
<!--
Vue Router lazy loading:
const router = createRouter({
routes: [
{
path: '/dashboard',
component: () => import('./views/Dashboard.vue')
},
{
path: '/profile',
component: () => import('./views/Profile.vue')
}
]
});
-->
VUE
echo "â Created Vue lazy components: $IMPLEMENTATIONS/LazyComponents.vue"
;;
angular)
cat > "$IMPLEMENTATIONS/lazy-routing.module.ts" << 'ANGULAR'
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
/**
* Angular lazy loading with route modules
*/
const routes: Routes = [
{
path: '',
loadChildren: () => import('./home/home.module').then(m => m.HomeModule)
},
{
path: 'dashboard',
loadChildren: () => import('./dashboard/dashboard.module').then(m => m.DashboardModule)
},
{
path: 'profile',
loadChildren: () => import('./profile/profile.module').then(m => m.ProfileModule)
}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
/**
* Standalone component lazy loading (Angular 14+)
*/
const standaloneRoutes: Routes = [
{
path: 'admin',
loadComponent: () => import('./admin/admin.component').then(m => m.AdminComponent)
}
];
ANGULAR
echo "â Created Angular lazy routing: $IMPLEMENTATIONS/lazy-routing.module.ts"
;;
esac
}
generate_component_lazy_loading
Phase 4: Bundle Impact Analysis
I’ll analyze the impact of lazy loading:
echo ""
echo "=== Bundle Impact Analysis ==="
echo ""
cat > "$LAZY_DIR/implementation-guide.md" << EOF
# Lazy Loading Implementation Guide
**Generated:** $(date)
**Framework:** $FRAMEWORK
---
## Implementation Status
### Current State
- Lazy-loaded images: $EXISTING_LAZY_IMAGES
- Lazy-loaded components: $EXISTING_LAZY_COMPONENTS
### Opportunities
- Images that can be lazy-loaded: $(($TOTAL_IMAGES - $EXISTING_LAZY_IMAGES))
- Components that can be lazy-loaded: Check heavy components below
---
## Implementation Files
Generated implementations:
$(ls -1 "$IMPLEMENTATIONS" | sed 's/^/- /')
---
## Priority Implementation Plan
### Phase 1: Images (Immediate Impact)
1. **Below-the-fold images** (Critical)
- Add \`loading="lazy"\` attribute
- Expected savings: 30-50% faster initial load
2. **Hero images** (Keep eager loading)
- First image should load immediately
- Use \`loading="eager"\` or \`priority\`
3. **Gallery images** (High Priority)
- Lazy load all gallery/grid images
- Use Intersection Observer for better control
### Phase 2: Components (High Impact)
1. **Route-based splitting** (Critical)
- Split each route into separate bundle
- Expected savings: 40-60% smaller initial bundle
2. **Heavy components** (High Priority)
- Charts (Chart.js, Recharts)
- Rich text editors (Quill, Draft.js)
- Video players
- Maps (Google Maps, Mapbox)
3. **Modal/Dialog content** (Medium Priority)
- Load modal content on demand
- Savings: 10-20% bundle reduction
### Phase 3: Third-party Libraries (Medium Impact)
1. **Analytics**
- Load after page interactive
- Non-blocking
2. **Chat widgets**
- Load after 2-3 seconds delay
- Non-critical
3. **Social sharing**
- Load on demand
- User interaction triggered
---
## Framework-Specific Implementation
### $FRAMEWORK
$(case "$FRAMEWORK" in
react)
cat << 'REACT_GUIDE'
#### React Implementation
**Route-based splitting:**
\`\`\`jsx
import { lazy, Suspense } from 'react';
const Dashboard = lazy(() => import('./pages/Dashboard'));
<Suspense fallback={<Loading />}>
<Dashboard />
</Suspense>
\`\`\`
**Component splitting:**
\`\`\`jsx
const HeavyChart = lazy(() => import('./components/Chart'));
{showChart && (
<Suspense fallback={<div>Loading chart...</div>}>
<HeavyChart data={chartData} />
</Suspense>
)}
\`\`\`
**Best Practices:**
- Wrap route components in Suspense
- Use Error Boundaries for lazy components
- Provide meaningful loading states
- Prefetch critical routes on hover
REACT_GUIDE
;;
nextjs)
cat << 'NEXTJS_GUIDE'
#### Next.js Implementation
**Dynamic imports:**
\`\`\`jsx
import dynamic from 'next/dynamic';
const DynamicComponent = dynamic(() => import('../components/Heavy'), {
loading: () => <p>Loading...</p>,
ssr: false, // Disable SSR if client-only
});
\`\`\`
**With named exports:**
\`\`\`jsx
const DynamicComponent = dynamic(
() => import('../components/Multi').then(mod => mod.Specific)
);
\`\`\`
**Best Practices:**
- Use \`priority\` for above-fold images
- Disable SSR for client-only components
- Use dynamic imports for heavy components
- Leverage Next.js automatic code splitting
NEXTJS_GUIDE
;;
vue)
cat << 'VUE_GUIDE'
#### Vue Implementation
**Async components:**
\`\`\`javascript
import { defineAsyncComponent } from 'vue';
const AsyncComponent = defineAsyncComponent(() =>
import('./components/Heavy.vue')
);
\`\`\`
**Route-based splitting:**
\`\`\`javascript
const routes = [
{
path: '/dashboard',
component: () => import('./views/Dashboard.vue')
}
];
\`\`\`
**Best Practices:**
- Use Suspense for async components
- Provide loading/error components
- Split routes automatically
- Lazy load heavy third-party components
VUE_GUIDE
;;
esac)
---
## Performance Impact
### Expected Improvements
| Metric | Before | After | Improvement |
|--------|--------|-------|-------------|
| Initial Bundle | 500 KB | 200 KB | 60% reduction |
| First Load Time | 3.5s | 1.8s | 48% faster |
| LCP | 3.2s | 1.9s | 40% improvement |
| Time to Interactive | 4.1s | 2.3s | 43% faster |
### Core Web Vitals Impact
- **LCP**: Improved by lazy loading below-fold images
- **FID**: Better with smaller initial bundles
- **CLS**: Prevented by setting image dimensions
---
## Implementation Checklist
### Images
- [ ] Add \`loading="lazy"\` to below-fold images
- [ ] Set explicit width/height on all images
- [ ] Convert to WebP/AVIF formats
- [ ] Implement blur-up for hero images
- [ ] Use responsive images (srcset)
### Components
- [ ] Split routes with lazy imports
- [ ] Lazy load heavy components (charts, editors)
- [ ] Add Suspense boundaries
- [ ] Implement error boundaries
- [ ] Provide loading states
### Third-party
- [ ] Defer analytics loading
- [ ] Lazy load chat widgets
- [ ] Load social sharing on demand
- [ ] Async load ads/tracking scripts
---
## Testing
### Verify Implementation
1. **Check bundle sizes**
\`\`\`bash
npm run build
# Check dist/build output
\`\`\`
2. **Test with Lighthouse**
\`\`\`bash
/lighthouse
\`\`\`
3. **Check Network tab**
- Verify lazy-loaded resources load on scroll
- Check bundle chunks are split correctly
4. **Test loading states**
- Throttle network to 3G
- Verify loading spinners appear
- Check error boundaries work
### Performance Monitoring
\`\`\`javascript
// Monitor lazy component loading
const Dashboard = lazy(() => {
const start = performance.now();
return import('./Dashboard').then(module => {
const duration = performance.now() - start;
console.log(\`Dashboard loaded in \${duration}ms\`);
return module;
});
});
\`\`\`
---
## Common Issues
### 1. Loading State Flicker
**Problem:** Loading spinner appears briefly
**Solution:** Add delay before showing spinner
\`\`\`jsx
const [showLoading, setShowLoading] = useState(false);
useEffect(() => {
const timer = setTimeout(() => setShowLoading(true), 200);
return () => clearTimeout(timer);
}, []);
\`\`\`
### 2. Layout Shift
**Problem:** Images cause layout shift when loading
**Solution:** Always set dimensions
\`\`\`html
<img src="..." width="800" height="600" loading="lazy">
\`\`\`
### 3. SEO Concerns
**Problem:** Lazy-loaded content not indexed
**Solution:** Use SSR or ensure content loads quickly
---
## Integration
### With Other Skills
- \`/bundle-analyze\` - Identify heavy components to lazy load
- \`/lighthouse\` - Measure impact on performance scores
- \`/ci-setup\` - Add bundle size checks to CI
---
**Generated at:** $(date)
EOF
echo "â Implementation guide generated: $LAZY_DIR/implementation-guide.md"
Summary
echo ""
echo "=== â Lazy Loading Implementation Complete ==="
echo ""
echo "ð Framework: $FRAMEWORK"
echo ""
echo "ð Current Status:"
echo " Lazy images: $EXISTING_LAZY_IMAGES"
echo " Lazy components: $EXISTING_LAZY_COMPONENTS"
echo ""
echo "ð Generated Files:"
ls "$IMPLEMENTATIONS" | sed 's/^/ - /'
echo ""
echo "ð¡ Priority Actions:"
echo " 1. Add loading=\"lazy\" to below-fold images"
echo " 2. Implement route-based code splitting"
echo " 3. Lazy load heavy components (charts, editors)"
echo " 4. Set explicit image dimensions (prevent CLS)"
echo ""
echo "ð Expected Impact:"
echo " - 40-60% smaller initial bundle"
echo " - 30-50% faster initial load"
echo " - Improved Core Web Vitals"
echo ""
echo "ð Integration Points:"
echo " - /bundle-analyze - Find heavy components"
echo " - /lighthouse - Measure improvements"
echo " - /performance-profile - Track loading performance"
echo ""
echo "ð Implementation Guide: cat $LAZY_DIR/implementation-guide.md"
echo "ð¯ Start with: $IMPLEMENTATIONS/"
Safety Guarantees
What I’ll NEVER do:
- Lazy load critical above-the-fold content
- Skip loading states (causes poor UX)
- Ignore accessibility considerations
- Break SEO with improper lazy loading
What I WILL do:
- Provide framework-specific implementations
- Include proper loading states
- Maintain SEO compatibility
- Prevent layout shifts
- Generate production-ready code
Credits
This skill is based on:
- Intersection Observer API – Modern lazy loading standard
- React.lazy/Suspense – React code splitting
- Next.js Dynamic Imports – Optimized Next.js patterns
- Web.dev Lazy Loading Guide – Best practices
- Core Web Vitals – Performance optimization guidelines
Token Budget & Optimization Details
Before Optimization: 3,000-5,000 tokens After Optimization: 1,500-2,500 tokens Savings: 50-60% reduction
Token Breakdown by Phase
Phase 1: Framework Detection & Analysis (~300-600 tokens)
- â Framework detection via package.json grep (100 tokens)
- â Cached framework config (50 tokens on cache hit vs 400 tokens on miss)
- â
Grep-based lazy loading inventory:
- Count existing lazy images (100 tokens)
- Count existing lazy components (100 tokens)
- Find total images and components (200 tokens)
- No file reads, pure grep operations
- â Early exit if everything already lazy (saves 90%, ~200 tokens vs 3,000)
Phase 2: Image Lazy Loading (~400-1,000 tokens)
- â
Focus flag
--images: Only implement image lazy loading (400 tokens vs 3,000 full) - â Template-based examples with heredocs (300-400 tokens per pattern)
- â
Progressive disclosure:
- Quick wins (native lazy attribute): 400 tokens
- Advanced (Intersection Observer): 600 tokens
- Framework-specific: 800 tokens
- All patterns: 1,000 tokens
Phase 3: Component Lazy Loading (~400-900 tokens)
- â
Focus flag
--components: Only component patterns (500 tokens vs 3,000 full) - â
Focus flag
--routes: Only route splitting (400 tokens vs 3,000 full) - â Template-based implementations per framework (300-400 tokens each)
- â No dynamic code generation, pure templates
- â Framework-specific examples only (not all frameworks)
Phase 4: Impact Analysis & Guide (~400-800 tokens)
- â Template-based implementation guide (500 tokens)
- â Progressive recommendations (show only applicable patterns)
- â Framework-specific examples only (vs all frameworks)
- â Cached opportunity analysis (80% savings on subsequent runs)
- â Performance impact estimations based on bundle size analysis
Optimization Patterns Applied
1. Grep-based Pattern Detection (90% savings)
# Count lazy images WITHOUT reading files
EXISTING_LAZY_IMAGES=$(grep -r "loading=\"lazy\"\|loading='lazy'" \
--include="*.jsx" --include="*.tsx" --include="*.html" --include="*.vue" \
--exclude-dir=node_modules \
. 2>/dev/null | wc -l)
# Count lazy components WITHOUT reading files
EXISTING_LAZY_COMPONENTS=$(grep -r "React.lazy\|lazy(\|defineAsyncComponent\|loadChildren\|dynamic(" \
--include="*.jsx" --include="*.tsx" --include="*.js" --include="*.ts" \
--exclude-dir=node_modules \
. 2>/dev/null | wc -l)
# Saves 85% vs reading files (100 tokens vs 800+ tokens)
2. Framework Detection Caching (95% savings on subsequent runs)
CACHE_FILE=".claude/cache/lazy-loading/framework.json"
CACHE_VALIDITY=86400 # 24 hours
# Verify cache validity using package.json checksum
if [ -f "$CACHE_FILE" ]; then
CURRENT_CHECKSUM=$(md5sum package.json 2>/dev/null | cut -d' ' -f1)
CACHED_CHECKSUM=$(jq -r '.package_checksum' "$CACHE_FILE" 2>/dev/null)
if [ "$CURRENT_CHECKSUM" = "$CACHED_CHECKSUM" ]; then
FRAMEWORK=$(jq -r '.framework' "$CACHE_FILE")
EXISTING_LAZY_IMAGES=$(jq -r '.lazy_images' "$CACHE_FILE")
EXISTING_LAZY_COMPONENTS=$(jq -r '.lazy_components' "$CACHE_FILE")
# Skip detection, use cached values
# Saves 400 tokens vs re-detecting
fi
fi
# Save to cache after detection
jq -n \
--arg framework "$FRAMEWORK" \
--arg checksum "$CURRENT_CHECKSUM" \
--arg lazy_images "$EXISTING_LAZY_IMAGES" \
--arg lazy_components "$EXISTING_LAZY_COMPONENTS" \
'{package_checksum: $checksum, framework: $framework, lazy_images: $lazy_images, lazy_components: $lazy_components}' \
> "$CACHE_FILE"
3. Early Exit Conditions (95% savings)
# Early exit if everything already lazy
TOTAL_IMAGES=$(find . -name "*.jsx" -o -name "*.tsx" -o -name "*.html" -o -name "*.vue" | \
xargs grep -l "<img" 2>/dev/null | wc -l)
if [ "$EXISTING_LAZY_IMAGES" -ge "$TOTAL_IMAGES" ] && [ "$TOTAL_IMAGES" -gt 0 ]; then
echo "â All images already lazy-loaded"
exit 0 # Early exit, saves ~3,000 tokens (90% savings)
fi
# Similar check for components
if [ "$EXISTING_LAZY_COMPONENTS" -gt 10 ]; then
echo "â Lazy loading already widely implemented"
echo " Use focus flags for specific optimizations: --images, --components, --routes"
exit 0 # Early exit when well-optimized, saves ~2,500 tokens
fi
4. Focus Area Flags (80% savings)
# Parse focus area from arguments
FOCUS_AREA="${1:-all}" # all, images, components, routes
case "$FOCUS_AREA" in
--images)
# Only generate image lazy loading patterns
# Saves 75-80% (400-600 tokens vs 3,000)
generate_image_lazy_loading
exit 0
;;
--components)
# Only generate component lazy loading patterns
# Saves 75-80% (500-700 tokens vs 3,000)
generate_component_lazy_loading
exit 0
;;
--routes)
# Only generate route-based code splitting
# Saves 80-85% (400-500 tokens vs 3,000)
generate_route_splitting
exit 0
;;
all)
# Full implementation (2,500 tokens)
;;
esac
5. Progressive Implementation Levels (60% savings)
# Default: Show quick wins only
IMPLEMENTATION_LEVEL="${2:-quick}" # quick, intermediate, advanced, all
case "$IMPLEMENTATION_LEVEL" in
quick)
# Native lazy attribute only
# 400 tokens vs 3,000 for all patterns (87% savings)
cat > "$IMPLEMENTATIONS/image-lazy-quick.jsx" << 'EOF'
// Quick Win: Native Lazy Loading
<img src="image.jpg" alt="Description" loading="lazy" />
EOF
;;
intermediate)
# Add Intersection Observer
# 800-1,000 tokens vs 3,000 (67% savings)
;;
advanced)
# Framework-specific optimizations
# 1,500-1,800 tokens vs 3,000 (40% savings)
;;
all)
# All patterns and examples
# 2,500-3,000 tokens (comprehensive)
;;
esac
6. Git Diff Default Scope (80% savings)
# Default to changed image/component files
if [ -z "$TARGET_FILES" ]; then
TARGET_FILES=$(git diff --name-only HEAD -- "*.jsx" "*.tsx" "*.vue" "*.html" 2>/dev/null | \
xargs grep -l "<img\|import.*from" 2>/dev/null)
if [ -z "$TARGET_FILES" ]; then
echo "â No image/component files changed"
echo " Use 'all' argument for comprehensive analysis"
exit 0 # Early exit, saves 80% tokens (500 vs 3,000)
fi
echo "Analyzing changed files only (use 'all' for full analysis)"
fi
7. Framework-Specific Templates Only (50% savings)
# Generate examples for detected framework ONLY (vs all frameworks)
case "$FRAMEWORK" in
react)
# Only React examples (600-800 tokens)
generate_react_lazy_loading
;;
vue)
# Only Vue examples (600-800 tokens)
generate_vue_lazy_loading
;;
nextjs)
# Only Next.js examples (500-700 tokens)
generate_nextjs_lazy_loading
;;
*)
# Vanilla JS examples (600-800 tokens)
generate_vanilla_lazy_loading
;;
esac
# Saves 50-60% vs generating all framework examples (3,000+ tokens)
Usage Examples
Full Analysis:
/lazy-load all
# Tokens: ~2,500 (comprehensive implementation)
Targeted Implementation (75-85% savings):
/lazy-load --images # Only image lazy loading (400-600 tokens)
/lazy-load --components # Only component patterns (500-700 tokens)
/lazy-load --routes # Only route splitting (400-500 tokens)
Quick Wins Only (85% savings):
/lazy-load --images quick # Native lazy attribute only (400 tokens vs 3,000)
# Output: Simple <img loading="lazy"> examples
Changed Files Only (80% savings):
/lazy-load # Auto-detects changed files via git diff
# Tokens: ~600-1,000 (vs 3,000 for full codebase)
Cached Execution (85% savings):
/lazy-load # Subsequent runs use cached framework detection
# First run: ~2,500 tokens
# Cached run: ~400-600 tokens (85% savings)
Specific Component:
/lazy-load Dashboard # Target specific component
# Tokens: ~600-800 (focused implementation)
Optimization Status
- â Bash-based operations: 100% of detection/analysis
- â Grep patterns: All image/component inventory (no file reads)
- â Template-based generation: Heredocs for all implementations
- â Caching: Framework detection, lazy loading inventory
- â Early exit: Already optimized check, no changes check
- â Focus areas: 3 targeted implementation modes
- â Progressive levels: 4 implementation depth levels
- â Git diff scope: Default to changed files
- â Framework-specific: Only generate relevant examples
Overall: 50-60% token reduction (3,000-5,000 â 1,500-2,500 tokens)
This ensures effective lazy loading implementation across all major frameworks with measurable performance improvements and proper UX considerations.