Next.js Image Optimization with ImageKit

Learn how to go beyond anilla Next.js images with ImageKit for automatic AVIF delivery, smart cropping, blur-up placeholders, and AI-powered transformations.

The default image component of Next.js, next/image, handles the basics of image optimization well: lazy loading, responsive srcset generation, WebP conversion, and compression. But it offers limited options for more advanced requirements, such as automatic AVIF format conversion, limited support for optimizing animated images, and other advanced media transformations and GenAI, that can help us deliver exceptional image experiences on the web.

There’s also a practical consideration: when deployed on Vercel, image optimization is billed by usage. As your traffic grows, the cost of on-the-fly transformations and bandwidth can increase significantly.

That’s where ImageKit comes in.

In this guide, we’ll jump straight into using the ImageKit Image component, which is built on top of next/image, to enhance image optimization in a Next.js app. We’ll cover both the basics and advanced optimizations & transformations to help you create faster, smarter, and more visually polished experiences.

What we'll cover:

  1. Setting up the ImageKit SDK
  2. Automatic WebP and AVIF delivery and compression.
  3. Responsive images.
  4. Lazy loading with a blur-up placeholder.
  5. Smart cropping images with fo-face and fo-auto.
  6. AI transformations: background removal and extending an image with generative fill.

The complete working code is in the (demo-repo)

Setting up the ImageKit SDK

Install the package:

npm install @imagekit/next

Wrap your root layout in the Providers component:

// app/layout.tsx
import { ImageKitProvider } from '@imagekit/next';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html>
      <body>
        <ImageKitProvider urlEndpoint={process.env.NEXT_PUBLIC_IMAGEKIT_URL_ENDPOINT!}>
          {children}
        </ImageKitProvider>
      </body>
    </html>
  );
}

Add your URL endpoint to .env.local:

NEXT_PUBLIC_IMAGEKIT_URL_ENDPOINT=https://ik.imagekit.io/your_imagekit_id

Swap the Image import:

// Before
import Image from 'next/image';

// After
import { Image } from '@imagekit/next';

That's the entire migration from next/image to ImageKit to set up for your app for better optimizations. The ImageKit Image component is a wrapper around next/image, so every prop you already use ;width, height, fill, sizes, priority, alt works exactly the same. ImageKit adds URL generation, transformation, and CDN delivery on top.

Serve images in modern format like AVIF using automatic format conversion

The moment you switch to the SDK, ImageKit reads the browser's Accept header on every request and delivers the most efficient format that browser supports:

  • Chrome, Firefox, Edge: AVIF first, then WebP
  • Safari: WebP, then JPEG/PNG

No code change needed. No config changes to get AVIF output.

AVIF in ImageKit is enabled per account. Check your ImageKit dashboard under Settings > Images > Optimization > Automatic format conversion to confirm automatic AVIF optimization is available on your account.

To verify automatic format conversion is working: open DevTools, go to the Network tab, click your image request, and check the Content-Type response header. In Chrome, ideally, you'll see image/avif even though the URL itself ends in .jpg. And when you move to Safari, you might see image/webp .

AVIF compresses ~20% smaller than WebP at equivalent visual quality. For a product page with ten images, that savings arrives with zero code change.

Efficiently encode images using automatic compression

ImageKit compresses images on the way out. The default quality is 80 and is configurable globally in your dashboard under Settings > Image > Default quality. This is on a scale of 1-100 and balances visual quality and compression well.

You don't need to set anything to get the benefit of this compression. The original JPEG delivered through ImageKit at default settings of format optimization and compression is already significantly smaller.

MetricRaw sourceImageKit
File size
Format (Chrome)image/jpegimage/avif

Both format conversion and compression happen on first request and are cached at the CDN edge. Subsequent requests are instant.

You can also override the quality on a per-image basis when you need finer control. This is especially useful for scenarios like low-quality image placeholders (LQIP), increasing compression for users on slower networks, or boosting visual quality for high-detail assets such as product images.

// Higher quality for product images
<Image
  src="/product-image.jpg"
  alt="Product Image"
  width={1280}
  height={720}
  transformation={[{ quality: 90 }]}
/>

// Lower quality for more compression
<Image
  src="/thumbnail.jpg"
  alt="Product thumbnail"
  width={300}
  height={300}
  transformation={[{ quality: 60 }]}
/>

Proper image resizing using Responsive images

Serving a single image width to all devices wastes bandwidth on mobile and looks soft on retina displays. The sizes prop tells the browser which width to request at each breakpoint.

Because the ImageKit SDK wraps next/image, the sizes prop and srcset generation work exactly as you'd expect — no changes needed:

<Image
  src="/hero-banner.jpg"
  alt="Mountain landscape"
  fill
  sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
  transformation={[{ quality: 75 }]}
/>

next/image generates a srcset with multiple widths. The browser picks the closest match at each breakpoint and requests that size. ImageKit takes care of creating and delivering resized, compressed, correctly formatted images from the CDN.

For a product grid:

<Image
  src={`/products/${product.slug}.jpg`}
  alt={product.name}
  width={400}
  height={400}
  sizes="(max-width: 480px) 100vw, (max-width: 1024px) 50vw, 33vw"
  transformation={[{ quality: 75 }]}
/>

Always set sizes for images that don't span the full screen. Without it, the browser assumes the image fills 100vw and may request a file far larger than what you actually render. transformation applies quality and other non-resize transforms to every variant.

Lazy loading images

Lazy loading is on by default even in the next/image component. The ImageKit Image component inherits this directly from next/image. Images below the fold don't load until they scroll into view, with no extra configuration.

For above-the-fold images that should load immediately, use priority attribute.

