Skip to content

v0.3.0

Choose a tag to compare

@sbesh91 sbesh91 released this 25 May 01:18
· 139 commits to main since this release

hono-preact v0.3.0 — release notes

Draft for the gh release create v0.3.0 body. Headline features, breaking changes, and the upgrade path from v0.2.0.


hono-preact v0.3.0

Web-platform alignment: progressive-enhancement forms, cancellation-aware loaders and actions, and opt-in browser link prefetch.

Highlights

  • Progressive-enhancement forms (Spec C, #59). <Form> submissions now work without client JavaScript. The form posts multipart/form-data to the page's own URL; the framework's page handler runs the action, then redirects, re-renders with errors, or streams. With JS on, the framework intercepts the submit and content-negotiates the same URL with Accept: application/json to get the result back without a full page round-trip. One endpoint, content-negotiated.
  • useActionResult and useFormStatus hooks (Spec C, #59). Read the most recent action result on the no-JS-recovery path, and observe pending/error state of the form's submission. Both compose naturally with useOptimistic.
  • Loader and action timeouts (Spec A, #56). defineLoader and defineAction accept timeoutMs, default 30_000. Timed-out calls surface a structured TimeoutOutcome (catch with isTimeout(error) or error instanceof TimeoutError). Composes AbortSignal.any over the request signal and AbortSignal.timeout. Opt out with timeoutMs: false.
  • useOptimistic View Transitions (Spec A, #56). Pass { transition: true } to wrap the settle and revert paths in document.startViewTransition. The initial mutate stays unwrapped so optimistic UI still paints in the same frame.
  • TransformStream-based SSE codec (Spec A, #58). Streaming loaders and actions now use TransformStream + TextDecoderStream, replacing the hand-rolled SSE codec. Wire format is unchanged; the hono/streaming dependency is gone from this path.
  • Speculation Rules emitter (Spec E, #60). Set defineApp({ speculation: true }) to emit a single <script type="speculationrules"> in <head> that tells supporting browsers (Chrome, Edge) to prefetch same-origin <a href> links on moderate eagerness. Opt individual links out with <a data-no-prefetch>. Off by default; non-Chromium browsers ignore the tag.

Breaking changes

Actions endpoint moved from /__actions to the page URL

The framework no longer mounts a global /__actions handler. Actions are invoked at the page's own URL via POST and dispatched by __module / __action body fields. The default <Form> markup works unchanged; the change is wire-level.

If your app mounted custom middleware on /__actions, move it to the page route or to the page-level use array. The Vite plugin's reserved-path check still guards the legacy URL: building with a route at /__actions errors out.

Action result envelope unified

The JSON response shape from action POSTs is now uniformly __outcome-tagged:

{ __outcome: 'success', data }
{ __outcome: 'redirect', to, status }
{ __outcome: 'deny', status, message, data? }
{ __outcome: 'timeout', timeoutMs }
{ __outcome: 'error', message }

useAction reads __outcome first and dispatches; no consumer code changes. Apps that fetched /__actions with hand-rolled fetch() calls must update to match this shape and the new URL.

Default 30-second loader and action timeout

Any loader or action that legitimately runs longer than 30 seconds now produces TimeoutOutcome instead of completing. Opt out per-call:

defineLoader(fn, { timeoutMs: false });
defineAction(fn, { timeoutMs: false });

Or set a longer deadline: { timeoutMs: 120_000 }. Streaming loaders apply the timeout to total time-to-completion of the stream, not time-to-first-chunk.

New API surface

  • defineApp({ speculation: true }) — opt in to the Speculation Rules emitter.
  • defineLoader(fn, { timeoutMs }) / defineAction(fn, { timeoutMs }) — per-call deadline override.
  • useActionResult() — read the latest action result on the PE recovery path.
  • useFormStatus() — observe pending state of a <Form> submission.
  • useOptimistic(reducer, initial, { transition: true }) — wrap settle/revert in View Transitions.
  • useOptimisticAction(action, { transition: true, ...existing }) — same option.
  • pageActionHandler(serverModules) — handler for custom server entries that need to run page-URL POSTs themselves.
  • isTimeout(value) / TimeoutError — narrow on the new outcome variant.
  • <a data-no-prefetch> — per-link opt-out from speculation prefetch when enabled.

Docs

Three new docs pages and one updated:

  • /docs/link-prefetch — Speculation Rules opt-in and audit guide.
  • /docs/actions — updated for the PE-form path and the unified envelope.
  • /docs/render-pageoptions.appConfig documented for custom server entries.

Full docs: https://framework.sbesh.com/docs · Demo: https://framework.sbesh.com/demo

What's not in this release

  • Navigation API as primary intercept (Spec B). Deferred. Waiting on first-class Navigation API support in preact-iso upstream; the framework will adopt the option there once it lands.
  • Suspense-streamed SSR via renderToReadableStream (Spec D). Shelved. The existing async-generator loader streaming covers the same capability for slow data, and the late-outcome and SEO trade-offs of Suspense streaming aren't worth the ergonomics gap. See the roadmap doc for the rationale.

Install

pnpm add hono-preact hono preact preact-iso preact-render-to-string hoofd
pnpm add -D vite

# Pick one adapter:
pnpm add -D @cloudflare/vite-plugin wrangler            # Cloudflare Workers
pnpm add @hono/node-server                              # Node.js (add @hono/node-ws for WebSockets)

Or scaffold a new project:

pnpm create hono-preact my-app

Release mechanics

Driven by scripts/release.mjs. Reads the version from packages/hono-preact/package.json, sanity-checks that create-hono-preact and both template pins agree, publishes hono-preact first (its prepublishOnly runs tsc and consolidate.mjs to inline the @hono-preact/iso|server|vite source into dist/), then create-hono-preact second, then tags and pushes. Idempotent: a version already on the registry is skipped, so you can re-run after a partial failure.

pnpm release:dry        # preview tarball contents and tag plan, no upload
pnpm release            # real publish + git tag + push tag
gh release create v0.3.0 -F docs/superpowers/specs/2026-05-24-v0.3-release-notes.md --latest

The gh release create step is intentionally separate so you can review or edit the notes before publishing.

Lockstep versioning policy: hono-preact and create-hono-preact move together on minor/major bumps; patches are independent. Template hono-preact dep pins track the minor.

Pre-flight edits already applied on this branch:

  • Templates: hono-preact: ^0.2.0^0.3.0 (both Cloudflare + Node templates).
  • packages/hono-preact/package.json version: 0.2.00.3.0.
  • packages/create-hono-preact/package.json version: 0.2.00.3.0.