---
title: "Don't Make This Mistake with Images in Next.js 15"
slug: "don-t-make-this-mistake-with-images-in-next-js-15"
published: "2025-05-26"
updated: "2025-12-21"
categories:
  - "Next.js"
llm-intent: "reference"
framework-versions:
  - "next.js@15"
status: "stable"
llm-purpose: "Learn the critical difference between importing vs referencing images in Next.js 15. Get automatic optimisation, blur effects, and compression."
llm-prereqs:
  - "General familiarity with the article topic"
llm-outputs:
  - "Completed outcome: Learn the critical difference between importing vs referencing images in Next.js 15. Get automatic optimisation, blur effects, and compression."
---

**Summary Triples**
- (static import (local image), enables, Next.js build-time optimizations (blur placeholder, responsive srcSet, compression, inferred width/height, format selection))
- (referencing image via dynamic URL (string src), prevents, build-time image optimizations such as automatic blur, srcSet generation and compression)
- (tsconfig path mappings, helps, simpler imports for local images (example: '@/public/*' -> './public/*'))
- (next/image placeholder='blur', works automatically, when the image is imported statically (not when src is a dynamic/remote URL))
- (priority prop on next/image, use, for above-the-fold images to preload them)
- (remote images (CMS/CDN), require, proper next.config.js image domain/remotePatterns or manual blurDataURL if you need blur/optimizations)

### {GOAL}
Learn the critical difference between importing vs referencing images in Next.js 15. Get automatic optimisation, blur effects, and compression.

### {PREREQS}
- General familiarity with the article topic

### {STEPS}
1. Follow the detailed walkthrough in the article content below.

<!-- llm:goal="Learn the critical difference between importing vs referencing images in Next.js 15. Get automatic optimisation, blur effects, and compression." -->
<!-- llm:prereq="General familiarity with the article topic" -->
<!-- llm:output="Completed outcome: Learn the critical difference between importing vs referencing images in Next.js 15. Get automatic optimisation, blur effects, and compression." -->

# Don't Make This Mistake with Images in Next.js 15
> Learn the critical difference between importing vs referencing images in Next.js 15. Get automatic optimisation, blur effects, and compression.
Matija Žiberna · 2025-05-26

If you're dealing with images in Next.js 15, it's easy to shoot yourself in the foot by misunderstanding how images are handled under the hood. Here's the distinction that matters: **are you importing the image or referencing it via URL**?

I've seen people (myself included) load local images via dynamic URLs thinking Next.js will optimize them. Spoiler: it won't. And that leads to bad performance, no blur effect, no srcset, no compression. The whole point of `next/image` is lost.

## Two ways to use images in Next.js

Let's break it down with real examples.

### 1. Static import (recommended for local images)

First, make sure your `tsconfig.json` has the right path mapping for easier imports:

```json
{
  "compilerOptions": {
    "paths": {
      "@/public/*": ["./public/*"],
      "@/*": ["./src/*"]
    }
  }
}
```

Then import your image:

```tsx
import Image from 'next/image'
import heroImg from '@/public/hero.jpg'

export default function Hero() {
  return (
    <Image
      src={heroImg}
      alt="Hero image"
      placeholder="blur" // This works automatically with static imports
      priority // Use for above-the-fold images
    />
  )
}
```

What you get automatically:
- Blur placeholder handled by Next.js
- Responsive `srcSet` generated at build time
- Compression done during build
- Width and height inferred from the image file
- Optimal format selection (WebP/AVIF when supported)

Perfect for images you already have in your codebase. This is the default recommended way.

### 2. Remote URL (CDN or CMS)

```tsx
import Image from 'next/image'

export default function Gallery({ images }) {
  return (
    <div className="grid grid-cols-3 gap-4">
      {images.map((img) => (
        <Image
          key={img.id}
          src={img.url} // Remote URL from CMS/CDN
          alt={img.alt}
          width={400}
          height={300}
          blurDataURL={img.blurDataURL} // Optional but recommended
          placeholder="blur"
        />
      ))}
    </div>
  )
}
```

For remote images, you need to:
- Specify explicit `width` and `height`
- Configure allowed domains in `next.config.js`
- Optionally provide `blurDataURL` for the blur effect

```js
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  images: {
    remotePatterns: [
      {
        protocol: 'https',
        hostname: 'your-cms.com',
        port: '',
        pathname: '/images/**',
      },
    ],
  },
}

module.exports = nextConfig
```

## The trap: referencing local images via URL

Here's what NOT to do:

```tsx
// ❌ Wrong - treating local image like remote
import Image from 'next/image'

export default function BadExample() {
  return (
    <Image
      src="/gallery/photo.jpg" // Local file referenced by URL
      alt="Photo"
      width={800}
      height={600}
      // This won't get optimized!
    />
  )
}
```

This looks fine, but if that image is just sitting in `/public` and was never imported, you're serving a raw image directly.

