Fix: Local Images Must Be Imported in Astro

Error message:
Local images must be imported.
Images & Assets 2025-01-25

What Causes This Error?

This error occurs when you try to use a local image with a string path instead of importing it. Astro requires local images to be imported so it can optimize them at build time.

The Problem

---
import { Image } from 'astro:assets';
---

<!-- ❌ String path for local image -->
<Image src="../assets/photo.jpg" alt="Photo" />
<Image src="./images/hero.jpg" alt="Hero" />

The Fix

Import the Image

---
import { Image } from 'astro:assets';
// ✅ Import the image
import photo from '../assets/photo.jpg';
import hero from './images/hero.jpg';
---

<Image src={photo} alt="Photo" />
<Image src={hero} alt="Hero" />

Common Scenarios

Multiple Images

---
import { Image } from 'astro:assets';
import image1 from '../assets/image1.jpg';
import image2 from '../assets/image2.jpg';
import image3 from '../assets/image3.jpg';
---

<Image src={image1} alt="Image 1" />
<Image src={image2} alt="Image 2" />
<Image src={image3} alt="Image 3" />

Dynamic Imports with Glob

---
import { Image } from 'astro:assets';

// ✅ Use import.meta.glob for dynamic paths
const images = import.meta.glob<{ default: ImageMetadata }>(
  '../assets/gallery/*.{jpg,png,webp}',
  { eager: true }
);

const gallery = Object.values(images).map((mod) => mod.default);
---

{gallery.map((image, index) => (
  <Image src={image} alt={`Gallery image ${index + 1}`} />
))}

Content Collections

// src/content/config.ts
import { defineCollection, z } from 'astro:content';

const blog = defineCollection({
  type: 'content',
  schema: ({ image }) => z.object({
    title: z.string(),
    // ✅ Use image() helper for local images
    coverImage: image(),
  }),
});
---
title: My Post
coverImage: ./cover.jpg
---
---
import { Image } from 'astro:assets';
const { post } = Astro.props;
---

<!-- ✅ Content collection images are pre-imported -->
<Image src={post.data.coverImage} alt={post.data.title} />

Public Directory Images

---
// Images in public/ can use string paths
// But they won't be optimized
---

<!-- ✅ Public images use / prefix -->
<img src="/images/logo.png" alt="Logo" />

<!-- For optimization, import from src/assets instead -->

Conditional Images

---
import { Image } from 'astro:assets';
import defaultImage from '../assets/default.jpg';

// ✅ Import all possible images
import heroLight from '../assets/hero-light.jpg';
import heroDark from '../assets/hero-dark.jpg';

const isDark = true;
const heroImage = isDark ? heroDark : heroLight;
---

<Image src={heroImage} alt="Hero" />

Props with Images

---
// Card.astro
import { Image } from 'astro:assets';
import type { ImageMetadata } from 'astro';

interface Props {
  image: ImageMetadata;
  title: string;
}

const { image, title } = Astro.props;
---

<div class="card">
  <Image src={image} alt={title} />
  <h2>{title}</h2>
</div>
---
// Usage
import Card from './Card.astro';
import productImage from '../assets/product.jpg';
---

<Card image={productImage} title="Product Name" />

Why Imports Are Required

✅ Imported images:
- Get optimized (resized, compressed)
- Get content hashes for caching
- Are validated at build time
- Work with Image component features

❌ String paths:
- Skip optimization
- No build-time validation
- Miss out on automatic formats
- Can break in production

Quick Checklist

  • Use import statements for local images
  • Use import.meta.glob for dynamic images
  • Content collections use image() schema helper
  • Public folder images use / paths (unoptimized)
  • Pass imported images as props, not paths