@k8ordo/framework
v1.4.2
Published
The k8ordo framework: routes/ compiled into React Server Components, run with or without a server.
Maintainers
Readme
@k8ordo/framework
Turns src/routes/ into an application of React Server Components, built
with Vite, and lets the same routes run with or without a server.
mode: 'static' renders every page at build time into files any static host
serves. mode: 'server' renders per request, which is what gives an
application Server Actions, guards, cookies and a real 404. The routes, the
boundaries and the request handler are the same in both.
Like every k8ordo package it assumes React 19 and Server Components, uses only what has reached Baseline newly available, and ships no polyfills or legacy fallbacks.
- Documentation: https://ordo.k8o.me/en/framework
- Design guide: docs/GUIDE.md — shipped inside this package
Installation
npm install react react-dom server-only
npm install -D @k8ordo/framework @k8ordo/router vite typescript @types/node @types/react @types/react-dom
# or
pnpm add react react-dom server-only
pnpm add -D @k8ordo/framework @k8ordo/router vite typescript @types/node @types/react @types/react-dom@k8ordo/router is a required peer, so the application and the framework
share one copy, but the application imports from @k8ordo/framework, which
re-exports what it needs (href, notFound, PageProps, …). Both are dev
dependencies in either mode: the build bundles what the application runs.
Peer Dependencies
| Package | Version | Required | Needed for |
| --------------------- | -------- | -------- | ------------------------------------------ |
| @k8ordo/router | ^1.4.2 | yes | routing, re-exported by the framework |
| react | ≥19.3.0 | yes | rendering |
| react-dom | ≥19.3.0 | yes | rendering |
| vite | ≥8.1.0 | yes | the build (framework() is a Vite plugin) |
| oxc-transform-react | ^0.152.0 | optional | reactCompiler: true (React Compiler) |
Quick Start
// vite.config.ts
import { framework } from '@k8ordo/framework/vite';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [
framework({
mode: 'static',
paths: () => ['/products/1', '/products/2'], // for routes with parameters
site: 'https://example.com', // and a sitemap.xml
}),
],
});
// or: framework({ mode: 'server' }) — rendered per requestsrc/routes/
layout.tsx <html> and <body>; wraps everything through `children`
page.tsx /
not-found.tsx static: 404.html — server: a real 404
error.tsx shown in place of what is below when it throws
products/[id]/
page.tsx /products/:id — `export const paramsSchema` types :id
fallback.tsx static: the shell for the ids paths does not list
old/redirect.ts /old sends the visitor elsewhere
feed.xml/route.ts /feed.xml answered by its GET — a Response, not a page
admin/guard.ts server mode: runs before everything under /admin// src/routes/products/[id]/page.tsx
import { notFound } from '@k8ordo/framework';
import type { PageProps } from '@k8ordo/framework';
export default async function ProductPage({
params,
}: PageProps<'/products/:id'>) {
const product = await findProduct(params.id);
if (product === undefined) notFound();
return <h1>{product.name}</h1>;
}vite dev # renders per request, with Fast Refresh
vite build # static: dist/client/ is the site
k8ordo generate # only .k8ordo/, the route table and its types, for tsc in CIUnder mode: 'server' the build is a dist/ that runs on its own — every
dependency bundled in, nothing to install where it is deployed — a form
posts to a Server Action, and a page receives request, the headers and the
cookies, read-only:
vite build
PORT=3000 node dist/server.js// src/lib/actions.ts
'use server';
import { href } from '@k8ordo/framework';
import { redirect } from '@k8ordo/framework/server';
export async function createTalk(_previous: FormState, formData: FormData) {
const parsed = parseForm(talkSchema, formData);
if (!parsed.success) return parsed.state;
await insertTalk(parsed.data);
redirect(href('/talks')); // 303 without JavaScript, a navigation with it
}The built handler, dist/rsc/index.js, is a plain
(request: Request) => Promise<Response> that needs nothing from its runtime
but AsyncLocalStorage, so Node.js, Bun, Deno and Cloudflare Workers
(nodejs_compat) all run it, and vercel() from @k8ordo/framework/vercel
writes a build for Vercel.
Under mode: 'static' there is no request, so the build refuses by name
whatever needs one — a 'use server' module, a guard.ts, an import of
@k8ordo/framework/server — in vite dev as well: this application wants
mode: 'server'.
A value paths does not list — a product added after the build — is
answered by a fallback.tsx beside the page: the build writes it once as a
shell, the layouts in HTML, and the browser draws the page from the URL.
The build also writes the _redirects through which Netlify and Cloudflare
reach the shell, and vercel() the same rewrites for Vercel — around them,
routes of your own: headers on every page, redirects, a rewrite decided by
Accept, a proxy to another origin.
AI Agent Documentation
The docs ship inside the package, so an agent always reads the exact version you installed — there is no snapshot to copy or re-sync on upgrade.
Point your agent at them once by pasting this into your project's CLAUDE.md /
AGENTS.md:
This application is built with `@k8ordo/framework` (see `mode` in
`vite.config.ts`). Before adding or changing a route, read
`node_modules/@k8ordo/framework/docs/GUIDE.md`, then only the
`docs/references/*.md` it lists that the task needs. `src/routes/` is the
pathname space and holds only page.tsx, layout.tsx, not-found.tsx,
error.tsx, loading.tsx, fallback.tsx, redirect.ts, guard.ts and route.ts;
components go in `src/components/` and every other module (Server Actions,
schemas, state definitions, data, helpers) in `src/lib/`, since a
`_`-prefixed directory there is a URL segment, not a private one. Never
edit `.k8ordo/` — it is generated, with the `Register` augmentations: the
route table's, and `@k8ordo/i18n`'s when `src/i18n.ts` exports the locale
set by the name `locales` (write that one by hand only when the set lives
elsewhere). Import from `@k8ordo/framework` (never
`@k8ordo/router`), and the request API from `@k8ordo/framework/server`,
which `mode: 'static'` refuses. Build links with `href()`; search params
are `@k8ordo/state`'s.What each surface gives an agent:
| Surface | Where |
| -------------------------- | ----------------------------------------------------- |
| Design guide (entry point) | node_modules/@k8ordo/framework/docs/GUIDE.md |
| Reference docs | node_modules/@k8ordo/framework/docs/references/*.md |
| Docs index for LLMs | docs/llms.txt · https://ordo.k8o.me/llms.txt |
| Markdown twin on the web | https://ordo.k8o.me/framework/docs/GUIDE.md |
License
MIT License - see LICENSE for details.
