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.3
Vite                     v6.4.1
Node                     v22.21.1
System                   Linux (x64)
Package Manager          npm
Output                   static
Adapter                  none
Integrations             none

If this issue only occurs in one browser, which browser is a problem?

No response

Describe the Bug

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 my-app -- --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. Make sure Vite is allowed to read outside the project root, i.e. in astro.config.mjs:
cat > my-app/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 > my-app/src/pages/index.astro << 'EOF'
---
import { Image, getImage } from "astro:assets";
import logo from "@assets/logo.svg";
---

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

    <!-- This one uses Vite /@fs and works -->
    <img src={logo.src} width="140" alt="logo via img src" />
  </body>
</html>
EOF
  1. Run dev server:
(cd my-app && npm run dev -- --host 0.0.0.0)
  1. Open the page in the browser and check the Network tab.

Expected behavior

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

Actual behavior

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

  • The <Image src={logo}> / getImage({ src: logo }) 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 astro build && astro preview).

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.3
  • 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?

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

see
https://stackblitz.com/edit/github-fkzyjbhb?file=src%2Fpages%2Findex.astro

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/edit/github-fkzyjbhb?file=src%2Fpages%2Findex.astro

Participation

  • I am willing to submit a pull request for this issue.

Metadata

Metadata

Assignees

No one assigned

    Labels

    feat: assetsRelated to the Assets feature (scope)in reviewHas been triaged but not yet decided if it is a bug or not.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions