segment — Claude Code skill: Vovk.ts backend segments
Splits a Vovk.ts backend into segments with their own API prefixes, runtime settings and static endpoints. Useful once one route file stops being enough.
npx skills add finom/vovk --skill segment -a claude-codeWhat segment does
In Vovk.ts a segment is one slice of the backend: an API path prefix such as /api or /api/admin, the controllers that belong to it, and its own Next.js route handler file. Every segment compiles into a separate serverless function, so runtime and maxDuration can differ from one slice to the next. The skill picks the right shape for the job — root, named or static — and creates it.
The handler lives in an optional catch-all folder, for example src/app/api/foo/[[...vovk]]/route.ts. It exports HTTP methods built by initSegment(), which takes a map of controllers plus the segment name. That name has to mirror the folder path, so foo/bar becomes segmentName: 'foo/bar'. The CLI argument is a URL path piece, not a JS identifier: admin, user-portal, admin/settings. Ask for MyAdmin and the skill rewrites it to my-admin.
Routing goes to the deepest matching path, so /api/foo/bar/baz lands in the foo/bar segment rather than the root one. Add a named segment and any existing root-handled paths under that prefix stop reaching root — check that before you deploy. Static segments are pre-rendered to JSON at build time and pair with output: 'export', which requires cacheComponents to be off. Use npx vovk new segment instead of writing route.ts by hand.
What it helps with
- pull the admin API into its own
/api/adminsegment - give slow endpoints a longer
maxDuration - pre-render an OpenAPI spec as a JSON endpoint
- set up the first root segment in a new Vovk project
- map nested paths like
foo/baronto separate segments
Example prompts for the agent
- Split my backend and move the admin handlers into a separate
/api/adminsegment - Add a static OpenAPI endpoint that gets pre-rendered at build time
- Create a named segment
user-portalwithmaxDuration60
How to install segment
1Skills CLI (Vercel)
npx skills add finom/vovk --skill segment -a claude-code2Manually: put the files in place
~/.claude/skills/segment/SKILL.mdCopy the whole skill folder; for one project use .claude/skills/segment/
1Skills CLI (Vercel)
npx skills add finom/vovk --skill segment -a codex2Manually: put the files in place
.agents/skills/segment/SKILL.mdFor all projects use ~/.agents/skills/
1Skills CLI (Vercel)
npx skills add finom/vovk --skill segment -a cursor2Manually: put the files in place
.cursor/skills/segment/SKILL.mdCursor also reads .agents/skills/ and .claude/skills/
1GitHub CLI
gh skill install finom/vovk segment2Manually: put the files in place
.github/skills/segment/SKILL.md1Skills CLI (Vercel)
npx skills add finom/vovk --skill segment -a gemini-cli2Platform CLI
gemini skills install https://github.com/finom/vovk --path skills/segmentManually: put the files in place
.devin/skills/segment/SKILL.mdThe legacy .windsurf/skills/ path still works
Skills CLI (Vercel)
npx skills add finom/vovk --skill segment -a opencodeClaude.ai
Zip the folder and upload it in Settings → Capabilities → Skills
Step by step, with commands for each platform: How to install skills in Claude Code: five ways
Questions about segment
How do I install segment in Claude Code?
Run npx skills add finom/vovk --skill segment -a claude-code. The skill then sits at ~/.claude/skills/segment/SKILL.md.
What does the segment skill do?
It walks through slicing a Vovk.ts backend into segments: choosing between root, named and static shapes, generating route.ts via the CLI, and keeping segmentName in sync with the folder path.
How is segment different from the procedure and openapi skills?
segment only covers creating segments and their options. Anything inside a controller goes to procedure, OpenAPI authoring beyond wiring openAPIObject goes to openapi, and multi-tenant routing goes to multitenant.
Source text
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