Release Notes
Hono v4.12.0 is now available!
This release includes new features for the Hono client, middleware improvements, adapter enhancements, and significant performance improvements to the router and context.
$path for Hono Client
The Hono client now has a $path() method that returns the path string instead of a full URL. This is useful when you need just the path portion for routing or key-based operations:
const client = hc<typeof app>('http://localhost:8787')
// Get the path string
const path = client.api.posts.$path()
// => '/api/posts'
// With path parameters
const postPath = client.api.posts[':id'].$path({
param: { id: '123' },
})
// => '/api/posts/123'
// With query parameters
const searchPath = client.api.posts.$path({
query: { filter: 'test' },
})
// => '/api/posts?filter=test'Unlike $url() which returns a URL object, $path() returns a plain path string, making it convenient for use with routers or as cache keys.
Thanks @ShaMan123!
ApplyGlobalResponse Type Helper for RPC Client
The new ApplyGlobalResponse type helper allows you to add global error response types to all routes in the RPC client. This is useful for typing common error responses from app.onError() or global middlewares:
const app = new Hono()
.get('/api/users', (c) => c.json({ users: ['alice', 'bob'] }, 200))
.onError((err, c) => c.json({ error: err.message }, 500))
type AppWithErrors = ApplyGlobalResponse<
typeof app,
{
401: { json: { error: string; message: string } }
500: { json: { error: string; message: string } }
}
>
const client = hc<AppWithErrors>('http://api.example.com')
// Now client knows about both success and error responses
const res = await client.api.users.$get()
// InferResponseType includes { users: string[] } | { error: string; message: string }Thanks @mohankumarelec!
SSG Redirect Plugin
A new redirectPlugin for SSG generates static HTML redirect pages for HTTP redirect responses (301, 302, 303, 307, 308):
import { toSSG } from 'hono/ssg'
import { defaultPlugin, redirectPlugin } from 'hono/ssg'
const app = new Hono()
app.get('/old', (c) => c.redirect('/new'))
app.get('/new', (c) => c.html('New Page'))
// redirectPlugin must be placed before defaultPlugin
await toSSG(app, fs, {
plugins: [redirectPlugin(), defaultPlugin()],
})The generated redirect pages include a <meta http-equiv="refresh"> tag, a canonical link, and a robots noindex meta tag.
Thanks @3w36zj6!
onAuthSuccess Callback for Basic Auth
The Basic Auth middleware now supports an onAuthSuccess callback that is invoked after successful authentication. This allows you to set context variables or perform logging without re-parsing the Authorization header:
app.use(
'/auth/*',
basicAuth({
username: 'hono',
password: 'ahotproject',
onAuthSuccess: (c, username) => {
c.set('user', { name: username, role: 'admin' })
console.log(`User ${username} authenticated`)
},
})
)The callback also works with async functions and the verifyUser mode.
Thanks @AprilNEA!
getConnInfo for AWS Lambda, Cloudflare Pages, and Netlify
getConnInfo() is now available for three additional adapters:
// AWS Lambda (supports API Gateway v1, v2, and ALB)
import { handle, getConnInfo } from 'hono/aws-lambda'
// Cloudflare Pages
import { handle, getConnInfo } from 'hono/cloudflare-pages'
// Netlify
import { handle, getConnInfo } from 'hono/netlify'
app.get('/', (c) => {
const info = getConnInfo(c)
return c.text(`Your IP: ${info.remote.address}`)
})Thanks @rokasta12!
alwaysRedirect Option for Trailing Slash Middleware
The trailing slash middleware now supports an alwaysRedirect option. When enabled, the middleware redirects before executing handlers, which fixes the issue where trailing slash handling doesn't work with wildcard routes:
app.use(trimTrailingSlash({ alwaysRedirect: true }))
app.get('/my-path/*', async (c) => {
return c.text('wildcard')
})
// /my-path/something/ will be redirected to /my-path/something
// before the wildcard handler is executedProgressive Locale Code Truncation
The normalizeLanguage function in the language middleware now supports RFC 4647 Lookup-based progressive truncation. Locale codes like ja-JP will match ja when only the base language is in supportedLanguages:
app.use(
'/*',
languageDetector({
supportedLanguages: ['en', 'ja'],
fallbackLanguage: 'en',
order: ['cookie', 'header'],
})
)
// Accept-Language: ja-JP → matches 'ja'
// Accept-Language: ko-KR → falls back to 'en'Thanks @sorafujitani!
exports Field for ExecutionContext
The ExecutionContext type now includes an exports property for Cloudflare Workers. You can use module augmentation to type it with Wrangler's generated types:
import 'hono'
declare module 'hono' {
interface ExecutionContext {
readonly exports: Cloudflare.Exports
}
}Thanks @toreis-up!
Performance Improvements
TrieRouter 1.5x ~ 2.0x Faster
The TrieRouter has been significantly optimized with reduced spread syntax usage, O(1) hasChildren checks, lazy regular expression generation, and removal of redundant processes:
| Route | Node.js | Deno | Bun |
|---|---|---|---|
short static GET /user
| 1.70x | 1.40x | 1.34x |
dynamic GET /user/lookup/username/hey
| 1.38x | 1.69x | 1.51x |
wildcard GET /static/index.html
| 1.51x | 1.72x | 1.43x |
| all together | 1.58x | 1.60x | 1.82x |
Thanks @EdamAme-x!
Fast Path for c.json()
c.json() now has the same fast path optimization as c.text(). When no custom status, headers, or finalized state exists, the Response is created directly without allocating a Headers object:
// This common pattern is now faster
return c.json({ message: 'Hello' })Benchmark results:
| Metric | Before | After | Change |
|---|---|---|---|
| Reqs/sec | 92,268 | 95,244 | +3.2% |
| Latency | 5.42ms | 5.25ms | -3.1% |
| Throughput | 17.24MB/s | 19.07MB/s | +10.6% |
Thanks @mgcrea!
New features
- feat(client): Add
ApplyGlobalResponsetype helper for RPC Client #4556 - feat(ssg): add redirect plugin #4599
- feat(client): $path #4636
- feat(basic-auth): add context key and callback options #4645
- feat(adapter): add getConnInfo for AWS Lambda, Cloudflare Pages, and Netlify #4649
- feat(trailing-slash): add
alwaysRedirectoption to support wildcard routes #4658 - feat(language): add progressive locale code truncation to normalizeLanguage #4717
- feat(types): Add exports field to ExecutionContext #4719
Performance
- perf(context): add fast path to c.json() matching c.text() optimization #4707
- perf(trie-router): improve performance (1.5x ~ 2.0x) #4724
- perf(context): use
createResponseInstancefor new Response #4733
All changes
- fix(jsx/dom): handle empty arrays in render children loop by @mixelburg in #4729
- perf(jsx/dom): flatten children once at the start to avoid repeated flattening by @usualoma in #4730
- fix(client): skip undefined values in form data serialization by @aidenlx in #4732
- feat(client): Add
ApplyGlobalResponsetype helper for RPC Client by @mohankumarelec in #4556 - feat(ssg): add redirect plugin by @3w36zj6 in #4599
- feat(client): $path by @ShaMan123 in #4636
- feat(basic-auth): add context key and callback options by @AprilNEA in #4645
- feat(adapter): add getConnInfo for AWS Lambda, Cloudflare Pages, and Netlify by @rokasta12 in #4649
- feat(trailing-slash): add
alwaysRedirectoption to support wildcard routes by @yusukebe in #4658 - perf(context): add fast path to c.json() matching c.text() optimization by @mgcrea in #4707
- feat(language): add progressive locale code truncation to normalizeLanguage by @sorafujitani in #4717
- feat(types): Add exports field to ExecutionContext by @toreis-up in #4719
- perf(trie-router): improve performance (1.5x ~ 2.0x) by @EdamAme-x in #4724
- perf(context): use
createResponseInstancefor new Response by @yusukebe in #4733 - Next by @yusukebe in #4735
New Contributors
- @mixelburg made their first contribution in #4729
- @aidenlx made their first contribution in #4732
- @mohankumarelec made their first contribution in #4556
- @ShaMan123 made their first contribution in #4636
- @rokasta12 made their first contribution in #4649
- @mgcrea made their first contribution in #4707
Full Changelog: v4.11.10...v4.12.0