@k8ordo/framework

How it works

Static mode calls the request handler at build time, and server mode calls it per request. That is the chief difference between them, and it changes what each mode rejects.

On this page

Setting the mode

framework()'s mode option picks the mode. Either way, the package installed is the same @k8ordo/framework.

The routes/ conventions and the 'use client' boundary are the same in both modes.

Handler calls

text
mode: 'static'
  vite build → handler(/products/1)           → index.html
             → handler(/products/1/index.rsc) → index.rsc

mode: 'server'
  request    → handler(request)               → Response

Under either mode, vite build writes a request handler to dist/rsc/index.js: a function that takes a Request and returns a Response.

Under static mode the build calls the handler twice per page at its end, once for the HTML and once for the payload, and writes the responses to index.html and index.rsc. A page that redirects gets only index.html, and a route.ts is written as the one file its GET answers. Under server mode the handler is called for every request, and its response is returned.

vite dev calls the handler for every request under either mode. So under static mode too, a page for a value paths does not list still renders in development, or its shell does, for a page with a fallback.tsx. For the other ways development differs from the build, see Troubleshooting.

HTML and the payload

text
/products/1            → HTML
/products/1/index.rsc  → RSC payload

Under either mode a page has two forms: HTML and an RSC payload. The payload’s URL is the page’s path with /index.rsc appended. A client navigation fetches that payload.

The first page opened carries the payload it was rendered from inside its HTML. Hydration reads that, so the same page is never fetched twice.

Refusals under static mode

vite build
static build cannot run guard.ts — a file has no request to guard, and these are guards:
  src/routes/admin/guard.ts
this application wants mode: 'server'

A static mode application still has the server features installed. Using one under static mode fails the build.

A guard.ts, a page that exports search and a route.ts that exports anything but GET fail before the build starts. An import of @k8ordo/framework/server or 'use server' fails once the environments are compiled. Both stop the build before any page is written.

vite dev reports the same error the moment it compiles the module, so no code works in development and does nothing in production. What is refused, and why, is listed on Choosing a mode.

Refusals under server mode

A running server takes requests from anywhere. The handler turns away the ones the application should not answer before anything renders.

  • A POST from another origin: a POST to a page or a Server Action gets a 403 when it has no Origin header, or when that header names a host other than the request’s. This keeps another site’s form from calling a Server Action with the visitor’s cookies.
  • A method other than GET, HEAD and POST: a request to a page gets a 405 whose Allow lists those three.
  • A URL outside Vite’s base: it is not one of the application’s, and gets a 404.

Errors in both modes

These are errors under either mode.

  • A file or directory that breaks the routes/ conventions, and a route that answers no URL
  • A paramsSchema that validates asynchronously (a params schema must validate synchronously)
  • A server-only module imported from a Client Component
  • A Vite base that does not start from the root (./, for example)
k8ordo

React libraries that use Baseline features without holding back.

© 2026 k8o — MIT License

Typeset in Noto Sans JP & M PLUS 2