What you lose:
- No automatic blur placeholder
- No build-time compression
- No responsive `srcSet` generation
- No format optimization
- Larger bundle size and slower loading

Your Lighthouse score drops. Mobile users suffer.

## When you need blur effects with local URLs

Sometimes you need to reference local images by URL (maybe for dynamic galleries or CMS-like scenarios). In these cases, you can still get blur effects by generating `blurDataURL` yourself:

```tsx
import Image from 'next/image'

const photos = [
  {
    src: "/gallery/photo1.jpg",
    alt: "Beautiful landscape",
    base64: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAK..."
  },
  {
    src: "/gallery/photo2.jpg", 
    alt: "City skyline",
    base64: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAK..."
  }
]

export default function Gallery() {
  return (
    <div className="grid grid-cols-3 gap-4">
      {photos.map((photo, index) => (
        <Image
          key={index}
          src={photo.src}
          alt={photo.alt}
          width={500}
          height={375}
          className="w-full h-full object-cover rounded-lg transition-transform duration-300 hover:scale-[1.02]"
          placeholder="blur"
          blurDataURL={photo.base64}
        />
      ))}
    </div>
  )
}
```

To generate those base64 blur placeholders, use this Sharp script:

```js
const fs = require("fs");
const path = require("path");
const sharp = require("sharp");

async function compressImages(inputDir) {
  try {
    // Ensure the input directory exists
    if (!fs.existsSync(inputDir)) {
      console.error(`Directory ${inputDir} does not exist.`);
      return;
    }

    // Read all files in the directory
    const files = fs.readdirSync(inputDir);

    // Process each image file
    for (const file of files) {
      const inputPath = path.join(inputDir, file);
      const fileExt = path.extname(file);
      const fileName = path.basename(file, fileExt);

      // Skip non-image files
      if (
        ![".jpg", ".jpeg", ".png", ".webp", ".gif"].includes(
          fileExt.toLowerCase()
        )
      ) {
        continue;
      }

      // Compressed image path
      const compressedPath = path.join(inputDir, `${fileName}_tiny${fileExt}`);
      const blurPlaceholderPath = path.join(
        inputDir,
        `${fileName}_tiny.base64`
      );

      // Compress image
      await sharp(inputPath)
        .resize({ width: 800, withoutEnlargement: true }) // Resize to max width of 800px
        .webp({ quality: 80 }) // Convert to WebP with 80% quality
        .toFile(compressedPath);

      // Generate blur placeholder
      const blurBuffer = await sharp(inputPath)
        .resize(10) // Resize to a very small size (e.g., 10px)
        .toFormat("png")
        .toBuffer();

      const blurBase64 = `data:image/png;base64,${blurBuffer.toString(
        "base64"
      )}`;

      // Write blur placeholder to file
      fs.writeFileSync(blurPlaceholderPath, blurBase64);

      console.log(
        `Processed: ${file} -> ${path.basename(
          compressedPath
        )} and ${path.basename(blurPlaceholderPath)}`
      );
    }
  } catch (error) {
    console.error("Error processing images:", error);
  }
}

// Run the script with the gallery directory
compressImages(path.join(__dirname, "..", "public", "gallery"));
```

Run it with:
```bash
npm run compress-images
```

This generates both optimized images and base64 blur placeholders that you can use in your image data.

## Rule of thumb

**If the image is local and known ahead of time — import it.**
**If the image is remote — use the URL directly.**
**Never treat a local image like a remote one.**

## When does manual compression make sense?

The script you shared uses Sharp for manual compression:

```js
const sharp = require("sharp");

async function compressImages(inputDir) {
  // ... compression logic
}
```

This only makes sense for specific edge cases:
- Preparing assets for external CDN deployment
- Processing user uploads server-side
- Custom preprocessing before shipping files elsewhere
- Creating specific sizes for art direction

But for general use with `next/image`, you don't need it. Next.js handles optimization better than manual preprocessing.
## Quick checklist

✅ **Do this:**
- Import local images: `import img from '@/public/img.jpg'`
- Configure remote patterns for external images
- Use `priority` for above-the-fold images
- Let Next.js handle optimization automatically

❌ **Don't do this:**
- Reference local images by URL path
- Manually compress images that Next.js could optimize
- Skip the `alt` attribute
- Forget to configure `remotePatterns` for external images

The `next/image` component is powerful when used correctly. Import local images, configure remote sources properly, and let Next.js handle the heavy lifting. Your users (and Lighthouse scores) will thank you.

## LLM Response Snippet
```json
{
  "goal": "Learn the critical difference between importing vs referencing images in Next.js 15. Get automatic optimisation, blur effects, and compression.",
  "responses": [
    {
      "question": "What does the article \"Don't Make This Mistake with Images in Next.js 15\" cover?",
      "answer": "Learn the critical difference between importing vs referencing images in Next.js 15. Get automatic optimisation, blur effects, and compression."
    }
  ]
}
```