hono icon indicating copy to clipboard operation
hono copied to clipboard

serveStatic example does not work

Open JakobJingleheimer opened this issue 2 months ago • 6 comments

What version of Hono are you using?

4.9.9

What runtime/platform is your app running on? (with version if possible)

node

What steps can reproduce the bug?

Follow the example in the docs: https://hono.dev/docs/getting-started/nodejs#serve-static-files

What is the expected behavior?

No response

What do you see instead?

404 Not found

Additional information

The problem is caused by how root is computed under the hood. In almost any real-world use, it does not compute in a way that would result in an expected or useful path.

None of the following work:

hono.get('/www/*', serveStatic({
   root: './www'
}));
hono.get('/www/*', serveStatic());

It requires bizarre gymnastics one would find only after a significant amount of trial-and-error (in my case, wasting an hour) to eventually get to:

hono.get('/www/*', serveStatic({
   root: fileURLToPath(import.meta.resolve('./')),
}));
src/
  api/
  www/
  serve.ts

JakobJingleheimer avatar Oct 05 '25 12:10 JakobJingleheimer

Hi @JakobJingleheimer, thank you for your feedback.

Were you unable to achieve the expected results with the settings described in the documentation?

import { serveStatic } from '@hono/node-server/serve-static'

app.use('/static/*', serveStatic({ root: './' }))

usualoma avatar Oct 06 '25 22:10 usualoma

Were you unable to achieve the expected results with the settings described in the documentation?

Correct. I had to use the import.meta.resolve mentioned in the solution of my OP.

JakobJingleheimer avatar Oct 06 '25 23:10 JakobJingleheimer

Thanks for your reply. As documented, specifying the root (or path) is the standard usage.

However, when calling it without arguments, I personally think the default root should be ./.

app.get(‘/static/*’, serveStatic());

Currently, due to my PR, node-server treats an unspecified root as an absolute path. (The adapters for deno and bun use ./.) https://github.com/honojs/node-server/pull/261

There appears to be room for improvement in this area.

usualoma avatar Oct 07 '25 07:10 usualoma

The issue is that ./ (the default) is neither relative to the current file (what most people would expect) nor relative to the path of use//get (but that path DOES affect the composed path, just in an arcane way).

I understand the challenge under the hood to make it relative to the containing file, so I think next best would be to update the docs to not use a relative path (which will almost always result in the wrong result), and instead use path.resolve(__dirname, './') for CJS and fileURLToPath(import.meta.resolve('./')) for ESM.

JakobJingleheimer avatar Oct 07 '25 08:10 JakobJingleheimer

i experienced the same issue. for some reason couldn't get serveStatic to serve files from public directory under /static/* url path. i got this directory structure

.
└── project-root/
    └── apps/
        └── api/
            ├── src/
            │   └── app.ts
            └── public/
                └── styles.css

i use pnpm workspaces and this below setup didn't work:

app.use('/static/*', serveStatic({ root: '../public' })); // './public' didn't work either

so i had to stitch together some hacky solution for now which looks like this:

app.get('/static/*', (c) => {
	const filename = c.req.path.split('/').slice(2).join('/');
	try {
		const content = readFileSync(fileURLToPath(import.meta.resolve(`../public/${filename}`)));
		return c.body(content);
	} catch (error) {
		return c.notFound();
	}
});

outranker avatar Nov 03 '25 08:11 outranker

When can this problem be solved?

 project-root/
            ├── src/
            │   └── index.js
            └── public/
                └── 1.png
app.use('/static/*', serveStatic({ root: '../public' })); //didn't work

snake-zhang avatar Nov 11 '25 09:11 snake-zhang