Static sites
Any directory with an index.html is deployable. Static is the default runtime on VibeHost.
Static is the default runtime on VibeHost. If you have an index.html somewhere in a directory tree, you can deploy it without a build step or a particular framework. Most apps you'll ever ship to VibeHost are static.
A minimal deploy
mkdir hello && cd hello
echo '<h1>Hello, vibe coding</h1>' > index.html
vibehost app create hello --json
vibehost link --app hello
vibehost deployThat's the whole flow. The output looks like this:
{
"ok": true,
"data": {
"id": "depl_abc...",
"url": "https://hello-acme.vibehost.space",
"immutableUrl": null,
"deployKind": "static",
"status": "healthy"
}
}How static works
- Your directory is tarballed, validated (no
..traversal, no symlinks, size caps), and uploaded as content-addressed chunks. - Repeat deploys skip blobs the server already has, so only changed files are transferred.
- Assets land in our asset storage and are served directly from the edge. A first deploy is served at once; after a redeploy the URL can keep serving the previous release for up to about a minute (52 seconds when measured). There is no cold start, because a purely static app has no app-specific runtime.
- The edge serves index.html for
/, files by path for the rest, and a 404 fallback for misses (override with--spa, see below).
Framework recipes
Anything that emits a static directory works. The output directory name varies by framework, so point vibehost deploy at whatever your build emits.
npm create vite@latest my-app
cd my-app && npm install
npm run build
vibehost app create my-app
vibehost deploy ./dist --app my-appnpm run build emits to dist/. This holds for the React, Vue, Svelte, Solid, Preact, and vanilla templates, because Vite emits to dist/ by default.
For React Router / Vue Router with browser-history mode, add --spa:
vibehost deploy ./dist --app my-app --spanpm create astro@latest my-site
cd my-site && npm install
npm run build
vibehost app create my-site
vibehost deploy ./dist --app my-sitenpm run build emits to dist/. Astro emits to dist/ by default. Server-side rendered routes (output: 'server') won't work on the static runtime, so keep output: 'static' (the default).
import adapter from '@sveltejs/adapter-static';
export default {
kit: {
adapter: adapter({
fallback: 'index.html', // for SPA-style routing
}),
},
};npm run build emits to build/:
npm run build
vibehost app create my-app
vibehost deploy ./build --app my-app --spaIf you have +page.server.ts (SSR-only) load functions, they won't run on the static adapter. Migrate to client +page.ts.
nuxi generate emits to .output/public/:
npx nuxi generate
vibehost app create my-nuxt-site
vibehost deploy .output/public --app my-nuxt-siteNuxt 3's nuxi generate (vs nuxi build) produces a pure static tree. Dynamic API routes (server/api/*) won't work on the static runtime.
import { vitePlugin as remix } from '@remix-run/dev';
export default {
plugins: [
remix({ ssr: false }), // SPA mode
],
};npm run build emits to build/client/:
npm run build
vibehost deploy ./build/client --app my-remix-app --spaRemix SPA mode emits a fully static bundle. Move server loaders to client loaders, and don't put a loader function on routes that touch the database or file system.
There is no build step, only a folder:
mkdir my-site
cat > my-site/index.html <<'EOF'
<!doctype html>
<title>Hi</title>
<h1>Hello, web</h1>
EOF
vibehost deploy ./my-site --app my-siteHugo emits to public/; Jekyll emits to _site/.
hugo
vibehost deploy ./public --app my-blog
bundle exec jekyll build
vibehost deploy ./_site --app my-blogMkDocs emits to site/; Docusaurus emits to build/; VitePress emits to .vitepress/dist/. Each group below is one generator.
mkdocs build
vibehost deploy ./site --app my-docs
npm run build
vibehost deploy ./build --app my-docs
npm run docs:build
vibehost deploy .vitepress/dist --app my-docsWhat makes the cut
The tarball must contain at least one index.html somewhere. The CLI auto-detects the document root.
Each archive has these limits. An Enterprise contract can raise them.
| Constraint | Default cap |
|---|---|
| Total archive size | 500 MB compressed |
| Per-file size | 100 MB |
| Entry count | 50,000 |
| Symlinks | Rejected (security) |
Absolute paths / .. | Rejected (security) |
| Empty archive | Rejected (no index.html) |
The 500 MB total applies to the default upload paths. vibehost deploy and the dashboard's drag and drop both upload file by file, so no single request carries the whole site. vibehost deploy --no-chunked sends one tarball request instead, which the CDN edge caps at about 100 MB. Above that, drop the flag.
If your build exceeds 50,000 entries, that's almost always a node_modules-style leak: .next/cache, source maps from third-party dependencies, or a misconfigured build directory. Check what you're shipping:
vibehost deploy ./dist --dry-run --json | jq '.data.fileCount'SPA routing (history mode)
Single-page apps need a fallback so deep links don't 404:
vibehost deploy ./dist --spaThis adds a /* → /index.html rewrite. Without it, /dashboard returns 404 because no file at that path exists.
--spa sticks to the app and applies to later deploys until you turn it off. You can toggle it in the dashboard, or check the current setting with:
vibehost app inspect my-app --json | jq '.data.spaFallback'Custom 404 / 500 pages
Add files at the conventional paths:
404.htmlat the root is served on misses, but only when--spais off.500.htmlis served on edge-level errors. These are rare and usually mean asset storage is unreachable.
These work without --spa. If you turn --spa on, index.html wins over 404.html for unknown routes.
Asset cache headers
Custom response headers are not supported; the platform decides Cache-Control. A _headers file in your deploy is ignored.
| File | Cache-Control |
|---|---|
| HTML | public, max-age=0, must-revalidate |
Path contains /_next/static/, or 8+ lowercase hex characters between two dots anywhere in the path (app.3f9a2c1b.js) | public, max-age=31536000, immutable |
Everything else, including unhashed *.js and *.css | public, max-age=86400, stale-while-revalidate=604800 |
Platform default /favicon.ico (your deploy has none) | public, max-age=3600, stale-while-revalidate=86400 |
With search indexing turned on, HTML, XML and PDF documents are served with no-store instead.
Redirects
You can add redirects with a _redirects file at build time, or as platform redirects that need no rebuild.
# _redirects (in your deploy dir)
/old-page /new-page 301
/blog/* /posts/:splat 301Platform redirects are managed from the CLI and take effect without a rebuild:
vibehost redirects add /old-page /new-page --app my-site
vibehost redirects list --app my-site
vibehost redirects remove rdr_xyz --app my-sitePlatform redirects override _redirects if there's a conflict.
What you can't do (yet)
- The static runtime has no server-side logic: no edge functions, no middleware, and no API routes.
- There are no build-time secrets.
vibehost env setexists, but its values only reach the runtime, not your build, which runs on your machine. Read secrets from your shell environment or a dotenv file during the build. - The build can't make an app private. Visibility is set per app (public, workspace, or private), and private static apps are gated at the edge.
- There is no streaming. Static files are served with a full
Content-Length, so you can't stream a response from a static deploy.
Custom domains
vibehost domain add www.example.com --app helloThen add the printed CNAME record at your DNS provider. See Custom domains for full DNS setup, including Cloudflare-proxied domains.
Next
- Channels for preview deploys
- Custom domains to bring your own hostname
- Grants and visibility to control who sees the site