Skip to content

Custom 500.astro isn't rendered when middleware throws on the composable astro/hono path #17092

Description

@iseraph-dev

Astro Info

Astro                    v7.0.0-beta.4
Node                     v24.16.0
System                   Linux (x64)
Output                   server
Adapter                  @astrojs/node

(Built from main at 4c4a91c3, so this includes #17041. The version still prints 7.0.0-beta.4 because that's the last release.)

Describe the Bug

Follow-up to #17041 (now merged), where I flagged this as out of scope.

On the composable path, if you wire the pipeline up yourself with app.use(middleware()) and app.use(pages()), an error thrown from your own src/middleware.ts never becomes 500.astro. It falls through to Hono, which answers with its default plain-text Internal Server Error.

The all-in-one astro() handler doesn't have this problem (the same throw renders 500.astro there), so it's specific to wiring middleware() up on its own. With #17041 in place you can watch both sides on one server: a page that throws during render now gets 500.astro, but a middleware throw still falls through to Hono.

As far as I can tell it's the same shape as #16952. AstroHandler.render() wraps the whole middleware and page chain in a try/catch and renders 500.astro when something throws, but the composable middleware() just hands off to AstroMiddleware.handle() / callMiddleware() with nothing around it. The fallback from #17041 only sits on the pages() side.

What's the expected result?

A throw in src/middleware.ts should render 500.astro, the same way astro() and the normal adapter path already do.

Link to Minimal Reproducible Example

https://github.com/iseraph-dev/astro-hono-middleware-500

/boom throws in middleware and /page-throws throws during render. Build it, run node ./dist/server/entry.mjs, then hit both: /page-throws gives you 500.astro, /boom gives you Hono's plain text.

Possible fix

A try/catch straight inside middleware() doesn't really work, since its next is the host's next and it would also catch errors from Hono middleware further down the chain, hiding them from the user's own app.onError. Something opt-in exported from astro/hono would fit better, maybe an onError() you hand to Hono (app.onError(onError())), so Astro's error rendering plugs into Hono's error hook only when you want it. Happy to open a PR if that sounds right.

Participation

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    - P3: minor bugAn edge case that only affects very specific usage (priority)pkg: astroRelated to the core `astro` package (scope)triage: fix pendingReporter needs to verify the triage bot fix works

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions