Skip to content

astro:assets image service returns 500 in dev when @assets points to ext. dir #14957

@alexvas

Description

@alexvas

Astro Info

Astro                    v5.16.5
Node                     v22.21.1
System                   Linux (x64)
Package Manager          yarn
Output                   static
Adapter                  none
Integrations             none

Description

When @assets points to a directory outside the project root (one level up), images imported from it and rendered via astro:assets (either <Image> or getImage) fail in dev with a 500 from the /_image endpoint.

The same images, when used via a plain <img src={asset.src}>, work fine in dev.
Build (astro build) also works: the generated HTML references hashed files under /_astro/... and loads correctly.

So the problem appears to be specific to the dev image service when @assets points to an external directory outside the project root.


Minimal reproduction

  1. Create a fresh Astro project:

    npm create astro@latest mainsite -- --template minimal --install --no-git --skip-houston
  2. Make shared-assets as an external to the project directory:

mkdir shared-assets
cat > shared-assets/logo.svg << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg"
     viewBox="0 0 200 60">
  <rect width="100%" height="100%" fill="none"/>
  <circle cx="30" cy="30" r="20" fill="#1f8ceb"/>
  <path d="M46 22 L62 30 L46 38 Z" fill="#0b6abf" opacity="0.9"/>
  <text x="90" y="36" font-family="sans-serif"
        font-size="20" fill="#111" dominant-baseline="middle">Astro</text>
</svg>

EOF
  1. Create a test PNG file (yellow square 256x256):
base64 -d > shared-assets/square.png << 'EOF'
iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAIAAADTED8xAAAABGdBTUEAALGPC/xhBQAAAAFzUkdC
AdnJLH8AAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAf1JREFU
eNrt0zENAAAIwDAOFCIeO3CjgVbCkmVPBXyVEmAAMAAYAAwABgADgAHAAGAAMAAYAAwABgADgAHA
AGAAMAAYAAwABgADgAHAAGAAMAAYAAwABgADgAHAAGAAMAAYAAwABgADgAHAAGAAMAAYAAwABgAD
gAHAAGAAMAAYAAOAAcAAYAAwABgADAAGAAOAAcAAYAAwABgADAAGAAOAAcAAYAAwABgADAAGAAOA
AcAAYAAwABgADAAGAAOAAcAAYAAwABgADAAGAAOAAcAAYAAwABgADAAGAAOAAcAAYAAMAAYAA4AB
wABgADAAGAAMAAYAA4ABwABgADAAGAAMAAYAA4ABwABgADAAGAAMAAYAA4ABwABgADAAGAAMAAYA
A4ABwABgADAAGAAMAAYAA4ABwABgADAAGAAMAAYAA2AAMAAYAAwABgADgAHAAGAAMAAYAAwABgAD
gAHAAGAAMAAYAAwABgADgAHAAGAAMAAYAAwABgADgAHAAGAAMAAYAAwABgADgAHAAGAAMAAYAAwA
BgADgAHAAGAAMAAYAAyAAcAAYAAwABgADAAGAAOAAcAAYAAwABgADAAGAAOAAcAAYAAwABgADAAG
AAOAAcAAYAAwABgADAAGAAOAAcAAYAAwABgADAAGAAOAAcAAYAAwABgADAAGAAPAteHUBihHS2lg
AAAAAElFTkSuQmCC
EOF
  1. Make sure Vite is allowed to read outside the project root, i.e. in astro.config.mjs:
cat > mainsite/astro.config.mjs << 'EOF'
import { defineConfig } from "astro/config";
import { fileURLToPath } from "node:url";

// Resolve alias paths
const assetsPath = fileURLToPath(new URL("../shared-assets", import.meta.url));

export default defineConfig({
  vite: {
    resolve: {
      alias: {
        "@assets": assetsPath,
      },
    },
    server: {
      fs: {
        allow: ['.', '../..'],
      },
    },
  },
});
EOF
  1. In src/pages/index.astro add an image imported from @assets and rendered via astro:assets:
cat > mainsite/src/pages/index.astro << 'EOF'
---
import { Image, getImage } from "astro:assets";
import logo from "@assets/logo.svg";
import square from "@assets/square.png";
---

<html>
  <body>
    <!-- This SVG goes through /_image -->
    <Image src={logo} width={140} alt="SVG logo via astro:assets" />

    <!-- This SVG uses Vite /@fs and works -->
    <img src={logo.src} width="140" alt="SVG logo via img src" />
    <br/>

    <!-- This PNG goes through /_image -->
    <Image src={square} width={256} alt="PNG square via astro:assets" />

    <!-- This PNG uses Vite /@fs and works -->
    <img src={square.src} width="256" alt="PNG square via img src" />
  </body>
</html>
EOF
  1. Run dev server:
(cd mainsite && clear && npm run dev -- --verbose --host)
  1. Open the page in the browser and check the Network tab.

Expected behavior

  • Every image loads successfully in dev.
  • The <Image> (or getImage) call uses the /_image endpoint without errors, even though @assets reference an external directory.

Actual behavior

When item is logo or square:

  • The <img src={<item>.src}> version works in dev.

  • The <Image src={<item>}> / getImage({ src: <item> }) version fails:

    • The browser requests something like:

      <img src="/_image?href=%2F%40fs%2Ftmp%2Ft2%2Fshared-assets%2Flogo.svg%..."
    • The dev server responds with 500 for that /_image request.

  • No such problem in astro build output: the generated HTML references a static file under /_astro/... and it loads correctly (tested with npm run build && npm run preview -- --host).

So the behavior is inconsistent:

  • dev + external @assets + astro:assets → 500 from /_image
  • dev + external @assets + plain <img src={asset.src}> → OK
  • build + external @assets + astro:assets → OK

Environment

  • Astro: 5.16.5
  • Node: v22.21.1
  • Npm: 10.9.4
  • OS: Linux (also likely reproducible on other OSes as well)

Additional context

  • In my real project, @assets would be a reference to a shared assets folder in the parent workspace (monorepo).
  • Dev server already allows going up via vite.server.fs.allow = ['.', '../..'].
  • The broken dev URL encodes a path under /@fs/tmp/t2/shared-assets/logo.svg into the href query parameter of /_image, which seems to trip whatever path/FS checks the image service uses.
  • Using <img src={asset.src}> everywhere is a viable workaround, but it would be great if astro:assets worked with assets located in an upper-level directory (via alias) in dev the same way it does in build.
  • The issue seems to be strongly related with the astro:assets image service returns 500 in dev when src/assets is a symlink #14937

What's the expected result?

  • Each image loads successfully in dev.
  • The <Image> (or getImage) call uses the /_image endpoint without errors, even though @assets reference an external directory.

N.B. stackblitz does not allow to create parent dirs, so there is a ER app there

Link to Minimal Reproducible Example

https://stackblitz.com/~/github.com/alexvas/astro-issue-14957

yarn install
yarn workspace mainsite dev

Metadata

Metadata

Assignees

Labels

- P3: minor bugAn edge case that only affects very specific usage (priority)feat: assetsRelated to the Assets feature (scope)regression

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions