# SvelteKit on onvibe

Three approaches are available depending on whether you need SSR and how you want to manage the build output.

> **Important — onvibe has NO install or build phase.** It does not run `npm install` or
> `npm run build` for you; it just serves the files you deploy. So every `npm …` command below
> runs in **your own local environment** (a machine with Node/npm). You build there, then upload
> only the **build output** with `stage_files` + `deploy`. Runtime npm dependencies still work via
> Deno's `npm:` specifier (e.g. `import { Pool } from "npm:pg"`) — that needs no install step. If
> your assistant has no local shell to run the build (many MCP clients don't), prefer a plain
> Deno/TypeScript app or the `web`/`api` templates instead of a SvelteKit build.

---

## Option A — SSR with @deno/svelte-adapter (recommended for SSR)

Real server-side rendering on every request.

### 1. Install and configure

```bash
npm install -D @deno/svelte-adapter
```

```javascript
// svelte.config.js
import adapter from "@deno/svelte-adapter";
export default { kit: { adapter: adapter({ out: "output" }) } };
```

```bash
npm run build
# Outputs to: output/server/ and output/client/
```

### 2. Write main.ts wrapper

The adapter's output cannot be used directly — write a main.ts that imports the server bundle and exports a default handler:

```typescript
import { Server } from "./output/server/index.js";
import { manifest } from "./output/server/manifest.js";

const mimeTypes: Record<string, string> = {
  ".js": "application/javascript",
  ".css": "text/css",
  ".html": "text/html; charset=utf-8",
  ".png": "image/png",
  ".jpg": "image/jpeg",
  ".svg": "image/svg+xml",
  ".ico": "image/x-icon",
  ".woff2": "font/woff2",
  ".json": "application/json",
};

function mime(path: string): string {
  const ext = path.slice(path.lastIndexOf("."));
  return mimeTypes[ext] ?? "application/octet-stream";
}

const server = new Server(manifest);
await server.init({ env: Deno.env.toObject() });

export default async function handler(req: Request): Promise<Response> {
  const url = new URL(req.url);

  // Serve static assets from disk (output/client/)
  try {
    const file = await Deno.open(`./output/client${url.pathname}`);
    const stat = await file.stat();
    if (!stat.isDirectory) {
      const immutable = url.pathname.startsWith("/_app/immutable/");
      return new Response(file.readable, {
        headers: {
          "content-type": mime(url.pathname),
          "cache-control": immutable
            ? "public, max-age=31536000, immutable"
            : "no-cache",
        },
      });
    }
    file.close();
  } catch {
    // Not a static file — fall through to SSR
  }

  return server.respond(req, {
    getClientAddress: () => req.headers.get("x-forwarded-for") ?? "",
    platform: {},
  });
}
```

### 3. Deploy in chunks

The build output typically has 30–50 files. Use stage_files:

```
stage_files("my-app", [main.ts, output/server/index.js, ...chunk1])
stage_files("my-app", [output/client/_app/..., ...chunk2])
stage_files("my-app", [...remaining files])
deploy("my-app")
```

See onvibe://docs/large-deploy for the full chunked deploy workflow.

---

## Option B — adapter-static (pre-rendered, no SSR)

Use when the app has no dynamic server-side logic. Simpler: the entire build fits in a single main.ts with pages and assets embedded as strings.

```javascript
// svelte.config.js
import adapter from "@sveltejs/adapter-static";
export default { kit: { adapter: adapter() } };
```

```typescript
// main.ts — embed build output as strings
const pages = new Map([
  ["/", "<!DOCTYPE html>..."],
  ["/about", "<!DOCTYPE html>..."],
]);
const assets = new Map([
  ["/_app/immutable/entry/start.js", "// bundled js"],
]);

export default async function handler(req: Request): Promise<Response> {
  const path = new URL(req.url).pathname;
  const asset = assets.get(path);
  if (asset) return new Response(asset, { headers: { "content-type": "application/javascript", "cache-control": "public, max-age=31536000, immutable" } });
  const page = pages.get(path) ?? pages.get("/");
  return new Response(page ?? "Not found", { status: page ? 200 : 404, headers: { "content-type": "text/html; charset=utf-8" } });
}
```

---

## Option C — adapter-cloudflare (CF Workers export shape)

Use when you want to target the CF Workers runtime format. The platform runs the CF Workers module natively and provides an ASSETS shim for serving static files from disk.

### 1. Install and configure

```bash
npm install -D @sveltejs/adapter-cloudflare
```

```javascript
// svelte.config.js
import adapter from "@sveltejs/adapter-cloudflare";
export default { kit: { adapter: adapter() } };
```

```bash
npm run build
# Outputs to: .svelte-kit/cloudflare/
# Main bundle: _worker.js
# Static assets: _app/, favicon.png, etc.
```

### 2. Write main.ts wrapper

Re-export the worker module as the default export:

```typescript
// main.ts
export { default } from "./_worker.js";
```

The platform detects the CF Workers module shape (export default { fetch }) and provides:
- env.ASSETS — serves static files from the deployed file tree

### 3. Deploy in chunks

```
stage_files("my-app", [main.ts, _worker.js])
stage_files("my-app", [_app/immutable/..., favicon.png, ...])
deploy("my-app")
```

### Notes

- API routes (+server.ts) work if your SvelteKit build includes them in _worker.js
- Database connections: use Deno.env.get("DATABASE_URL") — it is injected automatically and accessible from within _worker.js via the standard env object
- SSR: supported if your SvelteKit config enables it (no "export const prerender = true" on all pages)

---

## Which to choose

| | Option A (@deno/svelte-adapter) | Option B (adapter-static) | Option C (adapter-cloudflare) |
|---|---|---|---|
| SSR per request | ✅ | ❌ | ✅ |
| API routes (+server.ts) | ✅ | ❌ | ✅ |
| $state / reactivity | ✅ | ✅ (client only) | ✅ |
| File count | 30–50 files | 1 file | 10–30 files |
| Deployment | stage_files + deploy | deploy directly | stage_files + deploy |
| Build target | Deno-native | static HTML | CF Workers |
