Astro shows “Local images must be imported” when you pass a file path string, like "../assets/hero.jpg" or "/src/assets/hero.jpg", as the src of <Image /> or getImage(). The message explains it: the src parameter “must be an imported image or a URL, it cannot be a string filepath.” Import the image instead, so Astro can read its size and format and optimise it at build time.
Key Takeaways
- Import local images:
import hero from '../assets/hero.jpg', thensrc={hero}.- In content collections, use the
image()schema helper so frontmatter paths become imports.- For a dynamic set of images, use
import.meta.glob()and pick the one you need.- Images in
public/are served by path, but they aren’t optimised.
Fix 1: import the file
---
import { Image } from 'astro:assets';
import hero from '../assets/hero.jpg';
---
<Image src={hero} alt="Product dashboard" />
The import returns an object with src, width, height and format, which is why no dimensions are needed.
Fix 2: images referenced in frontmatter
A path in Markdown frontmatter is just a string until the schema says otherwise. Use image():
// src/content.config.ts
const blog = defineCollection({
loader: glob({ pattern: '**/*.md', base: './src/content/blog' }),
schema: ({ image }) => z.object({
title: z.string(),
cover: image(),
coverAlt: z.string(),
}),
});
# src/content/blog/launch.md
cover: ./launch-cover.jpg
<Image src={post.data.cover} alt={post.data.coverAlt} />
The path is resolved relative to the Markdown file, and a missing file fails the build. The content collections guide covers schemas in full.
Fix 3: choosing an image by name at runtime
When the file name comes from data (a team member’s photo, say), static imports don’t work. Glob the folder once and look it up:
---
import { Image } from 'astro:assets';
import type { ImageMetadata } from 'astro';
const photos = import.meta.glob<{ default: ImageMetadata }>('../assets/team/*.{jpg,png}');
const { photo } = Astro.props; // e.g. "anika.jpg"
const loader = photos[`../assets/team/${photo}`];
if (!loader) throw new Error(`Missing team photo: ${photo}`);
const image = (await loader()).default;
---
<Image src={image} alt={`Photo of ${photo.replace(/\.\w+$/, '')}`} />
The glob pattern must be a literal string, so Vite can find the files at build time.
Images in public/
public/ files can be used with a path (src="/images/logo.png"), but Astro treats them like remote images: no optimisation, and you must pass width and height (see Missing image dimensions). Move images you want optimised to src/assets/.
The Astro performance guide explains what optimisation gives you, and responsive images in Astro covers srcset. See the astro:assets src reference for every accepted value.
FAQ
Can I use a variable path with Image in Astro?
Not as a string. Use import.meta.glob() to import a set of images, then select one by key.
Do images in Markdown body text need importing?
No. Relative Markdown images like  are resolved and optimised automatically.