slew docs/deploy/serving

How sites are served

Every deploy is an immutable tree of files, served on https://<project>.slew.cloud (and any custom domains you attach). This page covers what the router does with a request so you can predict how your site behaves.

URLs and subdomains

Your project name is your subdomain: project my-site serves at my-site.slew.cloud. One label only — nested subdomains like a.b.slew.cloud don't resolve to a project. Temporary shares serve at their tmp-*.slew.cloud URL until they expire, at which point they stop resolving immediately.

Path resolution and SPA fallback

Requests map onto the files you uploaded, with sensible fallbacks:

Request path Files tried, in order
/ index.html
/blog/ blog/index.html, then index.html
/blog/post (no extension) blog/post, then blog/post/index.html, then index.html
/app.css (has extension) app.css only

The final index.html fallback for extensionless paths means client-side routed SPAs work out of the box — deep links land on your app shell. Paths with extensions never fall back, so a missing asset is a real 404 rather than your index.html served as CSS.

Percent-encoded paths are decoded; anything containing .. segments, backslashes, NUL bytes, or invalid encoding is rejected.

Custom 404 pages

If your deployment contains a 404.html at its root, it is served (with status 404) whenever no file matches. Without one, visitors get slew's minimal branded 404.

Caching

Responses fall into two classes:

Asset class Cache-Control
Content-hashed build artifacts — Vite/webpack-style name-h4sh123.ext filenames (never .html) public, max-age=31536000, immutable
Everything else, notably HTML public, max-age=30, stale-while-revalidate=60

Hashed artifacts are immutable by construction (new content means a new URL), so they cache for a year. HTML stays on a 30-second TTL, and slew additionally purges the CDN edge when you deploy or roll back — so a deploy or rollback is visible worldwide within seconds, without you managing any cache invalidation.

404 responses are never cached.

Content types and headers

Content types are derived from file extensions — HTML, CSS, JS, JSON, XML and text (all charset=utf-8), common image formats (svg, png, jpg, gif, webp, avif, ico), fonts (woff, woff2, ttf, otf), plus pdf, wasm, mp4, webm, mp3, and source maps. Unknown extensions are served as application/octet-stream. Every file response carries X-Content-Type-Options: nosniff and Referrer-Policy: strict-origin-when-cross-origin.

Rollback semantics

Deployments are never mutated: each upload gets its own file tree, and "live" is a pointer to one of them. slew rollback <deployment-id> (or the dashboard's "Roll back to this", or POST …/activate) flips that pointer and purges cached HTML. Any deployment that ever went live can be re-activated.

Old deployments you no longer need can be deleted — slew deployments rm <id>, the dashboard's "Delete", or DELETE /projects/:name/deployments/:id. The live deployment is protected; deleting anything else permanently removes its files, so it can no longer be rolled back to.

Custom domains

Attach a domain with POST /projects/:name/domains (see the HTTP API reference). You get back a dns_target; CNAME your domain to it and TLS is provisioned for free once DNS resolves. A domain can be attached to only one project platform-wide. Re-adding the same domain is safe and retries the TLS setup — useful right after flipping DNS.

To see the certificate a domain is actually serving — issuer, expiry, whether it's valid — run slew domains cert <domain> or use the dashboard's "Check certificate". If issuance is still pending (or you want to re-trigger it), the dashboard's "Renew certificate" (or POST …/certificate/refresh) asks the CDN to issue again.

Limits

Limit Value
Upload (compressed tarball) 100 MB
Files per deployment 10,000
Single file 25 MB
Total uncompressed 250 MB

Symlinks and directories in an archive are skipped — only regular files are served.