Silgi

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

server/rpc/instance.ts
import { silgi } from 'silgi'
import { todos } from './todos/schema'

export const s = silgi({
  context: () => ({ todos }),
})

2. Procedures

server/rpc/todos/schema.ts
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' }]
server/rpc/todos/index.ts
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

server/rpc/router.ts
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 appRouter

4. Server entry

server.ts
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

nuxt.config.ts
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:

app/composables/useClient.ts
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:

app/pages/todos.vue
<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:

server.ts
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

On this page