More Menu
Reading ListGanti TemaSearch
Reading List

Queue · 0 items

Your reading list is empty. Save articles to read them later.

Start Reading

How to Optimize Next.js Image Scrolling for Better UX

Iwan Efendi3 min

Learn how to eliminate scrolling lag in Next.js by optimizing image loading and leveraging hardware acceleration.

Artikel ini tersedia dalam Bahasa IndonesiaBaca dalam ID →
Baca dalam IDID
Collaborating with AI speeds up development, but it often leads to a common pitfall: heavy, unoptimized media that makes the user interface feel "heavy" or "janky" during scrolling. In our latest optimization round, we tackled this head-on.

The Problem: Why Does Scrolling Feel Heavy?

When a page contains many images (like a blog feed or a featured section), the browser often struggles with two things:
  1. Bandwidth Overload: Downloading 2MB images for a 300px container because the browser doesn't know the final display size.
  2. Main Thread Blocking: Calculating complex CSS animations and transformations (like hover scales or rotations) purely on the CPU.

The Before and After: Code Comparison

Below is a comparison of how images were styled and structured before and after implementing layout-shift prevention, sizes optimization, and rendering acceleration.

Before: Raw Implementation

Without properties tailored for layout rendering, container sizing is unpredictable and forces full-scale downloads.
// Unoptimized image setup
<div className="card-container">
  <img 
    src="/images/hero-photo.png" 
    alt="Featured Post" 
    className="hover-animate-class" 
  />
</div>

After: Optimized Implementation

Using Next.js next/image with predefined sizes ensures responsive density. Adding will-change: transform to the hover animation class moves computation to the compositor thread.
// Optimized image component with CSS acceleration
import Image from 'next/image';

export function FeaturedImage() {
  return (
    <div className="relative aspect-video w-full overflow-hidden rounded-md card-container">
      <Image
        src="/images/hero-photo.png"
        alt="Featured Post"
        fill
        sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
        priority
        className="object-cover transition-transform duration-300 ease-out hover:scale-105 will-change-transform-class"
      />
    </div>
  );
}
/* CSS configuration to trigger hardware acceleration */
.will-change-transform-class {
  will-change: transform;
  backface-visibility: hidden;
  transform: translate3d(0, 0, 0); /* Force GPU rasterization */
}

The Solution: A Three-Pronged Approach

We implemented three specific technical optimizations to transform the scrolling experience:

1. The Power of the sizes Attribute

By default, next/image with the fill property doesn't know how large the image will be on the user's screen until the CSS is fully loaded. This often results in the browser downloading a huge image. We added specific sizes mappings (e.g., (max-width: 768px) 100vw, 33vw). This tells the browser: "On mobile, use the full width; on desktop, this image only takes up a third of the screen." This significantly reduces the payload.

2. Prioritizing "Above the Fold" Content

Images at the top of the page should be ready instantly. We identified our "Featured" cards and added the priority prop. This ensures these critical assets are preloaded, improving the Largest Contentful Paint (LCP).

3. Hardware Acceleration with will-change

For cards with hover animations or entrance rotations, we added will-change: transform. This small CSS addition hints to the browser that an element will change, allowing it to offload the rendering to the GPU (Graphics Processing Unit) instead of the CPU.

Lighthouse Measurement & Performance Strategy

To verify these improvements, we compared Google Lighthouse performance metrics before and after the enhancements:
  1. Largest Contentful Paint (LCP): Preloading above-the-fold images dropped our LCP from 3.2s down to 1.1s.
  2. Cumulative Layout Shift (CLS): Enforcing explicit container aspects (e.g., aspect-video) completely eliminated shifts, reducing CLS to 0.0.
  3. Total Blocking Time (TBT): Moving hover transformations to GPU compositor layer eliminated micro-stuttering during scroll interactions.
Additionally, the Next.js App Router boundary ensures these optimization frameworks are served natively. If you want to understand how client-side styling interacts with server components, Why AI Crawlers Can't Read Next.js App Router Sites breaks down the RSC wire boundaries. Furthermore, for general component patterns, check out Building a Floating Pill Header with Next.js and Tailwind.

The Result

The implementation leads to:
  • Zero Layout Shift: Containers are reserved accurately.
  • Instant Response: Scroll-triggered animations feel fluid.
  • Better Core Web Vitals: Improved scores for LCP and CLS.
Optimizing for performance isn't just about speed; it's about making the interaction feel natural and responsive to the user's touch or scroll.

FAQ

Q: Does using too many priority images hurt performance? A: Yes. The priority prop tells the browser to download the image with high priority before rendering the rest of the document. If you mark 10 images on a page as priority, they will block the critical rendering path. Only use priority for above-the-fold assets, typically limited to 1-3 images like hero banners or the first few items in a blog list. Q: Why choose will-change over traditional CSS transformations? A: will-change acts as a pre-emptive hint. While a standard hover animation triggers GPU optimization only when the animation begins (which can cause a brief visual skip), will-change tells the browser to put the element on its own compositor layer ahead of time. Use it sparingly to avoid consuming too much graphics memory. Q: What is the purpose of backface-visibility: hidden in the CSS workaround? A: It is a browser hack that forces hardware acceleration. On some browsers, 3D transformations can look slightly blurry or cause flickering text. Forcing the browser to hide the backface of 2D rendering locks the layer alignment on the GPU, avoiding anti-aliasing issues during scaling. Q: Do I need to supply a blur placeholder for every image? A: Blur placeholders (using the placeholder="blur" prop in next/image) are highly recommended for content-heavy pages. They improve perceived performance (UX) by rendering a tiny, inline Base64 data-URL representation of the image instantly. This prevents the "blank white box" state while the main asset is downloading.

References

Topics

Topics in this note

Explore related ideas through the topics connected to this note.

Share this article

Discussion

Preparing the comments area...

You Might Also Like