AI Loads Everything on Page Load
AI generates pages that: load all images immediately (50 images below the fold, all requested at once), fetch all data upfront (API calls for tabs the user may never click), render all components (chart libraries, maps, and editors loaded but not visible), show no loading states (blank space until content appears, then layout shift), and prioritize nothing (hero image loads at the same priority as a footer icon). The initial page load is slow, bandwidth is wasted, and the user stares at a blank screen.
Modern lazy loading is: image-native (loading='lazy' for below-fold images), observer-driven (Intersection Observer triggers component/data loading when visible), skeleton-rich (layout-matching placeholders during load), priority-aware (fetchpriority='high' for hero image, 'low' for non-critical), and progressive (load critical data first, secondary data on interaction). AI generates none of these.
These rules cover: native image lazy loading, Intersection Observer for components, progressive data fetching, skeleton loading states, and priority hints for critical above-the-fold content.
Rule 1: Native Image Lazy Loading
The rule: 'Use loading="lazy" on all images below the fold: <img src="photo.jpg" loading="lazy" alt="Description" width="400" height="300" />. The browser defers loading until the image is near the viewport (approximately 1250px away on fast connections, further on slow connections). Above-the-fold images (hero, logo) should NOT have loading='lazy' — they need to load immediately for LCP.'
For width and height: 'Always include width and height attributes (or CSS aspect-ratio) on lazy images. The browser reserves the correct space before the image loads, preventing layout shift (CLS). Without dimensions: the image loads, the layout jumps, content moves, and CLS score suffers. Next.js Image component handles this automatically with required width/height props.'
AI generates: <img src='photo.jpg' /> — no loading attribute (eager by default), no dimensions (layout shift on load). A page with 50 product images makes 50 HTTP requests on initial load — most for images the user cannot see. loading='lazy': only visible images load initially. The rest load as the user scrolls. Same user experience, fraction of the initial bandwidth.
- loading='lazy' on all below-fold images — browser defers loading until near viewport
- Never loading='lazy' on above-fold images — hero and LCP images load immediately
- Always width + height or aspect-ratio — prevent layout shift (CLS)
- Next.js Image: automatic lazy loading, required dimensions, optimized formats
- 50 images on page: 5 load initially, 45 load on scroll — 10x less initial bandwidth
A product listing with 50 images: without loading='lazy', 50 HTTP requests on initial load. With loading='lazy': 5 visible images load, 45 load on scroll. Same page, 10x less initial bandwidth, faster LCP.
Rule 2: Intersection Observer for Component Loading
The rule: 'Use Intersection Observer to trigger component loading when the component area enters the viewport: const ref = useRef(null); const [isVisible, setVisible] = useState(false); useEffect(() => { const observer = new IntersectionObserver(([entry]) => { if (entry.isIntersecting) setVisible(true); }, { rootMargin: "200px" }); observer.observe(ref.current); return () => observer.disconnect(); }, []). Load the heavy component only when isVisible is true.'
For the rootMargin trick: 'rootMargin: "200px" triggers the observer 200px before the element enters the viewport. This gives the browser a head start — by the time the user scrolls to the component, it is already loaded and rendered. Adjust rootMargin based on component load time: fast components (50ms) need 100px margin; heavy components (500ms) need 500px margin.'
AI generates: import HeavyChart from './HeavyChart' with static import at the top of the page. The chart is below the fold, inside a tab, and most users never see it. With Intersection Observer: the chart chunk downloads and renders only when the chart area scrolls into view. The initial page load is hundreds of KB lighter.
Rule 3: Progressive Data Fetching
The rule: 'Fetch data in priority order: (1) above-the-fold content (hero, primary content — fetched server-side or in parallel), (2) visible secondary content (sidebar, related items — fetched on mount), (3) below-the-fold content (comments, recommendations — fetched on scroll/interaction), (4) predictive prefetch (next page data — fetched on hover/idle). Never fetch everything in one waterfall of API calls on page load.'
For tab-based content: 'When content is behind tabs: fetch only the active tab data on page load. Fetch other tab data on tab click (or prefetch on tab hover). A settings page with 5 tabs: fetching all 5 tabs on load means 5 API calls, 5 loading states, and 80% wasted bandwidth (users typically view 1-2 tabs). Fetch the active tab immediately; prefetch the rest lazily.'
AI generates: useEffect(() => { fetchUsers(); fetchPosts(); fetchComments(); fetchAnalytics(); fetchSettings(); }, []) — five API calls on mount, all blocking, all sequential (if awaited in series). Progressive loading: fetchUsers() on mount (critical), fetchComments() on scroll to comments section, fetchAnalytics() on tab click. Same data, better perceived performance.
Settings page with 5 tabs: fetching all on mount = 5 API calls, 80% wasted bandwidth (users view 1-2 tabs). Fetch active tab on mount, others on click. Same data available, dramatically less initial load.
Rule 4: Skeleton Loading States
The rule: 'Replace loading spinners with skeleton screens that match the layout of the content being loaded. A skeleton is: a gray placeholder with the same dimensions and layout as the final content. When the content loads, it replaces the skeleton with zero layout shift. Skeletons show progress (the page is loading, content will appear here) while spinners show activity (something is happening, somewhere, eventually).'
For skeleton design: 'Match the content layout: text lines are gray rectangles (varying widths for visual interest), images are gray rectangles with the correct aspect ratio, buttons are gray rounded rectangles, and avatars are gray circles. Animate with a shimmer effect (CSS gradient animation). Never show a skeleton for more than 3 seconds — if content takes longer, show a progress indicator or message.'
AI generates: {loading ? <Spinner /> : <Content />} — a generic spinner that tells the user nothing about what is loading or where it will appear. When content loads, the entire layout shifts from centered-spinner to actual content. Skeleton screens: zero layout shift, clear indication of where content will appear, and perceived faster load times (users see the page structure immediately).
Spinners say: something is happening, somewhere. Skeletons say: content will appear here, in this shape, at this size. Zero layout shift when content loads. Users perceive skeleton pages as loading faster than spinner pages — same actual load time.
Rule 5: Priority Hints for Critical Content
The rule: 'Use fetchpriority="high" on the LCP element (largest contentful paint — usually the hero image): <img src="hero.jpg" fetchpriority="high" />. Use fetchpriority="low" on non-critical resources (footer images, decorative icons). Preload critical resources: <link rel="preload" href="hero.jpg" as="image" />. These hints tell the browser: load this first, load that later — optimizing the critical rendering path.'
For font preloading: 'Preload web fonts used above the fold: <link rel="preload" href="/fonts/inter.woff2" as="font" type="font/woff2" crossorigin />. Without preloading: the browser discovers the font only when it parses the CSS, triggering a late request and a flash of unstyled/invisible text (FOUT/FOIT). Preloading: the font request starts immediately, available when the CSS needs it.'
AI generates: all resources at default priority. The hero image competes with footer icons and analytics scripts for bandwidth. fetchpriority='high' on the hero image: the browser prioritizes it, LCP improves. fetchpriority='low' on non-critical images: they yield bandwidth to critical content. Same resources, optimized loading order.
Complete Lazy Loading Rules Template
Consolidated rules for lazy loading.
- loading='lazy' on below-fold images — never on above-fold LCP images
- Width + height on all images — prevent layout shift (CLS)
- Intersection Observer with rootMargin for component lazy loading
- Progressive data fetching: above-fold first, below-fold on scroll, tabs on click
- Skeleton screens: match content layout, zero shift, shimmer animation
- fetchpriority='high' on LCP element — fetchpriority='low' on non-critical
- Preload critical fonts and hero images — <link rel='preload'>
- Never fetch all data on mount — prioritize by visibility and interaction likelihood