Skip to content

Framework Adapters

SvelteKit

SvelteKit integration via server hooks and @surfjs/svelte

SvelteKit#

SvelteKit uses Vite and exposes server hooks and +server.ts route handlers — both work well with Surf. Use @surfjs/core on the server and @surfjs/svelte on the client.

Bash
pnpm add @surfjs/core @surfjs/svelte

Server Setup

Define Your Surf Instance

Create a shared Surf instance in a server module:

TypeScript
// src/lib/server/surf.ts
import { createSurf } from '@surfjs/core'
 
export const surf = await createSurf({
name: 'My SvelteKit App',
commands: {
search: {
description: 'Search products',
params: {
query: { type: 'string', required: true },
limit: { type: 'number', default: 10 },
},
run: async ({ query, limit }) => {
return db.products.search(query, { limit })
},
},
},
})

Catch-All Route Handler

Create a catch-all +server.ts that delegates to Surf's middleware:

TypeScript
// src/routes/surf/[...path]/+server.ts
import { surf } from '$lib/server/surf'
import type { RequestHandler } from './$types'
 
const handler = surf.middleware()
 
function handleSurf(event: Parameters<RequestHandler>[0]): Promise<Response> {
return new Promise((resolve) => {
const { request } = event
 
// Build a minimal Node-like req/res pair from the web Request
const url = new URL(request.url)
 
const req = {
method: request.method,
url: url.pathname + url.search,
headers: Object.fromEntries(request.headers.entries()),
on: (_event: string, cb: (chunk: Buffer) => void) => {
request.text().then((body) => {
if (body) cb(Buffer.from(body))
})
},
}
 
const chunks: Buffer[] = []
const resHeaders: Record<string, string> = {}
let statusCode = 200
 
const res = {
statusCode,
setHeader(name: string, value: string) { resHeaders[name] = value },
getHeader(name: string) { return resHeaders[name] },
writeHead(code: number, headers?: Record<string, string>) {
statusCode = code
if (headers) Object.assign(resHeaders, headers)
},
write(chunk: string | Buffer) { chunks.push(Buffer.from(chunk)) },
end(chunk?: string | Buffer) {
if (chunk) chunks.push(Buffer.from(chunk))
resolve(new Response(Buffer.concat(chunks), {
status: statusCode,
headers: resHeaders,
}))
},
}
 
handler(req as never, res as never, () => {
resolve(new Response(JSON.stringify({ error: 'Not Found' }), {
status: 404,
headers: { 'Content-Type': 'application/json' },
}))
})
 
// Feed the request body
request.text().then((body) => {
if (body) {
req.on('data', () => {})
}
})
})
}
 
export const GET: RequestHandler = (event) => handleSurf(event)
export const POST: RequestHandler = (event) => handleSurf(event)

💡 Tip: For a cleaner approach, use the Hono adapter with SvelteKit's @sveltejs/adapter-node — Hono uses Web Standard Request/Response natively, avoiding the Node.js compatibility shim above.

Discovery Endpoint

Add the /.well-known/surf.json route for agent discovery:

TypeScript
// src/routes/.well-known/surf.json/+server.ts
import { surf } from '$lib/server/surf'
import type { RequestHandler } from './$types'
 
export const GET: RequestHandler = async () => {
const manifest = surf.manifest()
return new Response(JSON.stringify(manifest), {
headers: { 'Content-Type': 'application/json' },
})
}

Endpoints

With the routes above, your SvelteKit app exposes:

| Method | Path | Description | |--------|------|-------------| | GET | /.well-known/surf.json | Manifest (discovery) | | POST | /surf/execute | Command execution | | POST | /surf/pipeline | Pipeline execution | | POST | /surf/session/start | Start session | | POST | /surf/session/end | End session |

Client-Side Integration

Use @surfjs/svelte stores in your Svelte components.

Provider Setup

Initialize the Surf context in your root layout:

svelte
<!-- src/routes/+layout.svelte -->
<script>
import { createSurfProvider, setSurfContext } from '@surfjs/svelte'
 
const provider = createSurfProvider({
endpoint: 'https://myapp.com',
})
 
setSurfContext(provider)
</script>
 
<slot />

Registering Local Commands

svelte
<!-- src/routes/+page.svelte -->
<script>
import { surfCommands, surfExecute } from '@surfjs/svelte'
 
// Register local commands for browser-based agents
surfCommands({
'theme.toggle': {
mode: 'local',
run: () => {
document.documentElement.classList.toggle('dark')
return { ok: true }
},
},
})
 
// Execute server commands
let results = $state(null)
 
async function search(query) {
results = await surfExecute('search', { query })
}
</script>

Real-Time State with Surf Live

Add WebSocket support to the provider:

svelte
<script>
import { createSurfProvider, setSurfContext } from '@surfjs/svelte'
 
const provider = createSurfProvider({
endpoint: 'https://myapp.com',
url: 'wss://myapp.com/surf/ws',
})
 
setSurfContext(provider)
</script>

Then subscribe to live state:

svelte
<script>
import { surfState } from '@surfjs/svelte'
 
const metrics = surfState('metrics', { users: 0, events: 0 })
</script>
 
<div>Online: {$metrics.users}</div>

With Authentication

Pass auth tokens through the provider:

svelte
<script>
import { createSurfProvider, setSurfContext } from '@surfjs/svelte'
import { page } from '$app/stores'
 
const provider = createSurfProvider({
endpoint: 'https://myapp.com',
auth: $page.data.token,
})
 
setSurfContext(provider)
</script>

Alternative: Hono on SvelteKit

For a more ergonomic server-side setup, use the Hono adapter with SvelteKit. Hono uses Web Standard APIs natively, eliminating the Node.js compatibility layer:

TypeScript
// src/hooks.server.ts
import { Hono } from 'hono'
import { honoApp } from '@surfjs/core/hono'
import { surf } from '$lib/server/surf'
 
const app = new Hono()
app.route('/', await honoApp(surf))
 
export const handle = async ({ event, resolve }) => {
const response = await app.fetch(event.request)
if (response.status !== 404) return response
return resolve(event)
}

Deployment Notes

| Feature | Vercel / Cloudflare | Node.js Server | |---------|--------------------|--------------------| | Manifest + Discovery | ✅ | ✅ | | Command execution | ✅ | ✅ | | Pipelines + Sessions | ✅ | ✅ | | SSE Streaming | ✅ | ✅ | | WebSocket + Surf Live | ❌ | ✅ |

⚠️ Surf Live requires a persistent server process. Use @sveltejs/adapter-node and deploy to Railway, Fly.io, or a VPS for WebSocket support.