Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/every-birds-relax.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'astro': patch
---

Fixes images outside the project directory not working when using astro:assets in development mode
19 changes: 11 additions & 8 deletions packages/astro/src/assets/endpoint/dev.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,27 @@
// @ts-expect-error
import { root } from 'astro:config/server';
import { readFile } from 'node:fs/promises';
import os from 'node:os';
import { fileURLToPath } from 'node:url';
import { isParentDirectory } from '@astrojs/internal-helpers/path';
import type { APIRoute } from '../../types/public/common.js';
import { handleImageRequest, loadRemoteImage } from './shared.js';

function replaceFileSystemReferences(src: string) {
return os.platform().includes('win32') ? src.replace(/^\/@fs\//, '') : src.replace(/^\/@fs/, '');
}

async function loadLocalImage(src: string, url: URL) {
// Vite uses /@fs/ to denote filesystem access
// Vite uses /@fs/ to denote filesystem access, we can fetch those files directly
if (src.startsWith('/@fs/')) {
src = replaceFileSystemReferences(src);
if (!isParentDirectory(fileURLToPath(root), src)) {
try {
const res = await fetch(new URL(src, url));

if (!res.ok) {
return undefined;
}

return Buffer.from(await res.arrayBuffer());
} catch {
return undefined;
}
}

// Vite allows loading files directly from the filesystem
// as long as they are inside the project root.
if (isParentDirectory(fileURLToPath(root), src)) {
Copy link
Member Author

@Princesseuh Princesseuh Dec 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure exactly what the code below here is trying to cover, because you cannot pass file paths anyway to getImage / Image so the only way you can hit it is by building a path manually, which won't anyway work in prod (well, it could, but then it wouldn't work in dev). I decided to leave it as-is just in case, but in my mind, we could remove it.

Expand Down
6 changes: 6 additions & 0 deletions packages/astro/test/core-image.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,12 @@ describe('astro:image', () => {
}),
true,
);

// Verify that the images can be fetched successfully
for (const img of $img.toArray()) {
const imgRes = await fixture.fetch(img.attribs['src']);
assert.equal(imgRes.status, 200);
}
});

it('supports inlined imports', async () => {
Expand Down