Next.js course · Module 3: Styling and Visual Optimization

Image Loading Strategies (lazy, eager, priority) in Next.js 16

16 min read ·
In this lesson13

In Metropolis Quantum, the city's transportation system is organized according to different priorities. Emergency vehicles have the highest priority and always have the right of way. Public transport has medium priority, and private vehicles have the lowest. This intelligent traffic management ensures that the most important resources always reach their destination fastest, while less critical ones can wait.

Similarly in web applications, different images have different levels of importance. The hero image of the page should load immediately, while images placed lower on the page can be loaded only when the user scrolls to them. Next.js 16 offers different image loading strategies that allow you to customize how images are loaded based on their importance and position on the page.

Three Main Image Loading Strategies

In Next.js 16, we have three main image loading strategies available:

  1. Lazy - default strategy, images are loaded only when they approach the visible area (viewport)
  2. Eager - images are loaded immediately, regardless of their position on the page
  3. Priority - images are loaded with the highest priority, ideal for "above the fold" images. In Next.js 16 you turn this strategy on with the preload attribute, which replaced the deprecated priority attribute

Each of these strategies has its use case and impact on page performance.

Lazy Loading

Lazy loading is the default strategy for the Image component in Next.js 16. It works according to a simple principle:

Load only what the user currently sees or will see soon.

How Does Lazy Loading Work?

  1. Next.js automatically delays loading images that are outside the visible area (viewport)
  2. Images are loaded only when the user scrolls the page and they approach the visible area
  3. Behind the scenes, the modern browser feature loading="lazy" is used, with additional optimizations

Lazy Loading Usage Example:

import Image from 'next/image';

export default function QuantumGallery() {
  return (
    <div className="gallery">
      {Array.from({ length: 20 }).map((_, index) => (
        <div key={index} className="relative h-64 w-full">
          <Image
            src={`/images/quantum-city-${index + 1}.jpg`}
            alt={`Metropolis Quantum - photo ${index + 1}`}
            fill
            style={{ objectFit: 'cover' }}
            // No need to add loading="lazy" - it is the default value
          />
        </div>
      ))}
    </div>
  );
}

Benefits of Lazy Loading:

  1. Faster initial page loading - only critical images are loaded immediately
  2. Less data consumption - images that the user never reaches are not loaded
  3. Better performance - the browser can focus resources on elements that are currently important

Scenarios Where Lazy Loading is Worth Using:

  • Long pages with many images
  • Photo galleries
  • Product lists
  • Image carousels
  • Infinite scroll pages

Eager Loading

The eager loading strategy is the opposite of lazy loading. Instead of waiting for images to approach the visible area, images are loaded immediately during page loading.

How to Set Eager Loading:

import Image from 'next/image';

export default function QuantumLogo() {
  return (
    <Image
      src="/images/quantum-logo.png"
      alt="Logo Metropolis Quantum"
      width={200}
      height={100}
      loading="eager"
    />
  );
}

Benefits of Eager Loading:

  1. Immediate availability - all images are available right after page loading
  2. No delays - the user does not have to wait for images to load while scrolling
  3. More predictable behavior - all resources are loaded in one cycle

Scenarios Where Eager Loading is Worth Using:

  • Short pages with a small number of images
  • Pages where all images are important for context
  • Applications where smooth scrolling without delays is crucial
  • Offline-first applications that want to load all resources at once

Priority Loading

Priority loading is a special strategy for the most important images on the page, especially those visible "above the fold" (in the upper part of the page, visible without scrolling).

How to Set Priority Loading:

import Image from 'next/image';

export default function QuantumHero() {
  return (
    <Image
      src="/images/quantum-hero.jpg"
      alt="Welcome to Metropolis Quantum"
      width={1920}
      height={1080}
      preload
    />
  );
}

When you use the preload attribute (called priority in Next.js 15 and earlier):

  1. Next.js adds a <link rel="preload"> tag for this image to the <head>
  2. The browser starts downloading the image right away, before it reaches it in the page content
  3. The largest element of the page (LCP) appears sooner

