Node-first local runtime with optional Cloudflare adapters - #1
Draft
kamath wants to merge 6 commits into
Draft
Conversation
Keep packages/api as a fetch handler that reads portable AppBindings instead of Worker types or request.cf. Serve the frontend and standalone API on Node by default, and leave wrangler.jsonc plus thin Worker entries as an opt-in deploy path.
Vite's config runner cannot execute the workspace TypeScript package directly. Dev now ssrLoadModules @template/api, and preview uses tsx, so typecheck still consumes the API's emitted declarations.
wrangler cannot bundle src/server.ts on its own: the TanStack Start entry only exists inside the Vite build, so deploying the root wrangler.jsonc failed on unresolved virtual modules. Ship the resolved dist/server/wrangler.json the Cloudflare Vite plugin emits instead, and drop the tsconfig.worker.json left orphaned by the Node-first switch.
tls.getCACertificates and setDefaultCACertificates only exist on newer Node, so the named import took down every CLI command — including pnpm dev — on Node 22.14. Import the namespace and feature-detect instead, skipping the portless CA when the runtime cannot extend it.
Node stays the default, but running on workerd was previously an undocumented env var and, for the standalone API, not possible at all. Give both apps a dev:cf script and teach the CLI a --cloudflare flag so the portless URL and browser handoff are identical either way. Rename the build-target variable to TARGET so it stops colliding with the RUNTIME binding that reports which runtime served a request, and pass --no-install-skills so wrangler never blocks startup on a prompt.
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.
Node is the default runtime. Cloudflare is one flag away, on the same code.
pnpm dev --cloudflareworks too — it's a flag onpnpm cli dev, so it shows up in--helpinstead of being tribal knowledge. Hit/api/statsto see which runtime answered:uptimeModereportslong-running processon Node andper-request isolateon workerd.Approach
packages/apiis the source of truth and stays a plain fetch handler. It reads a smallAppBindingsobject (DATABASE_URL,REGION,RUNTIME) and never imports wrangler, Worker types, orEnv. Only the host adapters differ:apps/frontendsrc/server.tsWorker entryapps/server@hono/node-server(src/node.ts)src/server.tsWorker entryTARGET=cloudflareselects the Cloudflare pipeline at build/dev time, but thedev:cf,build:cf, anddeployscripts set it, so you shouldn't need to type it. It's deliberately not calledRUNTIME— that's the binding reporting which runtime actually served a request.To add bindings: put them in
wrangler.jsonc, runpnpm cf-typegen, and map them ontoAppBindingsin the adapter (Hyperdrive becomesDATABASE_URL). Never hand-writeEnv. That sameDATABASE_URLslot is the later swap point for PGlite locally and real Postgres in production. Do not auto-migrate on startup.Two pre-existing bugs fixed along the way
Cloudflare deploy was broken on
main. The rootwrangler.jsoncpointsmainatsrc/server.ts, so wrangler rebundled it from source and failed on TanStack Start's virtual modules (#tanstack-start-entry).pnpm deploynow ships the resolveddist/server/wrangler.jsonthe Vite plugin emits.The CLI crashed at import on Node 22.14.
tls.getCACertificatesdoesn't exist there, and the named import took down every command includingpnpm dev. Now a namespace import with feature detection.Also removed
apps/server/tsconfig.worker.json, orphaned by the Node-first switch, and passed--no-install-skillsso wrangler never blocks startup on an interactive prompt.Verification
All four dev paths were started and probed, confirming each reports the expected runtime:
uptimeModefrontend devlong-running processfrontend dev:cfper-request isolateserver devlong-running processserver dev:cfper-request isolatepnpm typecheckandpnpm lintpass, including from a clean checkout with no emitteddist/,/about,/api,/api/stats,/api/openapi.jsonall 200 on both runtimeswrangler deploy --dry-run(bundled and validated, not deployed)