feat(next): @devframes/next — Next.js host integration (experimental package) - #140
Merged
Conversation
Serve mounted devframe SPAs and connection meta from a Next.js App Router route via a single WHATWG-fetch handler that reuses devframe's serveStaticHandler, closing the Nuxt/Next bridge parity gap. Refactors the minimal-next-devframe-hub example onto the bridge, removing ~200 lines of hand-rolled static serving and registries. Implements plan 027 (spike); proposal in plans/notes/devframes-next-proposal.md.
✅ Deploy Preview for devfra ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
Add docs/helpers/next.md (sidebar + helpers index + example cross-links), flagged experimental. Remove the plan-027 spec file now that it's DONE — the plans/README.md row remains the record, pointing at the proposal notes.
- Publish shape: drop private, publishConfig experimental tag, react/next optional peers, add ./client export; generate tsnapi API snapshots. - Add createDevframeNextHandler: single-plugin host wrapper that serves the SPA and starts a bridge-mode side-car RPC/WS server (mirrors viteDevBridge). - Add @devframes/next/client: React RpcProvider + useRpc surface. - Normalize the host fetch miss to a bare 404 (static-server parity). - Register source aliases + a vitest project; cover the handler with a test. - Docs: document both host modes + the client surface; drop the design-system transpilePackages example. Update the proposal notes to the promoted state.
- Drop the experimental npm dist-tag (publishConfig); the package stays flagged
experimental in its description and docs only, matching the other packages.
- RpcProvider now renders children eagerly and exposes live connection state:
useRpc() returns the client or null, useRpcStatus() returns { status, error }
(subscribed to the client's connection:status / connection:error events).
- Apply @devframes/next/client to examples/next-runtime-snapshot: its connect
provider is now a thin adapter over the package (the host/handler adapter
stays N/A there since devframe owns the server via createCac). Wire the turbo
build edge and regenerate the client API snapshot.
The spike proposal is fully realized in the shipped @devframes/next package, its tests/API snapshots, and docs/helpers/next.md; remove the stale note and point the plans/README row at the shipped artifacts instead.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Both Next examples hand-rolled static serving that duplicates devframe's own
serveStaticHandler, and@devframes/nuxthad no Next counterpart — a bridge parity gap. This spike (plan 027) closes it: a thin Next host-integration package, proven by refactoring the hub example onto it.Nuxt stays tiny because it reuses
viteDevBridge; Next is webpack/Turbopack and can't, so it needs its own route-handler primitive. That primitive is the deliverable.What
packages/next(private spike):createDevframeNextHost({ resolveOrigin, getStorageDir }) → { host, fetch, setConnectionMeta }— aDevframeHostwhosemountStatic/mountConnectionMetacalls accumulate into one WHATWG-fetchhandler that reuses devframe'sserveStaticHandler(via h3 v2app.mount+app.fetch). Connection-meta is answered before the static handler so SPA fallback can't swallow the discovery fetch; meta returns503untilsetConnectionMetapublishes the live WS port.withDevframe(nextConfig)— applies the host-mode setting Next requires (skipTrailingSlashRedirect).examples/minimal-next-devframe-hubrefactored onto the bridge: the 109-line catch-all route, both hand-rolled registries, and the by-handDevframeHostare gone (~200 lines removed); both routes collapse toreturn hub.fetch(request).turbo.json: added the@devframes/next#buildtask (this repo uses explicit per-package build tasks) and wired it into the example'sdependsOnso a clean CI builds the bridge first.plans/notes/devframes-next-proposal.md;plans/README.mdrow marked DONE.Scope note
Runtime pinned to Node (the static handler streams from disk). The single-plugin
createDevframeNextHandler(definition)wrapper and a clientRpcProviderare deferred to the promotion follow-up — documented in the proposal. This ships as a private spike, not a published package.Verification
next build(Next 16) · full Turbo build 20/20 · typecheck 21/21 incl. coverage guard · lint · example tests 4/4 · API snapshots 194 · runtime smoke against realplugins/git/dist/client(dir→index, SPA fallback, HEAD, 404, meta precedence, 503→200).This PR was created with the help of an agent.
Docs & plan cleanup
docs/helpers/next.mddocuments the bridge (sidebar + helpers index + example cross-links), flagged experimental since the package is an unpublished spike.plans/027-*.mdspec file (theplans/README.mdDONE row remains the record, pointing at the proposal notes) — matching the repo convention of keeping only open plans.Promotion to a published (experimental) package
Follow-up commit
9bbb582implements the proposal's open questions and ships it for real:private;publishConfig.tag: experimental(installs via@devframes/next@experimental, neverlatest).next+reactare optional peers; exports are.(Node) +./client(browser). tsnapi API snapshots generated undertests/__snapshots__/tsnapi/@devframes/next/.createDevframeNextHandler(def, options)— single-plugin host wrapper: serves the SPA + starts a bridge-mode side-car RPC/WS server (mirrorsviteDevBridge), returning{ fetch, ready, close }. Covered by a test that boots a real side-car.@devframes/next/client— ReactRpcProvider+useRpc(the$rpccounterpart),'use client'preserved through the browser build.fetchnormalizes a miss to a bare 404.transpilePackages: ['@antfu/design']example from the docs/config docstring; docs now cover both host modes + the client; proposal notes updated to the promoted state.Gates: full build 20/20, typecheck 21/21, tests (API snapshots 198 + handler 2 + example 4), lint, and docs build all green.
Refinements (commit
66a78c2)publishConfig; the package is flagged experimental in its description + docs only (matching every other package — no@experimentaltag).RpcProvidernow renders children eagerly and exposes live connection state:useRpc()returns the client ornull,useRpcStatus()returns{ status, error }(subscribed to the client'sconnection:status/connection:errorevents).examples/next-runtime-snapshot. Its hand-rolled connect provider is now a thin adapter over@devframes/next/client(host/handler adapter stays N/A there — devframe owns the server viacreateCac). Turbo build edge wired; client API snapshot regenerated.