The old priority attribute still works in Next.js 16, but it is deprecated. The documentation also advises using preload only when the image is definitely the LCP element; in other cases choose loading="eager" or fetchPriority="high".

Benefits of Priority Loading:

  1. Lower LCP (Largest Contentful Paint) - a key Core Web Vitals metric
  2. Faster display of the most important images - better perceived performance
  3. Core Web Vitals optimization - positive impact on SEO

Scenarios Where Priority is Worth Using:

  • Main page hero images
  • Brand logo
  • Product images on detail pages
  • User avatars in social applications
  • Any images that constitute the main content of the page

Key Notes About the preload Attribute:

  • Use preload sparingly - only for the most important images
  • The Next.js documentation recommends preload only for the LCP (Largest Contentful Paint) image, not for several images that may become it depending on the screen width
  • Too many images with preload can paradoxically decrease performance

Loading Strategy Comparison

StrategyAttributeUse CasesPerformance Impact
Lazyloading="lazy" (default)Images below the fold, galleries, listsFaster initial loading, data savings
Eagerloading="eager"Small pages, all images importantSlower initial loading, better responsiveness while scrolling
Prioritypreload (formerly priority)"Above the fold" images, hero images, main contentBetter LCP, faster display of key images

Advanced Image Loading Strategies

Beyond the basic strategies, Next.js 16 allows for more advanced approaches to image loading:

1. Preloading Important Images with useRouter

Sometimes we want to preload images for a page the user will likely navigate to:

'use client';

import { useEffect } from 'react';
import { useRouter } from 'next/navigation';
import Image from 'next/image';

export default function ProductCard({ product, isPriority }) {
  const router = useRouter();

  useEffect(() => {
    // Preload product detail image on mouse hover
    const preloadDetailImage = () => {
      router.prefetch(`/products/${product.id}`);
    };

    const cardElement = document.getElementById(`product-card-${product.id}`);
    if (cardElement) {
      cardElement.addEventListener('mouseenter', preloadDetailImage);
      return () => {
        cardElement.removeEventListener('mouseenter', preloadDetailImage);
      };
    }
  }, [product.id, router]);

  return (
    <div
      id={`product-card-${product.id}`}
      className="relative h-64 overflow-hidden rounded-lg"
      onClick={() => router.push(`/products/${product.id}`)}
    >
      <Image
        src={product.thumbnail}
        alt={product.title}
        fill
        style={{ objectFit: 'cover' }}
        preload={isPriority}
      />
      <div className="absolute bottom-0 w-full bg-black/60 p-4">
        <h3 className="text-white font-bold">{product.title}</h3>
      </div>
    </div>
  );
}

2. Dynamic Priority Switching Based on Visibility

We can dynamically assign priority to images that are currently visible:

'use client';

import { useState, useEffect, useRef } from 'react';
import Image from 'next/image';

