Nuxt
Use Silgi with Nuxt 4 — modular server routes, typed client composable, analytics, and WebSocket support.
Nuxt uses Nitro under the hood. Silgi integrates via the serverEntry config, giving you a modular RPC layer alongside Nuxt's file-system routing, auto-imports, and Vue pages.
Project structure
nuxt.config.ts
server.ts
instance.ts
router.ts
guards.ts
schema.ts
index.ts
Setup
1. Silgi instance
import { silgi } from 'silgi'
import { todos } from './todos/schema'
export const s = silgi({
context: () => ({ todos }),
})2. Procedures
import { z } from 'zod'
export const TodoSchema = z.object({
id: z.number(),
title: z.string(),
completed: z.boolean(),
createdAt: z.string(),
})
export const CreateTodoSchema = z.object({ title: z.string().min(1).max(200) })
export const TodoIdSchema = z.object({ id: z.number() })
export type Todo = z.infer<typeof TodoSchema>
export const todos: Todo[] = [{ id: 1, title: 'Buy groceries', completed: false, createdAt: '2026-03-20T10:00:00Z' }]import { } from 'silgi/analytics'
import { } from '../instance'
import { , , , } from './schema'
export const =
.$route({ : 'GET' })
.$output()
.$resolve(async ({ }) => {
return (, 'db.todos.findMany', () => [....todos])
})
export const =
.$input()
.$output()
.$resolve(async ({ , }) => {
const = { : .(), : .title, : false, : new ().() }
.todos.push()
return
})3. Router
import * as todos from './todos'
import { s } from './instance'
export const appRouter = s.router({
todos: {
list: todos.list,
create: todos.create,
},
})
export type AppRouter = typeof appRouter4. Server entry
import { s } from './server/rpc/instance'
import { appRouter } from './server/rpc/router'
export default {
fetch: s.handler(appRouter, {
analytics: { auth: process.env.ANALYTICS_TOKEN },
}),
}5. Nuxt config
export default defineNuxtConfig({
compatibilityDate: '2025-01-01',
})No
serverEntry config is needed — Nuxt automatically detects server.ts at the project root.Typed client composable
Create a composable that gives Vue components a fully typed RPC client:
import { createClient } from 'silgi/client'
import { createLink } from 'silgi/client/ofetch'
import type { AppRouter } from '../../server/rpc/router'
export function useClient() {
const link = createLink({ url: '' })
return createClient<AppRouter>(link)
}Use it in any page or component:
<script setup lang="ts">
const client = useClient()
const { data: todos } = await useAsyncData(() => client.todos.list())
</script>
<template>
<ul>
<li v-for="todo in todos" :key="todo.id">{{ todo.title }}</li>
</ul>
</template>Binary protocols
Silgi supports MessagePack and Devalue codecs alongside JSON. Pass binary or devalue to the link:
export function useClient(options?: { binary?: boolean; devalue?: boolean }) {
const link = createLink({
url: '',
binary: options?.binary,
devalue: options?.devalue,
})
return createClient<AppRouter>(link)
}MessagePack requires
msgpackr in vite.optimizeDeps.include to work in dev mode.Coexisting with Nuxt routes
When Silgi handles all requests, Nuxt's own server routes and pages won't work. To let unmatched routes fall through:
const silgiHandler = s.handler(appRouter)
export default {
fetch: async (request: Request) => {
const response = await silgiHandler(request)
if (response.status === 404) return
return response
},
}What's next?
- Nitro — standalone Nitro without Nuxt
- Analytics — built-in monitoring dashboard
- Client — typed RPC client setup
- TanStack Query — query/mutation options generation