hono icon indicating copy to clipboard operation
hono copied to clipboard

Hono not picking up MIME types correctly (I think)

Open hbjydev opened this issue 1 year ago • 18 comments

What version of Hono are you using?

4.6.12

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

Node 22 LTS

What steps can reproduce the bug?

  • Set up static serving for an SPA (rerouting all requests to index.html)
  • Hit the endpoint and see that the page returns with a Content-Type of text/plain; charset=utf-8, not text/html.
import { Hono } from 'hono';
import { serveStatic } from "@hono/node-server/serve-static";

const app = new Hono();

app.use('/*', serveStatic({ root: './path/to/public', rewriteRequestPath: () => 'index.html' }));

serve({ fetch: app.fetch });

What is the expected behavior?

When I try to make a request to the server on any path (per my example code), I should get the content returned as text/html, not text/plain.

What do you see instead?

The content comes through as text/plain, which prevents it from rendering in the browser correctly.

Additional information

The code I'm running is being executed in a nodejs:22-slim Docker image on Fly.io.

  • Source code for my specific app is here: https://github.com/recipes-blue/recipes.blue
  • Tracking issue in my repo: hbjydev/cookware#1
  • Live version for you to see the problem in action: https://cookware.fly.dev

hbjydev avatar Dec 09 '24 11:12 hbjydev

Hi, I'm currently working on this issue. I could reproduce the wrong mime-type behavior. I will continue to investigate the cause.

sushichan044 avatar Dec 09 '24 17:12 sushichan044

Hi, I'm currently working on this issue. I could reproduce the wrong mime-type behavior. I will continue to investigate the cause.

Awesome, thanks! :)

hbjydev avatar Dec 09 '24 23:12 hbjydev

@sushichan044 Thanks! Please do it.

yusukebe avatar Dec 10 '24 03:12 yusukebe

Hello, any workaround while waiting for a fix ? I have to deploy the app 😅

For now I've done that but if you reload the page on a different route it doesn't work

import { serveStatic } from "hono/serve-static";

app.use(
  "*",
  serveStatic({
    root: "./dist",
    getContent: async (path, c) => {
      try {
        const data = await fs.readFile(path);
        let contentType = "text/plain";

        if (path.endsWith(".html")) {
          contentType = "text/html";
        } else if (path.endsWith(".js")) {
          contentType = "application/javascript";
        } else if (path.endsWith(".css")) {
          contentType = "text/css";
        } else if (path.endsWith(".json")) {
          contentType = "application/json";
        } else if (path.endsWith(".png")) {
          contentType = "image/png";
        } else if (path.endsWith(".jpg") || path.endsWith(".jpeg")) {
          contentType = "image/jpeg";
        }

        return new Response(data, {
          headers: {
            "Content-Type": contentType,
          },
        });
      } catch (error) {
        return null;
      }
    },
  })
);

MGMehdi avatar Dec 19 '24 14:12 MGMehdi

This happens with different combinations of middleware that modify the c.header, and the investigation takes a long time. Please give me some more time.

sushichan044 avatar Dec 19 '24 16:12 sushichan044

@sushichan044 Any progress?

yusukebe avatar Apr 13 '25 03:04 yusukebe

@yusukebe Sorry, I had investigated this before but got busy and had to suspend it. I almost know the cause and will report back in a few days.

sushichan044 avatar Apr 14 '25 13:04 sushichan044

@sushichan044 Okay! Thanks!

yusukebe avatar Apr 14 '25 23:04 yusukebe

I would like to add that .webmanifest should have application/manifest+json as content type but currently return as application/octet-stream.

sep2 avatar Apr 16 '25 09:04 sep2

@sep2 The MIME type handling in the webmanifest is a different cause than this issue, but it should be a bug. I'll create a PR to resolve it.

sushichan044 avatar Apr 16 '25 09:04 sushichan044

@yusukebe

I have created a repository contains a minimal reproduction code and a detailed explanation of the cause. https://github.com/sushichan044/hono-header-handling

The explanation may be complicated, but would you please read it in your spare time?

sushichan044 avatar Apr 18 '25 06:04 sushichan044

I think there are two ways to fix this.

First, special handling of content-type in this branch would be an ad hoc approach. https://github.com/honojs/hono/blob/5ca6c6ef867e022671b4c429c04d0ff89ed0c37c/src/context.ts#L670-L676