export default function DynamicPriorityImage({ src, alt }) {
  const [isPriority, setIsPriority] = useState(false);
  const imageRef = useRef(null);

  useEffect(() => {
    const observer = new IntersectionObserver(
      ([entry]) => {
        if (entry.isIntersecting) {
          setIsPriority(true);
          observer.disconnect();
        }
      },
      { threshold: 0.1 }
    );

    if (imageRef.current) {
      observer.observe(imageRef.current);
    }

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

  return (
    <div ref={imageRef} className="relative h-64 w-full">
      <Image
        src={src}
        alt={alt}
        fill
        style={{ objectFit: 'cover' }}
        preload={isPriority}
      />
    </div>
  );
}

3. Progressive Image Loading

You can combine strategies to first display a low-quality image and then replace it with a high-quality version:

'use client';

import { useState } from 'react';
import Image from 'next/image';

export default function ProgressiveImage() {
  const [isLoaded, setIsLoaded] = useState(false);

  return (
    <div className="relative h-96 w-full">
      {/* Low-quality placeholder */}
      <Image
        src="/images/quantum-city-low.jpg"
        alt="Metropolis Quantum"
        fill
        style={{
          objectFit: 'cover',
          opacity: isLoaded ? 0 : 1,
          transition: 'opacity 0.5s ease-in-out'
        }}
        preload // Placeholder loaded immediately
      />

      {/* High quality image */}
      <Image
        src="/images/quantum-city-high.jpg"
        alt="Metropolis Quantum"
        fill
        style={{
          objectFit: 'cover',
          opacity: isLoaded ? 1 : 0,
          transition: 'opacity 0.5s ease-in-out'
        }}
        onLoad={() => setIsLoaded(true)}
      />
    </div>
  );
}

Image Optimization for Different Devices

Loading strategies can be combined with techniques for adapting images to different devices:

1. Responsive Images with Different Sources

import Image from 'next/image';

export default function ResponsiveHero() {
  return (
    <>
      {/* Image for mobile devices */}
      <div className="md:hidden">
        <Image
          src="/images/hero-mobile.jpg"
          alt="Metropolis Quantum"
          width={640}
          height={960}
          preload // Priority for the main image
        />
      </div>

      {/* Image for tablets */}
      <div className="hidden md:block lg:hidden">
        <Image
          src="/images/hero-tablet.jpg"
          alt="Metropolis Quantum"
          width={1024}
          height={768}
          preload
        />
      </div>

      {/* Image for desktops */}
      <div className="hidden lg:block">
        <Image
          src="/images/hero-desktop.jpg"
          alt="Metropolis Quantum"
          width={1920}
          height={1080}
          preload
        />
      </div>
    </>
  );
}

2. Adaptive Loading Based on Connection

'use client';

import { useState, useEffect } from 'react';
import Image from 'next/image';

export default function AdaptiveLoadingImage() {
  const [connectionType, setConnectionType] = useState('high');

  useEffect(() => {
    // Detect connection quality
    const connection = navigator.connection ||
                      navigator.mozConnection ||
                      navigator.webkitConnection;

    if (connection) {
      if (connection.effectiveType === '4g' && !connection.saveData) {
        setConnectionType('high');
      } else {
        setConnectionType('low');
      }

      // Listen for changes in connection quality
      const updateConnectionStatus = () => {
        if (connection.effectiveType === '4g' && !connection.saveData) {
          setConnectionType('high');
        } else {
          setConnectionType('low');
        }
      };

      connection.addEventListener('change', updateConnectionStatus);
      return () => connection.removeEventListener('change', updateConnectionStatus);
    }
  }, []);

  return (
    <div className="relative h-64 w-full">
      <Image
        src={connectionType === 'high' ? '/images/high-quality.jpg' : '/images/low-quality.jpg'}
        alt="Adaptive image"
        fill
        style={{ objectFit: 'cover' }}
        loading={connectionType === 'high' ? 'eager' : 'lazy'}
        quality={connectionType === 'high' ? 90 : 50}
      />
    </div>
  );
}

Remember that in Next.js 16 both quality values (90 and 50) must be on the images.qualities list in next.config. By default the list contains only 75, so without extending it both variants get quality 75.

Implementing Advanced Loading Strategies in a Real Application

Example: Product Page with Different Loading Strategies

// app/products/[id]/page.tsx
import Image from 'next/image';
import { getProduct, getRelatedProducts } from '@/lib/api';
import RelatedProducts from '@/components/RelatedProducts';

export default async function ProductPage({ params }) {
  const product = await getProduct((await params).id);
  const relatedProducts = await getRelatedProducts((await params).id);

  return (
    <div className="container mx-auto px-4 py-8">
      <div className="grid grid-cols-1 md:grid-cols-2 gap-8">
        {/* Main product image - priority loading */}
        <div className="relative aspect-square">
          <Image
            src={product.mainImage}
            alt={product.name}
            fill
            style={{ objectFit: 'contain' }}
            preload
            sizes="(max-width: 768px) 100vw, 50vw"
          />
        </div>

        <div>
          <h1 className="text-3xl font-bold mb-4">{product.name}</h1>
          <p className="text-xl mb-4">{product.price}</p>
          <div className="prose max-w-none">
            <p>{product.description}</p>
          </div>

          <button className="mt-6 bg-blue-600 text-white px-6 py-3 rounded-md">
            Add to cart
          </button>
        </div>
      </div>

      {/* Additional photos gallery - lazy loading (default) */}
      <div className="mt-12">
        <h2 className="text-2xl font-bold mb-6">Product gallery</h2>
        <div className="grid grid-cols-2 md:grid-cols-4 gap-4">
          {product.gallery.map((image, index) => (
            <div key={index} className="relative aspect-square">
              <Image
                src={image.url}
                alt={`${product.name} - photo ${index + 1}`}
                fill
                style={{ objectFit: 'cover' }}
                sizes="(max-width: 768px) 50vw, 25vw"
                // Default lazy loading
              />
            </div>
          ))}
        </div>
      </div>

      {/* Related products - eager loading for the first item */}
      <div className="mt-12">
        <h2 className="text-2xl font-bold mb-6">Related products</h2>
        <RelatedProducts products={relatedProducts} />
      </div>
    </div>
  );
}
// components/RelatedProducts.tsx
'use client';

import Image from 'next/image';
import Link from 'next/link';
import { useRef, useEffect } from 'react';

export default function RelatedProducts({ products }) {
  const containerRef = useRef(null);

  useEffect(() => {
    // Preload images when hovering over the section
    const preloadImages = () => {
      products.forEach(product => {
        const img = new Image();
        img.src = product.image;
      });
    };

    const container = containerRef.current;
    if (container) {
      container.addEventListener('mouseenter', preloadImages, { once: true });
      return () => {
        container.removeEventListener('mouseenter', preloadImages);
      };
    }
  }, [products]);

  return (
    <div ref={containerRef} className="grid grid-cols-2 md:grid-cols-4 gap-4">
      {products.map((product, index) => (
        <Link key={product.id} href={`/products/${product.id}`}>
          <div className="relative aspect-square rounded-lg overflow-hidden">
            <Image
              src={product.image}
              alt={product.name}
              fill
              style={{ objectFit: 'cover' }}
              // Eager loading only for the first product
              loading={index === 0 ? 'eager' : 'lazy'}
              sizes="(max-width: 768px) 50vw, 25vw"
            />
            <div className="absolute bottom-0 w-full bg-black/60 p-2">
              <p className="text-white text-sm font-medium truncate">
                {product.name}
              </p>
            </div>
          </div>
        </Link>
      ))}
    </div>
  );
}

Impact of Loading Strategies on Core Web Vitals

Core Web Vitals is a set of Google metrics that measure user experience on websites. Choosing the right image loading strategy has a direct impact on these metrics:

1. LCP (Largest Contentful Paint)

LCP measures the loading time of the largest visible element in the viewport, which is often an image.

Impact of loading strategies:

  • Priority loading for the main image can significantly improve LCP
  • Eager loading for all images can worsen LCP by dispersing resources
  • Lazy loading for images below the fold does not affect LCP but improves overall performance

2. CLS (Cumulative Layout Shift)

CLS measures page layout instability during loading.

Impact of loading strategies:

  • All strategies can minimize CLS if images have specified dimensions
  • Priority loading with placeholder="blur" provides the best CLS as it reserves space immediately
  • Always specify width and height or use fill with a container of defined dimensions

3. FID (First Input Delay)

FID measures the time from the user's first interaction to the moment when the browser can respond to it.

Impact of loading strategies:

  • Lazy loading can improve FID by reducing browser work during loading
  • Eager loading can worsen FID by increasing main thread load
  • Priority loading for key images can be a compromise

Measuring and Optimizing Loading Strategies

To ensure that the chosen loading strategies work optimally, it is worth measuring their impact:

1. Performance Measurement Tools

  • Lighthouse - comprehensive performance analysis tool
  • Chrome DevTools Performance - detailed resource loading analysis
  • WebPageTest - performance testing under various conditions
  • Core Web Vitals in Google Search Console

2. A/B Testing of Loading Strategies

'use client';

import { useState, useEffect } from 'react';
import Image from 'next/image';

export default function ABTestImage({ src, alt }) {
  const [testGroup, setTestGroup] = useState('');

  useEffect(() => {
    // Assign user to group A or B
    const group = Math.random() > 0.5 ? 'A' : 'B';
    setTestGroup(group);

    // Save telemetry data
    const trackImageLoad = (time) => {
      // Sending data to the analytics system
      console.log(`Image loaded in ${time}ms (Group ${group})`);
    };

    const startTime = performance.now();
    const onLoadComplete = () => {
      const loadTime = performance.now() - startTime;
      trackImageLoad(loadTime);
    };

    // Listen for the image load event
    const imageEl = document.getElementById('test-image');
    if (imageEl) {
      imageEl.addEventListener('load', onLoadComplete);
      return () => {
        imageEl.removeEventListener('load', onLoadComplete);
      };
    }
  }, []);

  if (!testGroup) return null;

  return (
    <div className="relative h-96 w-full">
      <Image
        id="test-image"
        src={src}
        alt={alt}
        fill
        style={{ objectFit: 'cover' }}
        preload={testGroup === 'A'} // Group A uses preload, group B does not
      />
    </div>
  );
}

Best Practices for Image Loading Strategies

  1. Use preload only for above-the-fold images - ideally for a single LCP image per page

  2. Use lazy loading by default - this saves resources and speeds up initial page loading

  3. Monitor Web Vitals metrics - regularly check how loading strategies affect performance

  4. Adapt strategies to the page type:

    • Product pages: preload for the main product photo
    • Category pages: lazy for most images, eager for the first few
    • Homepage: preload for hero image, lazy for the rest
  5. Consider user devices:

    • Mobile users often have slower connections - it is even more important to use lazy and smaller images
    • Desktop users can benefit from eager for a better scrolling experience
  6. Use placeholders for a better loading experience:

    • For images with preload - show a blur or empty placeholder
    • For images with lazy - use blur for a smooth transition

Scenarios for Different Strategies

1. E-commerce Homepage

export default function Homepage() {
  return (
    <div>
      {/* Hero section - priority */}
      <div className="relative h-[80vh]">
        <Image
          src="/images/hero.jpg"
          alt="Welcome to Quantum Shop"
          fill
          style={{ objectFit: 'cover' }}
          preload
        />
      </div>

      {/* Promotions - eager for the first two */}
      <div className="grid grid-cols-2 md:grid-cols-4 gap-4 my-8">
        {promotions.map((promo, index) => (
          <div key={promo.id} className="relative aspect-square">
            <Image
              src={promo.image}
              alt={promo.title}
              fill
              style={{ objectFit: 'cover' }}
              loading={index < 2 ? 'eager' : 'lazy'}
            />
          </div>
        ))}
      </div>

      {/* Product categories - lazy (default) */}
      <div className="grid grid-cols-2 md:grid-cols-3 gap-6 my-12">
        {categories.map(category => (
          <div key={category.id} className="relative aspect-video">
            <Image
              src={category.image}
              alt={category.name}
              fill
              style={{ objectFit: 'cover' }}
              // default lazy loading
            />
          </div>
        ))}
      </div>
    </div>
  );
}

2. Blog with Long Articles

export default function BlogPost({ post }) {
  return (
    <article className="max-w-3xl mx-auto py-8">
      {/* Main article image - priority */}
      <div className="relative aspect-video mb-8">
        <Image
          src={post.featuredImage}
          alt={post.title}
          fill
          style={{ objectFit: 'cover' }}
          preload
        />
      </div>

      <h1 className="text-3xl font-bold mb-4">{post.title}</h1>
      <div className="prose max-w-none">
        {post.content.map((block, index) => {
          if (block.type === 'paragraph') {
            return <p key={index}>{block.content}</p>;
          }
          if (block.type === 'image') {
            return (
              <div key={index} className="relative aspect-video my-8">
                <Image
                  src={block.url}
                  alt={block.caption || ''}
                  fill
                  style={{ objectFit: 'contain' }}
                  // default lazy loading for images in content
                />
                {block.caption && (
                  <p className="text-sm text-gray-500 mt-2 text-center">
                    {block.caption}
                  </p>
                )}
              </div>
            );
          }
          return null;
        })}
      </div>

      {/* Related articles - all lazy */}
      <div className="mt-12">
        <h2 className="text-2xl font-bold mb-6">Related Articles</h2>
        <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
          {post.relatedPosts.map(relatedPost => (
            <div key={relatedPost.id} className="relative aspect-video">
              <Image
                src={relatedPost.thumbnail}
                alt={relatedPost.title}
                fill
                style={{ objectFit: 'cover' }}
                // All lazy (default)
              />
            </div>
          ))}
        </div>
      </div>
    </article>
  );
}

Summary

Choosing the right image loading strategy is crucial for optimizing performance and user experience. Just like in the transportation system of Metropolis Quantum, where vehicles with different priorities move with varying urgency, in a web application images can be loaded with different strategies depending on their importance:

  1. Priority loading - for the most important "above the fold" images that have a direct impact on LCP and the user's first impression

  2. Eager loading - for images that are less critical but still important, and will likely be needed soon after page loading

  3. Lazy loading - for most images, especially those below the fold, which will be loaded only when the user reaches them

When choosing a loading strategy for your images, consider:

  • Image visibility without scrolling
  • Impact on key performance metrics
  • Content type and its importance
  • Your users' devices and internet connections

By properly applying image loading strategies, you can create an application that loads quickly, efficiently manages resources, and provides a smooth user experience - just like the intelligent transportation system of Metropolis Quantum, which ensures smooth flow of vehicles with different priorities.

In the next chapter, we will focus on font optimization and the Font component, which will allow us to further improve the performance and aesthetics of our Next.js 16 application.

Code for this lesson: app/page.tsx
// Image Loading Strategies - lazy, eager, priority
'use client';

import { useState } from 'react';

export default function LoadingStrategiesDemo() {
  const [activeStrategy, setActiveStrategy] = useState('lazy');

  const strategies = [
    {
      id: 'lazy',
      name: 'Lazy Loading',
      icon: '',
      color: 'blue',
      description: 'Default strategy. Images load only when approaching the viewport.',
      code: `<Image
  src="/photo.jpg"
  alt="Gallery image"
  width={800}
  height={600}
  loading="lazy"  // default
/>`,
      useCase: 'Below-the-fold images, galleries, blog content images',
      pros: ['Saves bandwidth', 'Faster initial page load', 'Default behavior'],
      cons: ['Slight delay when scrolling to image', 'Not ideal for hero images'],
    },
    {
      id: 'eager',
      name: 'Eager Loading',
      icon: '',
      color: 'green',
      description: 'Images load immediately, regardless of position on page.',
      code: `<Image
  src="/photo.jpg"
  alt="Important image"
  width={800}
  height={600}
  loading="eager"
/>`,
      useCase: 'Images likely to be seen quickly, carousels, critical UI elements',
      pros: ['No loading delay', 'Good for near-viewport images'],
      cons: ['Uses more bandwidth', 'Can slow initial load'],
    },
    {
      id: 'priority',
      name: 'Priority Loading',
      icon: '',
      color: 'purple',
      description: 'Highest priority loading. Preloaded in <head> for fastest display.',
      code: `<Image
  src="/hero.jpg"
  alt="Hero banner"
  width={1920}
  height={1080}
  priority  // preloads in <head>
/>`,
      useCase: 'Hero images, LCP elements, above-the-fold content',
      pros: ['Fastest loading', 'Improves LCP score', 'Preloaded in head'],
      cons: ['Use sparingly (1-2 per page)', 'Increases initial payload'],
    },
  ];

  const active = strategies.find(s => s.id === activeStrategy);

  return (
    <div className="min-h-screen bg-gray-900 text-white p-8">
      <div className="max-w-4xl mx-auto">
        <h1 className="text-3xl font-bold mb-2">Image Loading Strategies</h1>
        <p className="text-gray-400 mb-8">
          Choose the right strategy for each image in your Next.js app
        </p>

        {/* Strategy Tabs */}
        <div className="flex gap-4 mb-8">
          {strategies.map(s => (
            <button
              key={s.id}
              onClick={() => setActiveStrategy(s.id)}
              className={`px-6 py-3 rounded-lg font-medium transition-colors ${
                activeStrategy === s.id
                  ? 'bg-blue-600 text-white'
                  : 'bg-gray-800 text-gray-400 hover:bg-gray-700'
              }`}
            >
              {s.icon} {s.name}
            </button>
          ))}
        </div>

        {/* Active Strategy Details */}
        {active && (
          <div className="bg-gray-800 rounded-xl p-8">
            <h2 className="text-2xl font-bold mb-2">
              {active.icon} {active.name}
            </h2>
            <p className="text-gray-400 mb-6">{active.description}</p>

            {/* Code Example */}
            <div className="bg-gray-900 rounded-lg p-4 mb-6">
              <pre className="text-sm text-green-400 overflow-x-auto">
                {active.code}
              </pre>
            </div>

            {/* Use Cases */}
            <div className="mb-6">
              <h3 className="font-semibold mb-2">When to use:</h3>
              <p className="text-gray-400">{active.useCase}</p>
            </div>

            {/* Pros & Cons */}
            <div className="grid grid-cols-2 gap-4">
              <div>
                <h3 className="font-semibold text-green-400 mb-2">Pros</h3>
                <ul className="space-y-1">
                  {active.pros.map((p, i) => (
                    <li key={i} className="text-sm text-gray-400">• {p}</li>
                  ))}
                </ul>
              </div>
              <div>
                <h3 className="font-semibold text-red-400 mb-2">Cons</h3>
                <ul className="space-y-1">
                  {active.cons.map((c, i) => (
                    <li key={i} className="text-sm text-gray-400">• {c}</li>
                  ))}
                </ul>
              </div>
            </div>
          </div>
        )}

        {/* Priority Guide */}
        <div className="mt-8 bg-gray-800 rounded-xl p-6">
          <h2 className="text-xl font-bold mb-4">Strategy Map</h2>
          <div className="space-y-3">
            <div className="flex items-center gap-4 p-3 bg-purple-900/30 rounded-lg">
              <span className="text-2xl">↯</span>
              <div>
                <span className="font-semibold text-purple-400">Priority</span>
                <span className="text-gray-400 ml-2">→ Hero image, main banner (1-2 per page)</span>
              </div>
            </div>
            <div className="flex items-center gap-4 p-3 bg-green-900/30 rounded-lg">
              <span className="text-2xl">»</span>
              <div>
                <span className="font-semibold text-green-400">Eager</span>
                <span className="text-gray-400 ml-2">→ Near-viewport images, carousel slides</span>
              </div>
            </div>
            <div className="flex items-center gap-4 p-3 bg-blue-900/30 rounded-lg">
              <span className="text-2xl">›</span>
              <div>
                <span className="font-semibold text-blue-400">Lazy</span>
                <span className="text-gray-400 ml-2">→ Everything else (default)</span>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
}

Docs entries used in this lesson

Spotted a mistake in this lesson?

Check yourself

Answer the questions from this lesson. Pick an answer to see right away whether it is correct.

  1. 1. Styled-components is a library for:

  2. 2. Mobile-first design means:

Hands-on tasks in the game

  • Code editor

    Create a file with CSS variables defining a complete design system (colors, spacing, typography, shadows)

  • Vertical ordering

    Arrange the steps for using CSS Modules in Next.js in the correct order:

  • Click in order

    Arrange the className syntax with Tailwind CSS classes in JSX

  • Code editor

    Create keyframes animation for a fade-in effect and hover transition for a button

Useful articles