Skip to content

Framework Adapters

Nuxt

Nuxt 3 integration via @surfjs/web and Nitro server routes

Nuxt#

Nuxt 3 runs on Nitro, which uses Web Standard Request/Response — making it a natural fit for Surf. Use @surfjs/core in server routes and @surfjs/vue on the client.

Bash
pnpm add @surfjs/core @surfjs/vue

Server Setup

Define Your Surf Instance

Create a shared Surf instance in a server utility file:

TypeScript
// server/utils/surf.ts
import { createSurf } from '@surfjs/core'
 
export const surf = await createSurf({
name: 'My Nuxt 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 })
},
},
},
})

Nitro auto-imports from server/utils/, so surf is available in all server routes without explicit imports.

API Route Handler

Create a catch-all route that delegates to Surf's middleware:

TypeScript
// server/routes/surf/[...path].ts
import { surf } from '~/server/utils/surf'
 
const handler = surf.middleware()
 
export default defineEventHandler(async (event) => {
const { req, res } = event.node
return new Promise<void>((resolve) => {
handler(req, res, () => {
res.statusCode = 404
res.end(JSON.stringify({ error: 'Not Found' }))
resolve()
})
})
})

Discovery Endpoint

Surf agents discover your app via /.well-known/surf.json. Add a route that proxies to the Surf manifest:

TypeScript
// server/routes/.well-known/surf.json.ts
import { surf } from '~/server/utils/surf'
 
const handler = surf.middleware()
 
export default defineEventHandler(async (event) => {
const { req, res } = event.node
// Rewrite the URL so Surf's middleware recognizes this as a manifest request
req.url = '/.well-known/surf.json'
return new Promise<void>((resolve) => {
handler(req, res, () => {
res.statusCode = 404
res.end()
resolve()
})
})
})

Endpoints

With the routes above, your Nuxt 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/vue composables in your Nuxt pages and components:

SurfProvider Plugin

Create a Nuxt plugin to initialize the Surf runtime app-wide:

TypeScript
// plugins/surf.client.ts
export default defineNuxtPlugin(() => {
// @surfjs/vue's SurfProvider handles initialization —
// this plugin just ensures it's available as a client-only module.
})

Then wrap your app layout with SurfProvider:

vue
<!-- layouts/default.vue -->
<template>
<SurfProvider endpoint="https://myapp.com">
<slot />
</SurfProvider>
</template>
 
<script setup lang="ts">
import { SurfProvider } from '@surfjs/vue'
</script>

Using Commands in Pages

vue
<!-- pages/index.vue -->
<script setup lang="ts">
import { useSurf, useSurfCommands } from '@surfjs/vue'
 
const { execute } = useSurf()
 
// Register local commands for browser-based agents
useSurfCommands({
'theme.toggle': {
mode: 'local',
run: () => {
document.documentElement.classList.toggle('dark')
return { ok: true }
},
},
})
 
// Execute server commands
const results = ref<unknown>(null)
 
async function search(query: string) {
results.value = await execute('search', { query })
}
</script>

Real-Time State with Surf Live

For WebSocket-powered real-time state, add the url prop to SurfProvider:

vue
<template>
<SurfProvider
endpoint="https://myapp.com"
url="wss://myapp.com/surf/ws"
>
<slot />
</SurfProvider>
</template>

Then subscribe to live state in any component:

vue
<script setup lang="ts">
import { useSurfState } from '@surfjs/vue'
 
const metrics = useSurfState('metrics', { users: 0, events: 0 })
</script>
 
<template>
<div>Online: {{ metrics.users }}</div>
</template>

With Authentication

Pass an auth token through SurfProvider:

vue
<template>
<SurfProvider
endpoint="https://myapp.com"
:auth="userToken"
>
<slot />
</SurfProvider>
</template>
 
<script setup lang="ts">
import { SurfProvider } from '@surfjs/vue'
 
const userToken = useCookie('auth-token')
</script>

Deployment Notes

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

⚠️ Surf Live requires a persistent server process. Serverless platforms do not support long-lived WebSocket connections. Deploy to a Node.js server (Railway, Fly.io, VPS) for real-time features.