<Image
  src="/hero.jpg"
  alt="Hero image"
  width={1280}
  height={720}
  priority
/>

Preloading LCP image

The priority prop does two things: it disables lazy loading and adds a <link rel="preload"> tag in the HTML <head>. This tells the browser to start fetching the image before it even parses the rest of the page.

Use priority on the image that's most likely to be your Largest Contentful Paint element. This is typically the hero image or the above-the-fold banner.

<Image
  src="/docs_images/examples/example_food_3.jpg"
  alt="Hero banner"
  width={1280}
  height={720}
  priority
  sizes="100vw"
/>

Only one or two images per page should have priority. Adding it to every image defeats the purpose by competing for bandwidth. For everything below the fold, let the default lazy loading do its job.

Improving lazy loading with blur-up placeholder

A blank space while an image loads is jarring, especially above the fold. You can show a blurred low-quality version of the image while the full one loads.

Use the buildSrc attribute to generate the placeholder URL and apply it as a CSS background image via the style prop. Once the full image loads, remove the background.

'use client';

import { Image, buildSrc } from '@imagekit/next';
import { useState, useCallback } from 'react';

const urlEndpoint = process.env.NEXT_PUBLIC_IMAGEKIT_URL_ENDPOINT!;

const placeholderSrc = buildSrc({
  urlEndpoint,
  src: '/products/backpack.jpg',
  transformation: [{ quality: 10, blur: 90 }],
});

export default function ProductImage() {
  const [showPlaceholder, setShowPlaceholder] = useState(true);

  // Handles cached images where onLoad won't fire
  const imgRef = useCallback((img: HTMLImageElement | null) => {
    if (!img) return;
    if (img.complete) setShowPlaceholder(false);
  }, []);

  return (
    <Image
      src="/products/backpack.jpg"
      alt="Hiking backpack"
      width={800}
      height={600}
      ref={imgRef}
      onLoad={() => setShowPlaceholder(false)}
      style={
        showPlaceholder
          ? {
              backgroundImage: `url(${placeholderSrc})`,
              backgroundSize: 'cover',
              backgroundRepeat: 'no-repeat',
            }
          : {}
      }
    />
  );
}

The buildSrc call generates a blurred, low-quality image using ImageKit’s URL transformations with quality: 10 and blur: 90. It loads almost instantly and fills the space while the full image arrives. Once the full image loads, onLoad fires, and the background is cleared.

The imgRef callback handles cached images where onLoad won't fire: if img.complete is already true on mount, the placeholder is removed immediately.

Verifying image optimization using Lighthouse

With AVIF format conversion, compression, responsive images, and lazy loading in place, we have covered all the basics of optimizing images in a Next.js app. We can use Lighthouse to test our website again, and these are the results:

Advanced image optimizations and transformations

When building an image-heavy Next.js application, basic optimizations like resizing and format conversion are just the starting point. In many cases, you’ll need more advanced transformations to truly elevate the visual experience for your users.

That’s where ImageKit stands out. Beyond being more cost-efficient, it offers 50+ real-time transformations that go far beyond standard optimization.

Let’s explore a few commonly used transformations for real-world use cases.

Smart crop for images

Standard cropping cuts from the edges inward. A landscape photo cropped to a square gives you the center third. If faces or the main subject sit off-center, they get cut.

ImageKit's focus parameter detects the subject and centers the crop around it.

Face crop (fo-face)

ImageKit detects faces in the frame and produces a square centered on them, regardless of where they appear in the original.

<Image
  src="/team/group-photo.jpg"
  alt="Team photo"
  width={400}
  height={400}
  transformation={[{
    width: 400,
    height: 400,
    focus: 'face',
  }]}
/>

General smart crop (fo-auto)

For product images or any content with or without faces, fo-auto detects the main subject:

<Image
  src="/products/sneaker.jpg"
  alt="Running shoe"
  width={600}
  height={600}
  transformation={[{
    width: 600,
    height: 600,
    focus: 'auto',
  }]}
/>

Useful for catalog pages where images arrive in different aspect ratios and need to be normalized to a grid keeping the product in the center.

AI transformations

ImageKit provides AI-powered transformations as URL parameters. Two that are particularly useful for product imagery are background removal and extending the image background using generative fill.

Background removal

<Image
  src="/products/headphones.jpg"
  alt="Wireless headphones"
  width={500}
  height={500}
  transformation={[{
    width: 500,
    raw: 'e-bgremove',
  }]}
/>

e-bgremove strips the background and outputs a transparent PNG. For higher accuracy, you can use e-removedotbg.

Background generative fill

<Image
  src="/docs_images/examples/example_food_3.jpg"
  alt="Product image - mobile hero"
  width={1080}
  height={1920}
  transformation={[{
    width: 1080,
    height: 1920,
    cropMode: 'pad_resize',
    raw: 'bg-genfill',
  }]}
/>

Use of this transform is to expand the image to fit different aspect ratios, like you traditionally have a square image but now want to fit 9:16 aspect ratio of mobile, you can use genfill to pad the image to this new aspect ratio.

You can optionally guide the generation with a text prompt:

transformation={[{
  width: 1080,
  height: 1920,
  cropMode: 'pad_resize',
  raw: 'bg-genfill-prompt-clean white studio background',
}]}

These AI transforms are in beta and take longer to process than basic transforms. Generate them before going live rather than on first request. See the AI transformation docs for the full list.

Conclusion

Switching from next/image to the ImageKit SDK with its Image Component is a one-line import change and a provider wrapper. From that point, every image gets AVIF/WebP delivery, automatic compression, responsive srcset generation, lazy loading, and access to the full transformation API without changing how you write your components.

Sign up for a free ImageKit account and try out some advanced transformations like the Face crop fo-face on a group photo.