Use this file to discover all available pages before exploring further.
Vertz apps can be deployed as fully static sites — no server required. The build script SSR-renders your pages at build time, bundles client JS for interactivity, and outputs a dist/ directory you can deploy anywhere. This guide uses Cloudflare Workers, but the output works with any static host.
For local development, use the standard Vertz dev server:The vtz runtime includes a built-in dev server with HMR, SSR, and the Vertz compiler — no additional configuration required. Just run:
Injects production head tags (meta, OG, fonts, client bundle reference)
// scripts/build.tsimport { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync,} from 'node:fs';import { resolve } from 'node:path';import { execSync, spawn } from 'node:child_process';const ROOT = resolve(import.meta.dir, '..');const DIST = resolve(ROOT, 'dist');const PORT = 4100;// ── 1. Build client JS bundle ──────────────────────────────rmSync(DIST, { recursive: true, force: true });mkdirSync(resolve(DIST, 'assets'), { recursive: true });// Use `vtz build` for the client bundle in production.// The build output goes to dist/assets/ with hashed filenames.execSync('vtz build', { cwd: ROOT, stdio: 'inherit' });// ── 2. Copy public/ → dist/public/ ────────────────────────const publicDir = resolve(ROOT, 'public');if (existsSync(publicDir)) { cpSync(publicDir, resolve(DIST, 'public'), { recursive: true });}// ── 3. SSR-render the page ─────────────────────────────────const server = spawn('vtz', ['dev', '--port', String(PORT)], { cwd: ROOT, env: { ...process.env, PORT: String(PORT) }, stdio: ['ignore', 'pipe', 'pipe'],});// Wait for serverfor (let i = 0; i < 60; i++) { try { if ((await fetch(`http://localhost:${PORT}`)).ok) break; } catch {} await new Promise((r) => setTimeout(r, 500));}const html = await fetch(`http://localhost:${PORT}`).then((r) => r.text());server.kill();// ── 4. Strip dev-only scripts ──────────────────────────────let clean = html .replace(/<style>[\s\S]*?<\/style>/g, (match) => (match.includes('hmr') ? '' : match)) .replace(/<script[^>]*data-dev-server[^>]*>[\s\S]*?<\/script>/g, '') .replace(/<script type="module" src="\/src\/entry-client\.ts"><\/script>/g, '');// ── 5. Inject production head + client bundle ──────────────// Find the built client JS in dist/assets/const assets = existsSync(resolve(DIST, 'assets')) ? readdirSync(resolve(DIST, 'assets')) : [];const clientJs = assets.find((f) => f.startsWith('entry-client') && f.endsWith('.js'));const clientCss = assets.filter((f) => f.endsWith('.css'));if (clientJs) { clean = clean.replace( /<\/body>/, ` <script type="module" crossorigin src="/assets/${clientJs}"></script>\n</body>`, );}const cssLinks = clientCss.map((f) => ` <link rel="stylesheet" href="/assets/${f}" />`).join('\n');if (cssLinks) { clean = clean.replace(/(<title>[^<]*<\/title>)/, `$1\n${cssLinks}`);}writeFileSync(resolve(DIST, 'index.html'), clean);console.log('✓ Build complete: dist/');
The build script starts the dev server temporarily to SSR-render the page. This ensures the
production HTML matches exactly what you see in development — same components, same data, same
CSS. The dev server is killed immediately after the fetch.
The not_found_handling = "single-page-application" setting returns index.html for any path that doesn’t match a file — this lets client-side routing work.
# Build and deploy in one commandvtz run deploy# Or step by stepvtz run build # Produces dist/vtzx wrangler deploy # Uploads dist/ to Cloudflare
The output is a clean dist/ directory:
dist/├── index.html # SSR-rendered HTML + meta tags├── assets/│ ├── entry-client-[hash].js # Client bundle (minified, hashed)│ └── vertz.css # Extracted component CSS└── public/ ├── logo.svg # Your static assets └── og.png # OG image
The dev server’s SSR output includes HMR scripts, WebSocket overlays, and dev server bootstrapping
code. The build script strips all of these — never deploy the raw dev server output directly.