serveStatic example does not work
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
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: './' }))
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.
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.
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.
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();
}
});
When can this problem be solved?
project-root/
├── src/
│ └── index.js
└── public/
└── 1.png
app.use('/static/*', serveStatic({ root: '../public' })); //didn't work