slew docs/runtimes/astro

Deploying Astro

A static Astro site is a plain deploy; SSR runs on slew's Node servers via @astrojs/node.

Static (the default)

output: 'static' builds plain files into dist/ — nothing platform-specific:

npm run build        # astro build
slew init
slew deploy

slew deploy recognizes a static Astro project and deploys dist/ by itself. Or connect the repo with the default recipe (install npm ci, build npm run build, output dist). That's it — the rest of this page is SSR only.

SSR with @astrojs/node

npx astro add node
// astro.config.mjs
import { defineConfig } from 'astro/config'
import node from '@astrojs/node'

export default defineConfig({
  output: 'server',
  adapter: node({ mode: 'standalone' }),
})

astro build now produces dist/server/entry.mjs (a standalone server) and dist/client/ (assets).

Push to GitHub — detected automatically

Connect the repo and every push deploys. The builder recognizes an Astro build with the node adapter — the dependencies plus dist/server/entry.mjs — and assembles the server artifact for you: entry point, manifest, a production node_modules from your lockfile (npm ci --omit=dev, so commit a package-lock.json), and dist/client/ served straight from the CDN. Leave the build settings at their defaults:

slew git connect owner/repo

No env vars needed: Astro's standalone server binds its host from HOST (not the platform-injected HOSTNAME) and defaults to localhost, but the generated launcher pins HOST=0.0.0.0 before the server loads. Keep production dependencies lean — the deploy caps at 10,000 files, node_modules included.

The manual artifact (fallback and reference)

An explicit slew.json in the output directory always wins over detection, so you can assemble the artifact yourself. Two platform facts shape it:

  • The standalone server reads PORT from the environment — the platform injects it. But it binds the host from HOST, which defaults to localhost and is unreachable inside the runner container. For a manual artifact, set it once (slew env set HOST=0.0.0.0) or front the entry with a launcher that sets it.
  • Astro leaves npm dependencies external in the server build, so dist/server needs node_modules at runtime — and slew deploy never packs node_modules, so this artifact ships via git push-to-deploy.

The artifact keeps client/ next to server/, where the standalone server expects it:

slew.json              { "server": "server/server/entry.mjs" }
server/                = dist (server/ + client/)
server/node_modules/   production dependencies
static/                = dist/client — served directly by the CDN

Assembly (scripts/build-slew.sh or an npm script):

astro build
npm ci --omit=dev
rm -rf out && mkdir out
cp -r dist out/server
cp -r node_modules out/server/node_modules
cp -r dist/client out/static
printf '{ "server": "server/server/entry.mjs" }\n' > out/slew.json

Connect and push:

slew env set HOST=0.0.0.0
slew git connect owner/repo --build-cmd "bash scripts/build-slew.sh" --output out

Either way, requests for /_astro/* and other asset paths are served from static/ by the CDN, everything else hits the Astro server.