How it works
How @k8ordo/static and @k8ordo/server work underneath. None of it is needed to use them, but knowing why the build refuses something makes the edge cases easier to reason about.
On this page
The mode is the dependency
Whether an application is written out as files or run on a server is not a setting: it is which package you installed. An application built with @k8ordo/static does not have the server’s machinery to reach for at all.
That is why it is not an option on one package. Not reading the request in a static application is not a rule to keep: there is no request to read.
Nothing else changes: the route grammar, the boundary between server and browser, and the handler that answers a request are all the same. That is why both plugins are named framework(), and why vite.config.ts looks the same under either mode.
One handler, called at different times
Under either mode, vite build writes a request handler to dist/rsc/index.js: a plain function that takes a Request and returns a Response. What tells the two modes apart is when it is called.
@k8ordo/static
vite build → handler(/products/1) → index.html
→ handler(/products/1/index.rsc) → index.rsc
@k8ordo/server
request → handler(request) → Response@k8ordo/static calls the handler twice for each page at the end of the build, and keeps the answers as files: the HTML as index.html, the payload as index.rsc.
@k8ordo/server calls it for every request that arrives, and sends back what it answers.
vite dev calls the handler per request under either mode, which is why a @k8ordo/static application behaves a little differently in development than in its build.
A page has two forms: HTML and a payload
Under either mode a page has a second form beside its HTML: an RSC payload. It lives at the page’s path with /index.rsc appended, and a client navigation fetches that.
It is told apart by its path rather than by a header or a query because a static host varies its answer on neither. A path works the same for a directory of files and for a running server.
The first page opened arrives with the payload it was rendered from written into its HTML. Hydration reads that, so the page is never fetched twice.
What @k8ordo/static refuses
A build that writes files has no request, so it refuses by name anything that needs one.
- A
'use server'module: a file cannot receive a form submission. - A
guard.ts: a file has no request to let through or to stop. - A
route.tsexporting a method other thanGET: a file answers nothing else. - A page exporting
search: a file reads the same whatever the search is.
vite dev is a running server and could accept all of these. But a form that works in development and posts into nothing in production is worse than one that never worked, so vite dev refuses them too, the moment the file is loaded.
Each refusal ends with the line this application wants @k8ordo/server. If you need any of these, move to @k8ordo/server; the rest of the code stays as it is.
The build also stops when it does not know the values of a route with parameters. It never warns and skips: a site quietly missing half its pages is worse than a build that stopped.
What @k8ordo/server refuses
A running server takes requests from anywhere, so the handler turns away the ones the application should not answer, before anything renders.
- A
POSTfrom another origin: one to anything but aroute.tswith noOriginheader, or one naming another host, gets a403, so another site’s form cannot call your actions with your visitor’s cookies. - A method other than
GET,HEADandPOST: a page answers it with a405whoseAllownames those three. - A URL outside Vite’s
base: it is not the application’s, and gets a404.
What both modes refuse
The framework’s conventions are not habits to remember but shapes that can be checked, so that where the URLs live and where the server ends and the browser begins is verified by the framework rather than by anyone’s memory. These are errors under either mode:
- A file or directory that breaks the
routes/grammar, and a route that can never render - A
paramsSchemathat validates asynchronously - A
server-onlymodule on its way into the client bundle - A Vite
basethat is not a path from the root