Skip to content

feat(inertia): add deferred props with defer()#1911

Merged
yusukebe merged 2 commits into
honojs:mainfrom
ashunar0:feat/inertia-deferred-props
Jun 2, 2026
Merged

feat(inertia): add deferred props with defer()#1911
yusukebe merged 2 commits into
honojs:mainfrom
ashunar0:feat/inertia-deferred-props

Conversation

@ashunar0

Copy link
Copy Markdown
Contributor

Summary

Second step of the @hono/inertia v3 protocol breakdown: ② Deferred props, built on top of the partial reload foundation from #1904.

Adds a defer(resolver, group?) helper. On the initial response the resolver is skipped and the prop key is advertised under page.deferredProps[group]. The Inertia client then issues one partial reload per group, at which point the resolver runs and the value is sent down — letting heavy data fetching happen after the page becomes interactive.

import { defer, inertia } from '@hono/inertia'

app.use(inertia())

app.get('/', (c) =>
  c.render('Dashboard', {
    user: { id: 1 },                       // sent on initial response
    posts: defer(() => fetchPosts()),      // fetched after mount
    stats: defer(() => fetchStats(), 'secondary'),
  }),
)

Multiple deferred props that share a group are fetched together in a single partial reload. The default group is "default".

Protocol behaviour

Visit kind posts: defer(fn) behaviour
Initial (full page or X-Inertia GET) fn not invoked, page.deferredProps.default = ['posts'] emitted
Partial reload that includes posts in X-Inertia-Partial-Data fn invoked, value returned under props.posts
Partial reload that does not include posts fn not invoked, key omitted from response
Non-Inertia Accept: application/json request fn not invoked, key omitted

deferredProps is only emitted on the initial response; subsequent partial reloads strip it, so the client does not re-trigger the same group.

Implementation

  • defer<T>(resolver, group = 'default'): T returns an opaque marker (Symbol.for('@hono/inertia/defer')) that the renderer unwraps. The return type is T, so call sites stay transparent and TypedResponse infers the awaited resolver type.
  • The renderer loop now distinguishes three prop kinds — plain values, function-valued props (from feat(inertia): add partial reloads support with lazy function props #1904), and deferred markers — and decides on a single sync/async path based on whether any resolver needs awaiting. The non-deferred sync path is preserved when no async work is needed.
  • PageObject gains an optional deferredProps?: Record<string, string[]> field, only present on the initial response when at least one prop was deferred.

Scope intentionally kept small (single PR per protocol feature, like #1904):

  • ✅ Top-level defer() markers
  • ✅ Multiple groups + default group
  • ✅ Skip on initial, resolve on requested partial
  • ❌ Nested / dot-path deferred markers — separate PR
  • merge / prepend / deepMerge / always(fn) / infinite scroll markers — separate PRs (③④⑤ in the breakdown)

Test plan

  • yarn workspace @hono/inertia test (28 passed, 9 new tests covering initial skip / group emission / partial resolve / only filter / JSON request / mixed deferred + function props / no-defer no-emit / non-XHR HTML visit)
  • eslint packages/inertia clean
  • prettier --check clean
  • tsc -b clean
  • tsdown build success

@changeset-bot

changeset-bot Bot commented May 28, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 84f47fe

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@hono/inertia Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@codecov

codecov Bot commented May 30, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 92.11%. Comparing base (1e8892a) to head (84f47fe).
⚠️ Report is 3 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1911      +/-   ##
==========================================
+ Coverage   92.08%   92.11%   +0.03%     
==========================================
  Files         115      115              
  Lines        3965     3983      +18     
  Branches     1023     1029       +6     
==========================================
+ Hits         3651     3669      +18     
  Misses        282      282              
  Partials       32       32              
Flag Coverage Δ
inertia 100.00% <100.00%> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ 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.

Comment thread packages/inertia/src/index.test.ts Outdated
@yusukebe

yusukebe commented Jun 1, 2026

Copy link
Copy Markdown
Member

I forgot to say that in #1904. Can you update the README for these new features? You can put both #1904 and this PR now.

The previous assertion checked for '"posts"\\:[{' (with an escaped
colon), but serializePage only escapes '/' → '\\/', so the string
would never appear in the rendered HTML — the test passed trivially.
Match the actual serialized form so the assertion verifies that the
deferred value is omitted from the initial HTML.
@ashunar0

ashunar0 commented Jun 2, 2026

Copy link
Copy Markdown
Contributor Author

@yusukebe Thanks for the review!

@yusukebe yusukebe left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@yusukebe

yusukebe commented Jun 2, 2026

Copy link
Copy Markdown
Member

@ashunar0 Thank you so much!

@yusukebe yusukebe merged commit c5758a6 into honojs:main Jun 2, 2026
98 checks passed
@github-actions github-actions Bot mentioned this pull request Jun 2, 2026
@ashunar0 ashunar0 deleted the feat/inertia-deferred-props branch June 5, 2026 08:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants