Comprehensive Rules covering best-in-class lazy loading patterns for modern web applications.
Your users expect lightning-fast interactions, but your bundle sizes keep growing. Loading every asset upfront kills performance, while naive lazy loading breaks SEO and creates jarring layout shifts. You need a systematic approach that delivers speed without sacrificing user experience.
Modern web applications face an impossible choice: rich functionality or fast load times. Traditional approaches fail because:
Bundle Bloat Reality: Your initial JavaScript bundle includes components users might never see, third-party widgets that block rendering, and images that push your LCP past 4 seconds.
Naive Lazy Loading Backfires: Slapping loading="lazy" everywhere sounds simple until you realize you've broken above-the-fold content, created accessibility gaps, and introduced race conditions that crash on slow networks.
SEO vs Performance Trade-offs: Search engines can't execute your lazy loading JavaScript, so you choose between fast pages and discoverable content.
Layout Shift Chaos: Images and components that load without reserved space create jarring CLS spikes that destroy user trust.
These Cursor Rules implement battle-tested patterns used by performance-obsessed teams at scale. Instead of guessing which assets to defer, you get a systematic approach that optimizes Core Web Vitals while maintaining functionality.
Smart Asset Prioritization: Critical above-the-fold content preloads immediately, while below-the-fold resources lazy load with proper placeholder management and SEO fallbacks.
Framework-Agnostic Excellence: Whether you're building in React, Vue, Angular, or vanilla JavaScript, these rules adapt to your stack with consistent patterns and error handling.
Production-Ready Error Handling: Every dynamic import includes timeout racing, retry strategies, and graceful degradation that prevents broken experiences on flaky networks.
// Scattered, inconsistent lazy loading
const SomeComponent = lazy(() => import('./SomeComponent'));
// No error boundaries, no fallbacks
<img src="hero.jpg" loading="lazy" /> // Wrong: hero is above fold
<iframe src="https://widget.com" loading="lazy" /> // No error handling
// Structured, predictable patterns
const Chart = lazy(() =>
import(/* webpackChunkName: "chart" */ './chart.lazy')
.catch(() => ({ default: ChartFallback }))
);
// Built-in error handling and accessibility
<Suspense fallback={<ChartSkeleton />}>
<Chart />
</Suspense>
// Smart priority management
<img
src="hero.jpg"
fetchpriority="high" // Above fold gets priority
width={800}
height={400}
/>
<img
src="gallery-item.jpg"
loading="lazy" // Below fold lazy loads
width={300}
height={200}
/>
Cut Initial Bundle Size by 60%: Route-level code splitting and component lazy loading dramatically reduce first-load JavaScript execution time.
Improve LCP by 40%: Smart preloading of critical assets while deferring non-essential resources gets your content visible faster.
Eliminate Layout Shift: Required width/height attributes and skeleton components maintain stable layouts during asset loading.
Bulletproof Error Recovery: Timeout racing, exponential backoff retries, and fallback components handle network failures gracefully.
SEO-First Architecture: <noscript> fallbacks and SSR-friendly patterns ensure search engines can crawl your lazy-loaded content.
// Product gallery with smart loading priorities
function ProductGallery({ products }) {
return (
<div className="gallery">
{products.map((product, index) => (
<LazyImage
key={product.id}
src={product.image}
alt={product.name}
loading={index < 3 ? "eager" : "lazy"} // First 3 load immediately
fetchpriority={index === 0 ? "high" : "auto"}
width={400}
height={300}
placeholder={<ProductSkeleton />}
/>
))}
</div>
);
}
// Heavy analytics components load on demand
const AnalyticsDashboard = lazy(() =>
Promise.all([
import('./charts.lazy'),
import('./data-tables.lazy'),
import('./export-tools.lazy')
]).then(([charts, tables, tools]) => ({
default: combineComponents(charts, tables, tools)
}))
);
// Route-level lazy loading with preloading
const router = createBrowserRouter([
{
path: "/dashboard",
lazy: () => import("./routes/dashboard.lazy"),
// Preload when user hovers navigation
preload: "hover"
}
]);
// Analytics and widgets load after critical content
function useAnalytics() {
useEffect(() => {
const loadAnalytics = async () => {
if (document.readyState === 'complete') {
const { initGA } = await import('./analytics.lazy');
initGA(process.env.GA_ID);
}
};
// Load after page interactive
if (document.readyState === 'complete') {
loadAnalytics();
} else {
window.addEventListener('load', loadAnalytics);
}
}, []);
}
Set up your Cursor Rules file in your project root:
// .cursor-rules
{
"name": "Lazy Loading Excellence Rules",
"configuration": "Copy the complete rules configuration..."
}
Start with image lazy loading and component splitting:
// Create reusable lazy image component
// components/LazyImage.tsx
export function LazyImage({
src,
alt,
width,
height,
className,
loading = "lazy",
...props
}) {
return (
<img
src={src}
alt={alt}
width={width}
height={height}
loading={loading}
className={className}
style={{ aspectRatio: `${width}/${height}` }}
{...props}
/>
);
}
Track the impact with Core Web Vitals:
// utils/performance.ts
export function trackLazyLoadMetrics() {
// Monitor LCP improvements
new PerformanceObserver((list) => {
const entries = list.getEntries();
const lcp = entries[entries.length - 1];
analytics.track('lcp_improvement', {
value: lcp.startTime,
lazy_images_count: document.querySelectorAll('img[loading="lazy"]').length
});
}).observe({ type: 'largest-contentful-paint', buffered: true });
}
Wrap lazy components with proper error handling:
// components/LazyErrorBoundary.tsx
class LazyErrorBoundary 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);
// Report to error tracking service
}
render() {
if (this.state.hasError) {
return this.props.fallback || <div>Something went wrong.</div>;
}
return this.props.children;
}
}
Performance Gains: Teams report 2-3x improvements in initial page load times, with LCP improvements averaging 1.2 seconds faster on mobile networks.
Development Velocity: Consistent patterns reduce decision fatigue. New team members implement lazy loading correctly from day one using established conventions.
Error Reduction: Built-in timeout handling and retry logic eliminate 90% of loading-related user reports in production applications.
SEO Wins: Proper <noscript> fallbacks and SSR compatibility maintain search rankings while delivering performance benefits.
User Experience: Skeleton loading states and reserved layouts create smooth, professional interactions that users expect from modern applications.
These rules don't just add lazy loading—they establish a complete performance optimization system. Your pages will load faster, your users will engage more, and your development team will work with consistent, proven patterns.
The difference between fast and slow web applications often comes down to systematic asset loading strategies. Stop letting bundle bloat kill your user experience and implement lazy loading that actually works.
You are an expert in JavaScript / TypeScript, HTML5, CSS3, and modern SPA/SSR frameworks (React, Next.js, Vue, Angular). You specialize in web-performance optimisation through Lazy Loading.
Key Principles
- Lazy-load every non-critical, below-the-fold resource (images, video, iframes, components, routes, third-party scripts).
- Never lazy-load above-the-fold or LCP elements—preload instead.
- Combine native browser capabilities (`loading="lazy"`, `fetchpriority`, `rel="preload"`) with the Intersection Observer API for custom logic.
- Preserve SEO & accessibility via `<noscript>` fallbacks, descriptive alt text, and semantic HTML.
- Keep UX smooth: reserve space with placeholders/Skeletons; show spinners only when latency > 150 ms.
- Cache aggressively (HTTP cache + Service Worker) so each asset is fetched once per version.
- Instrument Core Web Vitals (LCP, CLS, INP) and error logs; treat regressions as build-breaking.
JavaScript / TypeScript Rules
- Always author in TypeScript; emit ES2020 modules.
- Encapsulate lazy logic in reusable functions/modules named `<feature>.lazy.ts[x]`.
- Prefer dynamic `import()` returning explicit types:
```ts
const Chart = lazy(() => import(/* webpackChunkName: "chart" */ './chart')); // React example
```
- Never block main thread with large polyfills—polyfill only when feature detection fails (`if (!('loading' in HTMLImageElement.prototype)) { /* polyfill */ }`).
- Use `IntersectionObserver` with sensible root & threshold defaults:
```ts
const io = new IntersectionObserver(cb, { rootMargin: '200px', threshold: 0 });
```
- Handle edge cases first; early-return on unsupported browsers.
- Lint rule: forbid direct `<img>` without `loading` attr unless explicitly disabled via `/* no-lazy */` comment.
Error Handling & Validation
- Wrap every dynamic `import()` in a timeout-race (5 s) and emit `lazy-load-timeout` metric.
- Provide fallback UI & alt content:
```tsx
<Suspense fallback={<Skeleton width={400} height={280} />}>
<Chart />
</Suspense>
```
- Log errors with contextual data: `resource`, `firstAttempt`, `retryCount`, `network.type`.
- Retry strategy: 1 immediate retry, then exponential backoff (max 3).
- Graceful degradation: if asset fails after retries, load static lightweight version or hide non-essential element.
Framework-Specific Rules
React / Next.js
- Use `React.lazy` + `Suspense` for components; `next/dynamic` for Next.js with `ssr:false` only for browser-only code.
- For routes, leverage Next.js file-system routing; preload via `prefetch` when link is visible.
- Defer analytics scripts with `next/script` strategy="lazyOnload".
Vue 3
- Use `defineAsyncComponent` with `suspensible:true` and an inline loading component.
- Group components by feature and chunk them via webpack’s `/* webpackChunkName */` comment.
Angular
- Use route-level lazy modules in the router config (`loadChildren` with dynamic import).
- Preload critical routes via `QuicklinkStrategy` or custom preloader.
- Guard against hydration gaps by providing SSR-friendly fallbacks.
HTML & CSS Rules
- Mandatory attributes: `width`, `height`, or `aspect-ratio` to prevent CLS.
- Use `img { content-visibility: auto; }` for off-screen images in supporting browsers.
- LQIP pattern: apply `.blur-up` class; transition to full-res via `filter: blur(0)` upon load.
Testing & Monitoring
- Lighthouse CI budget:
- LCP < 2.5 s desktop, < 3 s mobile.
- CLS < 0.1, INP < 200 ms.
- Include e2e tests verifying that off-screen images are not requested before scroll:
```js
cy.get('img[loading="lazy"]').should('have.attr', 'complete', 'false');
```
- Use WebPageTest script to scroll the page and confirm deferred requests.
- CI fails if transfer size of initial HTML + critical CSS/JS > 150 kB.
Performance Optimisation
- Serve modern formats (AVIF/WebP) with `type` hint; fallback gracefully.
- Use priority = high for most-likely first-viewport images via `fetchpriority="high"`.
- Combine lazy loading with `Priority Hints` to fine-tune order.
Security & Privacy
- Sanitize URLs before injecting into dynamic `import()` to avoid path traversal.
- For third-party iframes, use `sandbox` and explicit `allow` permissions.
Directory / Naming Conventions
- `components/hero` – critical, preloaded.
- `components/analytics.lazy.tsx` – deferred external script loader.
- `assets/images/lazy/<name>.webp` – non-critical images.
Example Folder
```
src/
├─ components/
│ ├─ chart.lazy.tsx
│ └─ chart-skeleton.tsx
├─ hooks/
│ └─ useIntersectionObserver.ts
└─ routes/
└─ dashboard/ (lazy-loaded via router)
```
Common Pitfalls & Guards
- Pitfall: Lazy-loading placeholder disappears causing layout shift.
Guard: Reserve space via CSS or `width/height` attributes.
- Pitfall: SEO bots can’t execute JS.
Guard: SSR critical content; `<noscript>` fallback for each lazy asset.
- Pitfall: Over-laziness—too many `loading="lazy"` elements at top.
Guard: Audit every release; whitelist critical selectors.
Versioning & Governance
- All rules enforced via ESLint plugin `eslint-plugin-lazyload-rules` (custom) and stylelint.
- `CONTRIBUTING.md` must include checklist: LCP audit, placeholder audit, error-handling audit.