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
mode: 'static'
vite build → handler(/products/1) → index.html
→ handler(/products/1/index.rsc) → index.rsc
mode: 'server'
request → handler(request) → ResponseUnder 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
/products/1 → HTML
/products/1/index.rsc → RSC payloadUnder 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 buildstatic 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
POSTfrom another origin: aPOSTto a page or a Server Action gets a403when it has noOriginheader, 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,HEADandPOST: a request to a page gets a405whoseAllowlists those three. - A URL outside Vite’s
base: it is not one of the application’s, and gets a404.
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
paramsSchemathat validates asynchronously (a params schema must validate synchronously) - A
server-onlymodule imported from a Client Component - A Vite
basethat does not start from the root (./, for example)