Specifically, if Context.#headers already contains content-type, we do not overwrite the header.

This prevents overwriting the expected header content by the content-type: text/plain implicit in the 404 response when Context.#res is initialized.

Since only the content-type header may be implicitly included in Context.#res.headers, and the rest should be explicitly written from the Middleware or application code, this handling should not be a problem.


However, in edge cases, implementations that override the content-type set in c.header with c.res.headers.set will not work as expected.

sushichan044 avatar Apr 18 '25 06:04 sushichan044

Another approach would be to change the way response headers are stored to make the structure less prone to unexpected overwrites.

However, I have no specific idea yet. I think the scope of impact is also larger than the first way.

To simplify the implementation, it is possible to stop using preparedHeaders, but this may compromise performance.

sushichan044 avatar Apr 18 '25 07:04 sushichan044

In addition, I will document the differences between the two methods of editing headers, c.header and c.res.headers.

Or perhaps we should create a best practice guide when implementing Middlewares 🤔

sushichan044 avatar Apr 18 '25 07:04 sushichan044

@sushichan044

I have created a repository contains a minimal reproduction code and a detailed explanation of the cause. https://github.com/sushichan044/hono-header-handling

The explanation may be complicated, but would you please read it in your spare time?

Ops. This is a bug! I'll take a look. Thank you so much!

yusukebe avatar Apr 21 '25 08:04 yusukebe

Maybe this is a super minimal reproduce code:

import { Hono } from 'hono'

const app = new Hono()

app.use(async (c, next) => {
  c.header('foo', 'bar', { append: true })
  c.res // call Context.res()
  await next()
})

app.get('/', (c) => {
  c.header('Content-Type', 'text/html; charset=UTF-8')
  return c.body('<html><body><h1>Hello World</h1></body></html>')
})

const res = await app.request('/')
console.log(res.headers.get('content-type')) // ❌️ text/plain

yusukebe avatar Apr 21 '25 08:04 yusukebe

Current workaround by adding a middleware as follow

.use('*',
        async (c, next) => {
            await next()
            const path = c.req.path
            if (!path.includes('.') && !path.startsWith(`/assets`)) {
                c.header('Content-Type', 'text/html')
            }
        },

        serveStatic({
            root: './web/dist',
            rewriteRequestPath: (path) => {
                if (!path.includes('.') && !path.startsWith(`/assets`)) {
                    return '/index.html'
                }
                return path
            }
        }))

jcyh0120 avatar May 14 '25 03:05 jcyh0120

Oh, I've found the same issue, the problem is

I've used, it will serve correctly,

import { serveStatic } from "hono/bun";

// Serve static files from the frontend build directory
app.get("*", serveStatic({ root: FRONTEND_PATH }));
// Fallback route to serve the index.html for SPA routing
app.get("*", serveStatic({ root: FRONTEND_PATH, path: "index.html" }));

after i've make it like on /app, all mime type are override to be Content-Type: text/html

import { serveStatic } from "hono/bun";

// Serve static files from the frontend build directory
app.get("/app/*", serveStatic({ root: FRONTEND_PATH }));
// Fallback route to serve the index.html for SPA routing
app.get("/app/*", serveStatic({ root: FRONTEND_PATH, path: "index.html" }));

Note: hono 4.7.11

mildronize avatar Jun 15 '25 09:06 mildronize

I've never investigated well, but these problems may be fixed with the latest version 4.8.0. Can you try it?

yusukebe avatar Jun 19 '25 08:06 yusukebe

Still an Issue as of version 4.8.3

oscilloscope-rv avatar Jun 29 '25 22:06 oscilloscope-rv

I tried the cases that were commented on this issue:

  • https://github.com/honojs/hono/issues/3736#issue-2726757587
  • https://github.com/honojs/hono/issues/3736#issuecomment-2814695601
  • https://github.com/honojs/hono/issues/3736#issuecomment-2817946197
  • https://github.com/honojs/hono/issues/3736#issuecomment-2973609493

I can confirm these issues are fixed with the latest 4.8.3. I'd like to close this issue now. If you encounter any problems, please create a new issue with instructions for reproduction. We can work on fixing it.

Thanks.

yusukebe avatar Jul 02 '25 09:07 yusukebe