Web Standards end to end
The feature
June's entire request path is the Web platform's own vocabulary:
fetch(request: Request): Promise<Response>
That signature is the framework. Inside it: URL for routing, Headers
for negotiation, ReadableStream for HTML streaming
(renderToReadableStream, not the Node-only pipeable variant), Web crypto
for IDs, the Cache API where the host offers one. @junejs/core imports
zero node:* modules — enforced as an architectural rule, not a habit.
Standards as the portability mechanism
This is why deployment is an adapter instead of a rewrite (Deployment):
- workerd speaks fetch natively — the built worker's
fetch()IS the pipeline, no shim. - Bun serves it directly (
Bun.serve({ fetch })). - Node needs only an edge adapter:
node:httprequests are converted toRequest/ fromResponseat the boundary (Readable.toWeb/fromWeb) — and nothing below the boundary knows.
Same story for data: the db contract is async-first because edge databases
(D1) are async — the standard-shaped surface is the one every target can
implement.
Try it
The MCP endpoint is a plain standards handler too — mcpHandler(request)
takes a Request like everything else. There is no framework-specific
request object to learn and no middleware tower to thread:
// a June app is embeddable anywhere a fetch handler runs
const app = createApp({ appDir, config });
export default { fetch: (req: Request) => app.fetch(req) };
Why it matters
Framework-specific request objects are how lock-in starts. When the interface is the platform's own, your knowledge transfers in, your code transfers out, and new runtimes are adapters — for us and for you.