Skip to content

Each source folder builds independently.

sh
pnpm build          # all source folders
pnpm build front    # specific folder

One Entry Point for the Whole Project

The build writes dist/run.js - a dispatcher over every source folder in the project. Start it and the whole application is up:

sh
node dist/run.js -p 4556

It routes incoming requests by path the same way the dev server does: each folder's base and apiBase decide what it owns, longest prefix first. A project with a marketing site at /, an admin app at /admin and an API-only folder at /svc is one process, one port, no reverse proxy needed to glue the folders together.

Each folder is served as what it is. SSR folders render on the server; CSR folders are served as static assets with a fallback to their index.html; API-only folders answer on their apiBase.

You do not configure any of this - it follows from the generators the folder is built with.

dist/run.js is also what kosmo preview runs, so what you check locally is the same entry point production serve.

Other Runtimes

dist/run.js is built on node:http, which Bun and Deno both implement, so the same file runs unchanged on all three:

sh
node dist/run.js -p 4556

This is worth stressing because it is easy to assume otherwise: nothing in the dispatcher is tied to a runtime-specific server API.

It is node:http all the way down, and the compatibility layers in Bun and Deno cover it.

Running Folders Separately

dist/run.js is the simple path, not the only one.

Deploy folders separately when they have separate lifecycles - a marketing site on a CDN and an API in a container, an admin app scaled independently of the public one, or a folder that belongs in a different region entirely.

The per-folder servers below are what dist/run.js dispatches to internally, so nothing changes about how a folder behaves when you run it on its own.

Build Output

txt
dist/
├── run.js               # dispatcher over every folder
└── front
    ├── api
    │   ├── app.js       # app instance
    │   └── server.js    # ready-to-use API server
    ├── client
    │   ├── assets/      # scripts, styles, images
    │   └── index.html
    └── ssr
        ├── app.js       # SSR app instance
        └── server.js    # SSR server bundle

The SSR output is only present when SSR is enabled.

What to Deploy

When running folders separately, what you deploy depends on how the folder renders. (With dist/run.js this is all handled for you - the section below matters when you split folders across hosts.)

SSR folders - deploy the SSR server only.

The build bundles the folder's backend into the SSR server: one process serves the client assets, renders pages, and answers API requests.

During server-side rendering, the isomorphic fetch client calls your route handlers in-process - no network hop, no localhost round-trip.

After hydration, the same server answers the browser's API calls over HTTP through a built-in gateway that routes everything under the folder's apiBase to the bundled backend.

sh
node dist/front/ssr/server.js -p 4556   # pages + API, one process

There is nothing else to run - dist/front/api/server.js exists in the build output, but deploying it alongside the SSR server would just duplicate the API.

CSR folders - deploy the API server, serve the client statically.

The browser is the only consumer of your API here, and it reaches it over the network: run dist/front/api/server.js and put dist/front/client/ behind any static host or CDN.

API-only folders (no frontend)

Deploy the API server, same as CSR minus the static assets.

Running the API server

For CSR and API-only setups, the simplest deployment is running the bundled server directly:

sh
node dist/front/api/server.js -p 4556

For more control, use the app factory at dist/*/api/app.js.

Hono / H3 - app.fetch is a Web Fetch API handler, so it plugs into each runtime's native server directly:

js
import { createServer } from "node:http";
import { getRequestListener } from "@hono/node-server";

import app from "./dist/front/api/app.js";

createServer(getRequestListener(app.fetch)).listen(3000);

Koa - app.callback() is a Node.js (IncomingMessage, ServerResponse) handler. Deno and Bun support it via their node:http compat layer, not via their native serve APIs:

Node / Bun / Deno
js
import { createServer } from "node:http";

import app from "./dist/front/api/app.js";

createServer(app.callback()).listen(3000);

Or use app.listen() directly for Node only support:

Node
js
import app from "./dist/front/api/app.js";

app.listen(3000);

Released under the MIT License.