Get Started
Install @k8ordo/server, add one plugin to vite.config.ts, put a layout and a page under routes/, and serve() runs what vite build produced. This page walks the shortest path there, and what choosing this mode means.
The mode is the dependency
Installing this package is what makes an application one that runs. Every request is answered by rendering, which is what makes route parameters need no list of values, an unknown URL a real 404, and a Server Action something a form can post to.
The alternative, @k8ordo/static, renders every route at build time and ships files. Nothing else about the application changes between them — the same route grammar, the same boundaries, the same request handler, called per request instead of for each route at build time. That is why the plugin is called framework() in both packages: the mode is the import, and vite.config.ts reads the same either way.
Install
@k8ordo/server is a runtime dependency: serve() and the built handler are what the deployed application runs. @k8ordo/server itself is the plugin and loads Vite, but what the application’s own code imports — serve(), redirect() and the types — comes from @k8ordo/server/runtime, which does not, so vite is a dev dependency. server-only is there so TypeScript can resolve the import that marks a server-only module.
npm install @k8ordo/router @k8ordo/server react react-dom server-only
npm install -D viteThe peer dependencies, and the Node.js the build and the server run on.
- @k8ordo/router
- React >= 19.3.0
- React DOM >= 19.3.0
- Vite >= 8.2.1
- Node.js >= 24
vite.config.ts
The plugin is framework(). Its one option is routesDir, the route directory (default src/routes).
// vite.config.ts
import { framework } from '@k8ordo/server';
import { defineConfig } from 'vite';
export default defineConfig({ plugins: [framework()] });framework() returns an array of Vite plugins that already includes React's plugin (Fast Refresh) and the RSC pipeline, so there is no @vitejs/plugin-react to add yourself.
tsconfig.json
For the type wiring to apply, list .k8ordo in tsconfig.json's include with a glob. The name starts with a dot, and a bare directory entry silently skips it — the build still works, and href simply stops being checked against the table.
{
"include": ["src/**/*.ts", "src/**/*.tsx", ".k8ordo/**/*.ts"]
}The smallest routes/
The root layout renders <html> and <body>. The framework has no document template of its own, because a template you cannot see is a template you cannot change. A file with no directive is a Server Component.
// src/routes/layout.tsx
import type { ReactNode } from 'react';
export default function RootLayout({ children }: { children: ReactNode }) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}// src/routes/page.tsx
export default function HomePage() {
return (
<>
<title>home</title>
<h1>hello</h1>
</>
);
}The root layout is the document
Hydration checks all of the document. Anything that rewrites the HTML between what the framework wrote and the browser — a CDN that inlines web fonts, obfuscates email addresses or defers scripts — changes a tree React is about to reconcile, and hydration fails on the difference. The way to be unaffected is to give such a service nothing to rewrite: serve the assets from the same origin rather than linking them from another one.
Run it
vite dev runs the same pipeline as production, with Fast Refresh. vite build writes dist/, and a small script that calls serve() runs it.
// serve.js
import { serve } from '@k8ordo/server/runtime';
await serve({ port: 3000 });vite dev
vite build
node serve.jsWhat gets generated
The first vite dev or vite build writes .k8ordo/: the route table (routes.gen.ts), the type wiring that ties it into @k8ordo/router (register.gen.ts), and a .gitignore that keeps the directory out of git.
vite build does not type-check; the satisfies checks report through tsc. .k8ordo/ is not in git, so on a fresh checkout run vite dev or vite build once before tsc to write it.
What is in them, and what the build refuses, is covered here. routes/
When to choose @k8ordo/static
If nothing needs the request, @k8ordo/static writes the same application out as files, and there is no server to keep running. That is an application that needs none of the following.
- Server Actions a form can post to, and
redirect()from them - A page reading the request's headers and cookies
- A real 404 answered by the application itself
- Parameter values that cannot be listed ahead of time