Node servers
A deployment can carry a server: a Node app that runs on slew's sandboxed runner nodes behind https://<project>.slew.cloud. This page is the raw contract, shown with a plain Express app. The Astro, SvelteKit, and Remix recipes apply the same contract per framework — and Next.js needs none of this: a standalone Next build is detected and assembled automatically on git builds.
The output contract
A deployment becomes a server deployment when the directory you upload has a slew.json at its root pointing at a server entry:
slew.json the manifest, below
server/ your server bundle — the entry file plus everything it imports
static/ optional: files the router serves directly from the CDN
{ "server": "server/index.mjs", "runtime": "node22" }
| Field | Rules |
|---|---|
server |
Path of the entry file. Must start with server/ and exist in the upload. null — or no slew.json at all — means a plain static deployment. |
runtime |
Optional. node22 is the only runtime, and the default. |
Those are the only two fields; anything else in the file is ignored. A manifest that breaks the rules fails the upload with 400 invalid_archive.
How your server runs
- Started as
node <server-entry>— for the manifest above,node server/index.mjs— with the artifact root as its working directory. PORTandHOSTNAMEare injected by the platform (PORT=3000,HOSTNAME=0.0.0.0today) and always win — project env vars can't override them. Listen onprocess.env.PORTand bind0.0.0.0.NODE_ENV=productionis set as an overridable default; runtime-scoped project env vars are injected at start.- The bundle is mounted read-only; anything the process writes elsewhere on disk is ephemeral and gone after a restart.
- A new instance takes traffic only once it answers on its port (30-second limit), so a deploy that fails to boot never receives a request.
- Idle servers scale to zero; the next request wakes one (a cold start).
slew logstails the live instance.
Static files
For a server deployment, GET requests whose path has a file extension (/app-h4sh123.js, /logo.svg) are served from static/ — CDN-cached under the usual rules, your server never sees them. Everything else — extensionless paths, misses in static/, all non-GET requests — goes to your app. There is no SPA fallback: the server owns its routing and its 404s.
A minimal Express app
server.mjs:
import express from 'express'
const app = express()
app.get('/', (_req, res) => res.send('hello from slew'))
app.listen(process.env.PORT ?? 3000, '0.0.0.0')
slew deploy never packs node_modules — any path containing a node_modules component is excluded (see how deploy works) — so for CLI deploys the entry must be self-contained. Bundle it:
npm i -D esbuild
npx esbuild server.mjs --bundle --platform=node --format=esm --outfile=out/server/index.mjs
printf '{ "server": "server/index.mjs" }\n' > out/slew.json
cp -r public out/static # optional: anything the CDN should serve directly
Deploy:
slew init
slew deploy out
Git push-to-deploy
Put the assembly steps in a script (an npm script or scripts/build-slew.sh) and connect the repo with the output directory pointing at the artifact:
slew git connect owner/repo --build-cmd "npm run build:slew" --output out
Every push then builds the artifact in an isolated container and deploys it. Unlike the CLI, git builds tar the output directory as-is — a node_modules directory inside the artifact does ship, which is how the framework recipes carry dependencies that can't be bundled. Keep it pruned (npm ci --omit=dev): the usual deploy limits apply, including the 10,000-file cap.