Skip to content

perf(context): add fast path to c.json() matching c.text() optimization#4707

Merged
yusukebe merged 5 commits intohonojs:nextfrom
mgcrea:main
Feb 19, 2026
Merged

perf(context): add fast path to c.json() matching c.text() optimization#4707
yusukebe merged 5 commits intohonojs:nextfrom
mgcrea:main

Conversation

@mgcrea
Copy link
Contributor

@mgcrea mgcrea commented Feb 10, 2026

c.text() already has a fast path (added in an earlier optimization) that returns new Response(text) directly when no status, headers, or finalized state exists. c.json() was missing this optimization and always went through #newResponse(), which unconditionally allocates a Headers object and iterates header entries — even for the simplest c.json({ ... }) call.

This PR adds the same 5-condition fast path to c.json().

What changed

In src/context.ts, the json() method now checks:

!this.#preparedHeaders && !this.#status && !arg && !headers && !this.finalized

When all conditions are met (the common case for simple JSON responses), it creates the Response directly with an inline header object instead of going through #newResponse():

new Response(jsonString, {
  headers: { 'Content-Type': 'application/json' },
})

Why this matters

In the @hono/node-server adapter, this has a compounding effect. The lightweight Response constructor stores inline header objects directly in its cache. When the adapter writes the response, it checks header instanceof Headers — a plain object skips the buildOutgoingHttpHeaders() conversion entirely, while a Headers instance requires iteration and conversion to OutgoingHttpHeaders.

Benchmark results (nodejs)

with corresponding @hono/node-server optimizations, see honojs/node-server#301

Benchmark Before After Change
Ping 55,183 59,697 +8.2%
Query 46,033 52,883 +14.9%
Body 26,439 46,726 +76.7%
Average 42,552 53,102 +24.8%

Body benchmark improvement is primarily from @hono/node-server direct body reading; this PR contributes to the overall gains by eliminating allocations on the response path.

Benchmarked with bun-http-framework-benchmark using bombardier at 500 concurrent connections for 10s per test on Node.js.


Thanks for the great lib :)

mgcrea and others added 2 commits February 10, 2026 22:47
Skip #newResponse() and Headers allocation when no status, headers,
or finalized state exists. Creates Response directly with inline
Content-Type header, matching the existing c.text() fast path pattern.
@codecov
Copy link

codecov bot commented Feb 13, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 91.44%. Comparing base (69ad885) to head (a32bdde).
⚠️ Report is 16 commits behind head on next.

Additional details and impacted files
@@            Coverage Diff             @@
##             next    #4707      +/-   ##
==========================================
+ Coverage   91.43%   91.44%   +0.01%     
==========================================
  Files         173      173              
  Lines       11382    11424      +42     
  Branches     3302     3319      +17     
==========================================
+ Hits        10407    10447      +40     
- Misses        974      976       +2     
  Partials        1        1              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@yusukebe yusukebe changed the title perf: add fast path to c.json() matching c.text() optimization perf(context): add fast path to c.json() matching c.text() optimization Feb 15, 2026
@yusukebe
Copy link
Member

yusukebe commented Feb 17, 2026

@mgcrea

This PR is super cool. Especially if adding this fast path, we can use Response.json() as commented in #4707 (comment). If so, it can reduce the bundle size and get it faster.

Sorry! I completely misunderstood. The file size is slightly increased. But it's interesting that we can use Response.json() (#4707 (comment)) with this PR.

I would like to merge this. Can you handle my comments? Or, I'll fix them myself.

@yusukebe yusukebe added the v4.12 label Feb 19, 2026
@yusukebe yusukebe changed the base branch from main to next February 19, 2026 10:15
@yusukebe yusukebe merged commit 7438ab9 into honojs:next Feb 19, 2026
21 checks passed
yusukebe added a commit that referenced this pull request Feb 23, 2026
@yusukebe yusukebe mentioned this pull request Feb 23, 2026
4 tasks
yusukebe added a commit that referenced this pull request Feb 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants