@k8ordo/framework

Choosing a mode

The mode of framework() decides whether the application runs without a server or with one. The choice comes down to whether the application needs the request.

On this page

Choosing

Pick mode: 'server' if any of these applies.

  • Receiving a form submission in a Server Action
  • Stopping a request in a guard.ts before the page renders
  • Reading the request’s headers or cookies, or writing cookies with cookies()
  • Exporting search from a page to receive the query
  • Rendering pages on the server for values that cannot be listed ahead of time, such as the IDs of posts users create
  • Responding to a method other than GET from a route.ts

If none of them applies, pick mode: 'static'. Pages are rendered at build time, so the site goes on a static host with no server. When the content changes, such as a new post, you build again. It suits a site whose content is the same for every visitor, such as documentation or a blog.

Static mode can still answer values that cannot be listed ahead of time. With a fallback.tsx beside a page.tsx, a value the build did not write has its body rendered in the browser. A page that does not need its body in the first HTML can stay in static mode. How to write one is covered in Parameters.

When in doubt, start with mode: 'static'. Anything you write later that needs the request fails the build or the type check.

Mode differences

  • Build output: files in dist/client/ in static mode, a request handler in server mode.
  • A route with parameters: in static mode its values are listed in paths, and a fallback.tsx answers the rest with a shell rendered in the browser. In server mode they arrive with the request, so new values need no rebuild.
  • A URL no route matches: in static mode the host serves 404.html, in server mode the application responds with a 404 status.
  • redirect.ts: in static mode it is written as a page that sends the visitor on, in server mode it responds with 307 or 308.
  • route.ts: in static mode the response of its GET is written as a file, in server mode it responds to every method it exports.
  • Content-Security-Policy: in static mode the csp option writes it into a <meta>, in server mode a guard.ts writes it as a header.
  • A page’s request: not in the types in static mode, and reading it is a type error. Read without a type check, it fails the build. In server mode it holds the headers and cookies.

Switching

vite.config.ts
import { framework } from '@k8ordo/framework/vite';
import { defineConfig } from 'vite';

export default defineConfig({
  plugins: [framework({ mode: 'server' })],
});

The files under src/routes/ and the imports from @k8ordo/framework work as they are in either mode. A fallback.tsx is simply unused under server mode, so there is no need to remove it.

paths, site and csp are options of static mode only. Written beside mode: 'server' they are a type error, and framework() fails when the config loads, so drop them when moving to server mode. Running a server mode build is covered in Deployment.

When moving to static mode, list the values of each route with parameters in paths, or give the page a fallback.tsx. Remove the refused code below too, and any read of a page’s request.

Refused code

vite build
static build cannot answer a request — a file is written once for every visitor, and these import @k8ordo/framework/server:
  src/lib/session.ts
this application wants mode: 'server'

A static mode build fails on the following code, which responds to a request. It lists the files it finds, one list per kind. A guard.ts, search and a route.ts's methods are checked before compiling; imports of @k8ordo/framework/server and 'use server' after it. Fixing the first can reveal the second on the next build.

  • A module importing a value from @k8ordo/framework/server
  • A 'use server' module
  • A guard.ts
  • A page exporting search
  • A route.ts exporting a method other than GET

Each error ends with the line this application wants mode: 'server'. vite dev reports the same error the moment it compiles the module. import type is erased before the build sees it, so it passes. A type such as RedirectTarget can be imported from @k8ordo/framework/server in static mode too. import { type RedirectTarget } is refused, though: under verbatimModuleSyntax it stays behind as an import of the module.

The build checks only the application’s own modules. A dependency in node_modules that imports @k8ordo/framework/server is not named; calling cookies() or the like from it fails instead. A route.ts or page that re-exports another module with export * fails too, since the build cannot read the names it exports.

k8ordo

React libraries that use Baseline features without holding back.

© 2026 k8o — MIT License

Typeset in Noto Sans JP & M PLUS 2