GioJSdocs
On this page

Standalone Deploys

One folder, one command. Build a self-contained deploy directory, copy it to any server that has Node installed, and run node run.mjs. No node_modules, no npm install, no toolchain on the host.

A normal GioJS deploy runs your app from source: the server compiles and scans routes at startup, which means Node, your dependencies, and npm install all live on the production host. A standalone build moves all of that to build time. It packages the Rust server binary and your entire Node side - React included - into a single directory that runs on a bare server. tsx and esbuild do their work during the build and are never loaded at runtime.

Build

bash
gio build standalone [--out <dir>] [--target <platform>]

  --out <dir>         output directory (default: ./standalone)
  --target <platform> cross-build for another platform (see below)

(Plain gio build just prints an explanation - normal deploys have no build step; the server renders on demand. standalone is the packaging mode.)

What you get

text
standalone/
  server(.exe)     the Rust HTTP server binary for the target platform
  worker.js        the entire Node side bundled to one file (React included)
  run.mjs          launcher: spawns the server wired to worker.js and static/
  static/          prebuilt hydration chunks and route stylesheets
  public/          your public assets (if any)
  gio.toml         your server config (if any)
  .gio/            manifest (deployment ID input) and generated route types

worker.js is generated from your discovered app modules - every page, layout, route.ts handler, gio.config, and middleware file is bundled, so boot performs no filesystem discovery and no TypeScript transform. Modules are evaluated when gio evaluates them from source: gio.config, middleware and route.ts files at startup, pages and layouts on first use. A module that throws while it is imported (a missing GIO_SESSION_SECRET, a required variable check) makes the URLs that import it answer 500, with the file and the error in the log - the server still starts. The hydration chunks in static/ are built ahead of time too, and so are the route stylesheets: CSS imports and CSS Modules work exactly as with gio, with each CSS Module's class names compiled into worker.js. The build minifies them unless the project's gio.toml says [css] minify = false; changing that key in the deployed gio.toml needs a rebuild.

Deploy

Copy the folder to any server with Node 20+ installed, then:

bash
node run.mjs

run.mjs spawns the server binary with the environment wired up (NODE_ENV=production by default, worker and static paths pointed into the folder), forwards SIGINT/SIGTERM for clean shutdown, and passes any extra arguments through to the server (which takes only --check-config; any other argument exits with 2). If run.mjs itself is killed outright (SIGKILL, the OOM killer), the server still shuts down instead of lingering on the port: it is started with a stdin pipe the launcher holds open, and exits gracefully when that pipe closes (see process supervision).

Environment variables

The build loads the project's production .env files, and GIO_PUBLIC_* values are frozen into the hydration chunks and worker.js at build time - changing one needs a rebuild. Nothing else is baked in and no .env file is copied into the output: server-side variables are read at runtime from the environment, or from .env files you place inside the deploy folder (the server loads them at startup with the usual precedence; real environment variables win).

As a systemd service, the unit needs ExecStart plus a stop policy: systemd's default sends SIGTERM to every process at once, which stops the workers in the middle of requests the server is still draining. KillMode=mixed signals only the launcher (the server then drains and stops its workers itself); the full unit is in Deploying.

ini
[Service]
ExecStart=node /srv/app/run.mjs
Restart=always
Environment=NODE_ENV=production
# SIGTERM to the launcher only: the server drains requests, then stops its workers.
KillMode=mixed
TimeoutStopSec=30

Cross-building for another platform

By default the build packages the server binary for the machine you build on. To build on one platform and deploy to another (say, build on Windows or macOS, deploy to a Linux VPS), pass --target:

bash
npm i @gio.js/server-linux-x64 --force   # install the target's binary package
gio build standalone --target linux-x64

Targets: linux-x64, linux-x64-musl, linux-arm64, win32-x64, darwin-x64, darwin-arm64. The platform package must be installed - if it isn't, the build fails with the exact npm i command to run.

@gio.js/server-linux-arm64 is not published yet. For an arm64 Linux host, build the server from a checkout of the repository (cargo build --release -p giojs-server, on the target platform or with a cross toolchain) and point the build at it - GIO_STANDALONE_SERVER_BIN takes precedence over --target:

bash
GIO_STANDALONE_SERVER_BIN=/path/to/giojs-server gio build standalone

When to prefer a normal deploy

A standalone folder is frozen at build time: framework fixes only reach it when you rebuild and re-copy. A normal deploy (npm install on the host, run gio) keeps you on the update path - npm update picks up new GioJS releases - and needs no build step at all. Prefer standalone when the target host should stay minimal (only Node, no npm registry access, no node_modules); prefer a normal deploy when you want the easiest upgrades. See Deploying for the normal path.