VibeHost
Guides

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 deploy

That'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-app

npm 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 --spa
npm create astro@latest my-site
cd my-site && npm install
npm run build
vibehost app create my-site
vibehost deploy ./dist --app my-site

npm 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).

svelte.config.js
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 --spa

If 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-site

Nuxt 3's nuxi generate (vs nuxi build) produces a pure static tree. Dynamic API routes (server/api/*) won't work on the static runtime.

vite.config.ts
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 --spa

Remix 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-site

Hugo emits to public/; Jekyll emits to _site/.

hugo
vibehost deploy ./public --app my-blog

bundle exec jekyll build
vibehost deploy ./_site --app my-blog

MkDocs 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-docs

What 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.

ConstraintDefault cap
Total archive size500 MB compressed
Per-file size100 MB
Entry count50,000
SymlinksRejected (security)
Absolute paths / ..Rejected (security)
Empty archiveRejected (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 --spa

This 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.html at the root is served on misses, but only when --spa is off.
  • 500.html is 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.

FileCache-Control
HTMLpublic, 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 *.csspublic, 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 301

Platform 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-site

Platform 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 set exists, 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 hello

Then add the printed CNAME record at your DNS provider. See Custom domains for full DNS setup, including Cloudflare-proxied domains.

Next

On this page