Dynamically Import Images in Astro

Web Reaper avatar

Web Reaper

4 min read

Cover for Dynamically Import Images in Astro

In order to use Astro’s <Image /> component for image optimization, you need to import images in .astro files. But what if all you have is a string path of an image? This is where vite’s import.meta.glob comes in handy. We’ll expore this using two examples.

Note that I got started from this useful recipe in Astro’s docs, but had a slightly more complex example. I’ll go through both a simple example and the exact problem I came across while building the most recent theme - Dawnlight.

INFO

You’re likely to run into it if you use any sort of git-based CMS, such as Keystatic

What is import.meta.glob?

This is a Vite capability to import multiple files using a glob pattern. Basically, we can import all potential images, and use our path as a key to get the image we need.

It will look something like const images = import.meta.glob('./dir/*.jpg')

Starting Code

This is a simple component we can use to demonstrate dynamic image imports.

ImageComponent.astro
---
import { Image } from "astro:assets";
interface Props {
imagePath: string;
description: string;
}
const { imagePath, description } = Astro.props as Props;
// need to turn the imagePath string into an imported image
---
<div>
<!-- this wont' work, as imagePath is not an imported image -->
<Image src={imagePath} alt={description} width={600} />
<p>{description}</p>
</div>

Dyanamically Importing Images - Simple Example

File Structure 1

The first important order of business - what is the path to our images? For the simple example, we’ll say all images go to src/assets/{image}.

src/
└── assets/
β”œβ”€β”€ img1.jpg
β”œβ”€β”€ img2.png
└── img3.jpeg

Glob Pattern 1

For this, we want to import all image related types (jpg, png, jpeg, gif). We can do this with the following glob pattern:

const images = import.meta.glob<{ default: ImageMetadata }>("/src/assets/*.{jpeg,jpg,png,gif}");

INFO

ImageMetadata helps us define the type of the images variable.

Solution 1

Now lets put it all together. We’ll import all images, and then use our imagePath to get the image we need.

ImageComponent.astro
---
import { Image } from "astro:assets";
interface Props {
imagePath: string;
description: string;
}
const { imagePath, description } = Astro.props as Props;
// image path looks like "src/assets/img1.jpg"
const images = import.meta.glob<{ default: ImageMetadata }>("/src/assets/*.{jpeg,jpg,png,gif}");
---
<div>
<Image src={images[imagePath]()} alt={description} width={600} />
<p>{description}</p>
</div>

Production-ready Astro Templates

Astro website templates

Templates with tons of features others leave out. I18n, CMS, animations, image optimization, SEO, and more.

Dyanamically Importing Images - Complex Example

File Structure 2

For the complex example, we’ll solve the exact issue I had. In Keystatic you can set the path to images in the config file, and I like to keep images co-located with the content. This file structure looks like:

src/
└── content/
└── blog/
β”œβ”€β”€ blog-slug-1/
β”‚ β”œβ”€β”€ img1.jpg
β”‚ └── img2.png
└── blog-slug-2/
└── img3.jpeg

So here it’s a little extra challenging, given the dynamic nature of the path. We can’t just import all images in the src/content/blog directory, and instead need to use some fancy glob patterns.

Glob Pattern 2

Let’s get fancy. The ** here in the glob pattern allows us to search recursively. We can use this to search for all images in any subfolder inside the src/content/blog directory.

const images = import.meta.glob<{ default: ImageMetadata }>(
"/src/content/blog/**/*.{jpeg,jpg,png,gif}",
);

Solution 2

Like the previous example, lets put it all together by importing all images, then using our imagePath to get the image we need.

ImageComponent.astro
---
import { Image } from "astro:assets";
interface Props {
imagePath: string;
description: string;
}
const { imagePath, description } = Astro.props as Props;
// image path looks like "src/content/blog/blog-slug-1/img1.jpg"
const images = import.meta.glob<{ default: ImageMetadata }>(
"/src/content/blog/**/*.{jpeg,jpg,png,gif}",
);
---
<div>
<Image src={images[imagePath]()} alt={description} width={600} />
<p>{description}</p>
</div>

Throw an Error Message

It’s a good idea to throw an error message if the image path is not found. This helps you track the error down at the source, rather than receiving some nonsense error message further down the line. So let’s add one!

ImageComponent.astro
---
import { Image } from "astro:assets";
interface Props {
imagePath: string;
description: string;
}
const { imagePath, description } = Astro.props as Props;
const images = import.meta.glob<{ default: ImageMetadata }>(
"/src/content/blog/**/*.{jpeg,jpg,png,gif}",
);
if (!images[imagePath])
throw new Error(
`"${imagePath}" does not exist in glob: "/src/content/blog/**/*.{jpeg,jpg,png,gif}". See ImageComponent.astro to debug.`,
);
---
<div>
<Image src={images[imagePath]()} alt={description} width={600} />
<p>{description}</p>
</div>

Want More Details?

C O S M I C