#Including a fallback image in content.config

4 messages · Page 1 of 1 (latest)

fallow ruin
#

My content collection allows you to include images. I'd like to make them optional and provide a default fallback, but when i try, i get an error when building. However, it does work in dev mode, it only breaks upon building.
Any idea how to make it work without re-architecturing things? I don't want to rethink my organization to make it work.

my content.config.ts, simplified for length:

import { z, defineCollection } from 'astro:content';
import { glob, file } from 'astro/loaders';

// Using @content syntax in the import is also broken
import defaultDemonsCategorySculpture from './content/demons/nexus/default-sculpture.webp'

const demonsCategoriesCollection = defineCollection({
    loader: file("./src/content/demons/categories.yaml"),
    schema: ({ image }) => z.object({
        name: z.string(),
        sculptureSrc: image().optional(),
    }).transform((data) => ({
        ...data,
        sculptureSrc: data.sculptureSrc || defaultDemonsCategorySculpture,
    }))
});

Example data from categories.yaml:

- id: "dark-souls-3"
  name: "Dark Souls III"
  sculptureSrc: "@content/demons/dark-souls-3/sculpture.webp"

How i use the images:

<Image src={category.data.sculptureSrc} alt="" width="256" height="256" loading="eager" />

The error i get:

14:41:16   ▶ /_astro/bg.B11gS7d1_ZkfShr.webp (reused cache entry) (+9ms) (311/654)
node:internal/fs/promises:641
  return new FileHandle(await PromisePrototypeThen(
                        ^

Error: ENOENT: no such file or directory, open '/home/aria/aria-dog/dist/@fs/home/aria/aria-dog/src/content/demons/nexus/default-sculpture.webp'
barren boltBOT
#

Hi @fallow ruin. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave:

#

Astro’s docs show how images in content collections are validated and imported with the image() helper, and that the result can be passed directly to <Image />. They do not document using a statically imported image file (like your defaultDemonsCategorySculpture) as a fallback inside a schema transform, especially when using a file/glob loader. [Images collections]

Because the knowledge sources don’t describe this pattern or how to safely mix image() with a static import in a transform, they also don’t give an officially supported way to fix the ENOENT you’re seeing at build time. I can’t reliably tell from the docs alone whether this is a bug (e.g. path resolution under dist/@fs/...) or simply an unsupported combination of features.

What is documented is the “normal” approach:

  • Put an image path in frontmatter / data (cover, etc.).
  • Validate it with image() in the schema.
  • Use the resulting field directly in your page:
// content.config.ts
import { defineCollection, z } from "astro:content";

const blogCollection = defineCollection({
  schema: ({ image }) =>
    z.object({
      title: z.string(),
      cover: image(),
      coverAlt: z.string(),
    }),
});

export const collections = { blog: blogCollection };
---
// src/pages/blog.astro
import { Image } from "astro:assets";
import { getCollection } from "astro:content";

const allBlogPosts = await getCollection("blog");
---
#

{allBlogPosts.map((post) => (
<div>
<Image src={post.data.cover} alt={post.data.coverAlt} />
<h2>
<a href={"/blog/" + post.slug}>{post.data.title}</a>
</h2>
</div>
))}


The docs don’t show:

- Making that `cover`/`image()` field optional.
- Injecting a default static import as a fallback inside `.transform()`.

So, based solely on the provided docs:

- There is no documented, guaranteed‑working pattern for your “optional image + static default” requirement inside `content.config.ts`.
- The `ENOENT` you see at build time cannot be explained or fixed from the current documentation alone.

Given those limits, the safest *documented* approach is:

- Keep `image()` required in the schema, and
- Always provide a valid image path in your data (i.e. no fallback logic in the schema), or
- Move the fallback logic out of the schema and into your page/component render code (where you check for presence and then use a normal static import), but this pattern is not described in the docs either, so I can’t claim it’s officially supported.

If you need this exact behavior without changing your structure, you’re likely running into an edge case that isn’t covered by the docs, and I can’t point to a canonical fix from the available sources.
----------------------
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
----------------------