segment — скилл для Claude Code: сегменты бэкенда Vovk.ts
Делит бэкенд на сегменты Vovk.ts: отдельные префиксы API, свои настройки runtime и статические эндпоинты. Пригодится, когда бэкенд разрастается.
npx skills add finom/vovk --skill segment -a claude-codeЧто делает segment
Сегмент в Vovk.ts — это кусок бэкенда со своим префиксом пути (/api, /api/admin, /api/foo/bar), набором контроллеров и файлом route.ts. Каждый сегмент собирается в отдельную serverless-функцию, поэтому runtime и maxDuration задаются для каждого свои. Скилл помогает выбрать форму сегмента — корневой, именованный или статический — и создать его через CLI.
Файл route.ts лежит в папке с optional catch-all: например, src/app/api/foo/[[...vovk]]/route.ts. Внутри экспортируются HTTP-обработчики из initSegment(), куда передают карту контроллеров и имя сегмента. Имя сегмента должно совпадать с путём папки: для foo/bar это segmentName: 'foo/bar'. Имя в CLI — это часть URL, а не JS-идентификатор: admin, user-portal, admin/settings. Если попросить MyAdmin, скилл переведёт в my-admin.
Глубже всего совпавший путь и выигрывает: запрос к /api/foo/bar/baz уйдёт в сегмент foo/bar, а не в корневой. Поэтому после добавления именованного сегмента старые пути под этим префиксом перестанут доходить до корня — это стоит проверить до деплоя. Статические сегменты пре-рендерятся в JSON на этапе сборки и работают с output: 'export', для которого нужен выключенный cacheComponents. Создавать сегмент лучше через npx vovk new segment, а не писать route.ts руками.
Какие задачи решает
- вынести админский API в отдельный сегмент
/api/admin - задать свой
maxDurationдля долгих запросов - пре-рендерить OpenAPI-спецификацию в JSON на сборке
- создать корневой сегмент в новом проекте Vovk
- разложить вложенные пути вида
foo/barпо сегментам
Примеры запросов агенту
- Раздели бэкенд: вынеси админские ручки в отдельный сегмент
/api/admin - Сделай статический эндпоинт с OpenAPI-спецификацией, чтобы он пре-рендерился на сборке
- Создай именованный сегмент
user-portalсmaxDuration60
Как установить segment
1Через Skills CLI (Vercel)
npx skills add finom/vovk --skill segment -a claude-code2Вручную: положить файлы
~/.claude/skills/segment/SKILL.mdПапка скилла целиком; для одного проекта — .claude/skills/segment/
1Через Skills CLI (Vercel)
npx skills add finom/vovk --skill segment -a codex2Вручную: положить файлы
.agents/skills/segment/SKILL.mdДля всех проектов — ~/.agents/skills/
1Через Skills CLI (Vercel)
npx skills add finom/vovk --skill segment -a cursor2Вручную: положить файлы
.cursor/skills/segment/SKILL.mdCursor читает и .agents/skills/, и .claude/skills/
1GitHub CLI
gh skill install finom/vovk segment2Вручную: положить файлы
.github/skills/segment/SKILL.md1Через Skills CLI (Vercel)
npx skills add finom/vovk --skill segment -a gemini-cli2Командой платформы
gemini skills install https://github.com/finom/vovk --path skills/segmentВручную: положить файлы
.devin/skills/segment/SKILL.mdСтарый путь .windsurf/skills/ тоже читается
Через Skills CLI (Vercel)
npx skills add finom/vovk --skill segment -a opencodeClaude.ai
Упакуйте папку в zip и загрузите в Settings → Capabilities → Skills
По шагам, с командами для каждой платформы: Как установить скилл в Claude Code: пять способов
Вопросы о segment
Как установить segment в Claude Code?
Выполните npx skills add finom/vovk --skill segment -a claude-code, после чего скилл окажется в ~/.claude/skills/segment/SKILL.md.
Что делает скилл segment?
Помогает нарезать бэкенд Vovk.ts на сегменты: подсказывает форму (корневой, именованный, статический), создаёт route.ts через CLI и следит за совпадением segmentName с путём папки.
Чем segment отличается от скиллов procedure и openapi?
segment отвечает только за создание сегментов и их настройки. Всё, что внутри контроллера, — к скиллу procedure; авторство OpenAPI сверх подключения openAPIObject — к openapi; мультитенантная маршрутизация — к multitenant.
Исходный текст
SKILL.md · MIT
---
name: segment
description: "Vovk.ts segments in depth (`base` already covers the one root segment and its `route.ts`) — the unit of backend slicing in a Vovk project. Use whenever the user talks about splitting a backend into parts, API paths, catch-all routes, `route.ts`, `initSegment`, per-segment runtime/maxDuration, static APIs (OpenAPI specs, historical datasets), static export (`output: 'export'`), `generateStaticParams`, or the `vovk new segment` CLI. Triggers on phrasings like \"split my backend\", \"add an API slice\", \"make a static OpenAPI endpoint\", \"separate admin API\", \"pre-render API at build time\", \"I need a `/api/foo` namespace\", \"add a root/named/static segment\". Does NOT cover procedures/handlers themselves — hand off to the `procedure` skill for anything inside a controller. Does NOT cover OpenAPI authoring beyond wiring per-segment `openAPIObject` — hand off to the `openapi` skill. Does NOT cover multi-tenant routing mechanics (subdomains, `multitenant()` proxy, per-tenant frontend pages) — hand off to the `multitenant` skill."
---
# Vovk.ts segments
A segment is a **backend slice**. Owns API path prefix (`/api`, `/api/admin`, `/api/openapi`, etc.), set of controllers, own Next.js route-handler file. Each segment compiles to independent serverless function, so runtime settings (`runtime`, `maxDuration`, etc.) can differ per segment.
Think: "one segment = one mini-backend."
## Core concepts
### The catch-all file
Every segment is Next.js App Router **optional catch-all** route handler:
```
src/app/api/[[...vovk]]/route.ts ← root segment (path: /api/*)
src/app/api/foo/[[...vovk]]/route.ts ← "foo" segment (path: /api/foo/*)
src/app/api/foo/bar/[[...vovk]]/route.ts ← "foo/bar" segment (path: /api/foo/bar/*)
```
Slug name (`vovk`) can be any name: the CLI takes any `[[...name]]` folder that has a `route.ts`. Stick with `vovk` (what `vovk new segment` writes) unless user has reason to change it. `rootEntry` is something else: the API root folder (`api`).
### `initSegment()`
Every segment's `route.ts` exports HTTP handlers produced by `initSegment()`:
```ts
// src/app/api/[[...vovk]]/route.ts
import { initSegment } from 'vovk';
import UserController from '../../../modules/user/user-controller';
import PostController from '../../../modules/post/post-controller';
export const maxDuration = 300; // Next.js route-handler option
const controllers = {
UserRPC: UserController,
PostRPC: PostController,
};
export type Controllers = typeof controllers;
export const { GET, POST, PATCH, PUT, HEAD, OPTIONS, DELETE } = initSegment({
controllers,
});
```
**Keys** of `controllers` (`UserRPC`, `PostRPC`) are client-side module names — generated client will expose `UserRPC.getUser(...)`, etc.
`initSegment` parameters:
| Param | Required | Default | Purpose |
|---|---|---|---|
| `controllers` | yes | — | Map of `RPCModuleName → ControllerClass`. |
| `segmentName` | no | `""` (root) | Identifier; must mirror folder path for named segments (e.g., `"foo/bar"`). |
| `emitSchema` | no | `true` | Emit schema for codegen. Leave on unless you know why. |
| `exposeValidation` | no | `true` | Expose validation data to client. Leave on for client-side validation. |
| `onError` | no | — | `(err: Error, req: VovkRequest) => void \| Promise<void>` — for logging/metrics; it can't change the response (a custom error format → **`base`**). `err` is whatever the handler threw (often `HttpException`). |
| `onSuccess` | no | — | `(resp: unknown, req: VovkRequest) => void \| Promise<void>` — fires after successful response. Use for metrics, audit logs. |
| `onBefore` | no | — | `(req: VovkRequest) => void \| Promise<void>` — fires before handler runs. Use for tracing, request enrichment. |
### Segment priority
Deepest matching path wins. Given `/`, `/foo`, `/foo/bar`:
- `GET /api/foo/bar/baz` → handled by `foo/bar` segment.
- `GET /api/foo/qux` → `foo` segment.
- `GET /api/something` → root segment.
When adding new named segment, any existing root-handled paths under that prefix will stop reaching root. Flag this to user.
## Picking a segment shape
| Shape | When | Path |
|---|---|---|
| **Root** | Default. Most projects have exactly one. | `src/app/api/[[...vovk]]/route.ts` |
| **Named** | Isolate slice with different route options (`maxDuration`), or for multi-tenancy. | `src/app/api/<name>/[[...vovk]]/route.ts` |
| **Static** | API that can be pre-rendered at build time — OpenAPI specs, enumerated datasets, infrequently changing reference data. Works with Next `output: 'export'`, which needs `cacheComponents` off. | `src/app/api/<name>/[[...vovk]]/route.ts` + `generateStaticParams` |
**When user says...**
- "split my backend", "add an admin API", "separate `/api/admin`" → **named segment**.
- "pre-render OpenAPI", "static API docs", "build-time JSON endpoint" → **static segment**.
- "new Vovk project, first route" → **root segment**.
- "different Node runtime for X" or "longer timeout for X" → **named segment** + `export const maxDuration` / `export const runtime` in its `route.ts`.
- "multi-tenant", "multitenancy", "per-tenant API", "host per subdomain", "`admin.example.com` and `customer.example.com`" → **hand off to the `multitenant` skill**. (Segments are one part of that setup; this skill only covers creating them.)
## Creating a segment (CLI)
Always use the CLI — writes `route.ts` with right `initSegment()` wiring, easy to miscopy by hand.
```bash
# Root
npx vovk new segment
# Named
npx vovk new segment <name>
# Static named
npx vovk new segment <name> --static
```
**`<name>` is URL path segment**, not JS identifier — lowercase or kebab-case for multi-word (`admin`, `user-portal`), slash-separated for nested paths (`foo/bar`, `admin/settings`). **Not camelCase.** If user asks for segment like `MyAdmin` or `user_portal`, convert to URL-safe form (`my-admin`, `user-portal`) before calling CLI — name becomes part of URL (`/api/my-admin/...`) and `segmentName` inside `initSegment({ segmentName: '...' })`. Opposite rule from `vovk new controller service` (module names there are camelCase — see `procedure` skill).
After CLI runs, verify expected `route.ts` landed at expected path. If it didn't, surface CLI output — don't hand-write it.
## Named segment example
```ts
// src/app/api/admin/[[...vovk]]/route.ts
import { initSegment } from 'vovk';
import AdminController from '../../../../modules/admin/admin-controller';
export const runtime = 'nodejs';
export const maxDuration = 60;
const controllers = { AdminRPC: AdminController };
export type Controllers = typeof controllers;
export const { GET, POST, PATCH, PUT, HEAD, OPTIONS, DELETE } = initSegment({
segmentName: 'admin',
controllers,
});
```
`segmentName` MUST match folder path. For `src/app/api/foo/bar/[[...vovk]]/route.ts`, `segmentName: 'foo/bar'`.
## Static segments
Pre-renders endpoints to JSON files at build time. Useful for APIs that don't need per-request dynamism.
### 1. `route.ts` with `generateStaticParams`
```ts
// src/app/api/openapi/[[...vovk]]/route.ts
import { initSegment, controllersToStaticParams } from 'vovk';
import HelloController from '../../../../modules/hello/hello-controller';
const controllers = { HelloRPC: HelloController };
export type Controllers = typeof controllers;
export function generateStaticParams() {
return controllersToStaticParams(controllers);
}
export const { GET } = initSegment({
segmentName: 'openapi',
controllers,
});
```
If you've customized slug (not `vovk`), pass it: `controllersToStaticParams(controllers, 'custom')`.
No `dynamic` export: the segment pre-renders with `cacheComponents` on or off, and with it on (create-next-app 16.4+ sets it) Next.js refuses `dynamic = 'force-static'`. `output: 'export'` needs `cacheComponents: false` in next.config.
### 2. Endpoints must be enumerable
Every endpoint in static segment must resolve to finite set of URLs at build time.
**Zero-param endpoint** — give it `.json` suffix so static hosting serves with right content type:
```ts
@prefix('hello')
export default class HelloController {
@get('greeting.json')
static async getHello() {
return { greeting: 'Hello world!' };
}
}
```
**Parameterized endpoint** — enumerate via `staticParams` on decorator:
```ts
import { z } from 'zod';
import { procedure, get, prefix, operation } from 'vovk';
@prefix('static-params')
export default class StaticParamsController {
@operation({ summary: 'Static params' })
@get('{section}/page{page}.json', {
staticParams: [
{ section: 'a', page: '1' },
{ section: 'a', page: '2' },
{ section: 'b', page: '1' },
{ section: 'b', page: '2' },
],
})
static getStaticParams = procedure({
params: z.object({
section: z.enum(['a', 'b']),
page: z.enum(['1', '2']),
}),
}).handle(async (_req, { section, page }) => {
return { section, page };
});
}
```
Pre-renders four JSON files at build time.
### 3. Static export (optional)
If whole Next.js app is exported statically (`next.config.js` → `output: 'export'`), static segments work as-is. Generated RPC client still calls them normal way:
```ts
const resp = await StaticParamsRPC.getStaticParams({
params: { section: 'a', page: '1' },
});
```
### Rules for static segments
- No per-request state. Every response baked at build time.
- Use `.json` suffixes on endpoint paths for correct `Content-Type` when hosted statically.
- Keep static segments dedicated to static endpoints. Mixing dynamic handlers in static segment defeats pre-render.
## Multi-tenant routing
One named segment per tenant plus a `proxy.ts` with `multitenant()` serves `admin.example.com`, `customer.example.com`, … from one app. This skill creates the segments; everything else → **`multitenant` skill**.
## Per-segment configuration
Configure individual segments in `vovk.config.mjs`:
```ts
/** @type {import('vovk').VovkConfig} */
const config = {
outputConfig: {
segments: {
admin: {
openAPIObject: {
info: {
title: 'Admin API',
description: 'Internal admin endpoints.',
version: '1.0.0',
},
},
},
},
},
};
export default config;
```
Segment-specific overrides supported: `openAPIObject`, `imports`, other output settings. Segment name key (`admin`) matches `segmentName` in `initSegment()`. Root segment uses key `""`.
## Schema output
Each segment writes one file: `.vovk-schema/root.json` for the root segment, `.vovk-schema/<segmentName>.json` for the others, plus `_meta.json`. Commit `.vovk-schema/`.
## Common flows
### "Add an admin API with a longer timeout"
1. `npx vovk new segment admin`.
2. In generated `src/app/api/admin/[[...vovk]]/route.ts`, add `export const maxDuration = 300;` (or whatever).
3. Controllers go into `src/modules/admin/*` — creating them is `procedure` skill's job. Hand off.
### "Pre-render my OpenAPI spec at build time"
1. `npx vovk new segment openapi --static`.
2. Create controller in `src/modules/openapi/` with `.json`-suffixed endpoint (see static example above). Hand off to `procedure` for controller body.
3. OpenAPI payload itself comes from `openapi` skill.
### "Split `/api/public` and `/api/internal`"
Two named segments (`public`, `internal`). Flag segment priority: any pre-existing root-handled paths under those prefixes will shift.
### "Host `admin.example.com` and `customer.example.com` from one app"
Multi-tenant flow. Hand off to **`multitenant` skill**. Segment side is `npx vovk new segment admin` + `npx vovk new segment customer`; everything else (proxy, `vovk.config.mjs` changes, frontend pages, DNS) belongs to that skill.
### "Change the API root from `/api` to `/rpc`"
1. Move `src/app/api/` to `src/app/rpc/`.
2. Set `rootEntry: 'rpc'` in `vovk.config.mjs`. The CLI looks for segments under `src/app/<rootEntry>`: without it, `vovk generate` leaves them out of the client and `vovk dev` deletes their schema files.
3. Run `vovk dev` once: it rewrites `.vovk-schema/_meta.json`, which the client reads `rootEntry` from. Commit `.vovk-schema/`.
## Gotchas
- **`segmentName` mismatch**: must mirror folder path. `src/app/api/foo/bar/[[...vovk]]/route.ts` → `segmentName: 'foo/bar'`. Mismatch breaks codegen silently.
- **Don't hand-write `route.ts`**: use the CLI. `initSegment` shape and `controllers` object wiring are easy to miscopy.
- **Static segment + dynamic handler**: handler runs at build, not request time. If you need per-request logic, use regular named segment.
- **`controllers` keys vs. controller names**: key (`UserRPC`) is client-side module name, not controller class name. Mismatches here are most common "why doesn't my RPC call work" bug.
- **Removing a segment**: deleting `route.ts` is not enough — also remove folder and any `.vovk-schema/<name>.json` file, then run `vovk generate` to refresh client.
© finom · MIT · finom/vovk