The agent-ready React framework. One page definition serves humans (streamed HTML, zero client JS) and agents (markdown, JSON, MCP) — nothing drifts, because nothing is duplicated.
Status: 0.0.x preview. The spec is still being drafted and APIs will change. Early feedback is the point — open an issue.
npm create june@latest my-app
cd my-app && npm install
npm run dev # → http://localhost:3000The scaffolder runs on Node; the june CLI runs on Bun (≥ 1.3).
You get a working app, not a blank page:
my-app/
app/
page.tsx # one page — also answers /.json and /.md
users/page.tsx # a second route with a defineAction() → an MCP tool
layout.tsx # wraps every page (nested layouts compose root → leaf)
Counter.tsx # a client island — the ONE subtree that hydrates
_client.tsx # the island registry; its presence enables /client.js
june.config.ts # exists to turn things OFF — defaults are on
package.jsonThen try both audiences:
curl localhost:3000/.md # the page you just saw, as markdown
curl -X POST localhost:3000/mcp \
-H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
npx june info # routes + the agent surface, at a glanceA page's default export is the view; named exports configure the other
surfaces. .json auto-derives from the loader data:
import type { RouteContext, Loaded } from "@junejs/core/route";
export const loader = (ctx: RouteContext<{ slug: string }>) => fetchPost(ctx.params.slug);
export default function Post(post: Loaded<typeof loader>) { // GET /posts/x → streamed HTML
return <article>…</article>;
}
export const md = (post: Loaded<typeof loader>) => post.original; // GET /posts/x.md → authored markdown
// GET /posts/x.json → the loader data, auto-derivedAnd one defineAction() is simultaneously a server action for your UI and
an MCP tool at /mcp — same run(input, ctx), same authorization gate.
llms.txt, sitemap, and an API catalog derive from the route graph
automatically.
- Built-in MCP — your app is an MCP server, no adapter
- Markdown without drift —
.mdserves your authored source - og:images as routes — satori + resvg in the worker, CJK-ready
- Ambient data + cache magic —
import { db }, explicit SQL migrations; a write auto-invalidates cached reads - Server-first RSC + islands — zero client JS until a subtree earns it
- Styling —
app/global.cssauto-linked, Tailwind v4 + CSS Modules, hashed & minified on build - App Router —
[slug],[[optional]],[...catchAll],(groups), nested layouts - Browser-native navigation — Speculation Rules + View Transitions, no router by default
- Opt-in client router —
clientRouter: trueadds soft swaps +<Island persist>when state must outlive a navigation - Web Standards end to end —
fetch(Request) → Responseis the framework - Reload-on-save dev loop — server restarts, browser follows
- Deploy —
june deploy→ Cloudflare Workers today; the host seam makes other targets adapters
Every docs page is also markdown — append .md to any
june.build URL. The site is built with June and is its
own demo.
No streamed Suspense fallbacks yet, no Flight-payload navigation, and the Rust+V8 runtime numbers on the site are an experimental track — today's host is Bun/Node, deploying to Workers. The full list lives on june.build/why.
packages/core @junejs/core — the pure contract layer (zero node:*, enforced)
packages/june @junejs/server — host adapters, dev server, build, deploy
packages/cli @junejs/cli — the `june` command
packages/db @junejs/db — ambient db/kv/blob (request-scoped, edge-safe)
packages/juno @junejs/juno — the default data layer
packages/create-june the scaffolder
apps/june.build the framework site, dogfooded on June
examples/ fixtures (the golden dev ≡ built-worker parity contract)
docs/ architecture notesbun install
bun run ci # typecheck + the full suite (incl. parity + packed-artifact E2E)MIT © June.build