Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.vertz.dev/llms.txt

Use this file to discover all available pages before exploring further.

These are the most common mistakes we see in Vertz projects — especially when AI agents or developers are getting started. Each one has a clear fix.

Skipping vtz create

The fastest and most reliable way to start a Vertz project is vtz create. It scaffolds a working full-stack app with the compiler, dev server, SSR, typed API client, and theme — all configured correctly.
# Wrong — manually creating files from scratch
mkdir my-app && cd my-app
# ... creating package.json, tsconfig.json, entry files by hand

# Right — scaffold and customize
vtz create vertz my-app
cd my-app && vtz install && vtz dev
Why it matters: Vertz requires specific compiler configuration, JSX import source settings, dev server setup, and file conventions. Manual setup is error-prone and misses features like SSR, HMR, scoped CSS extraction, and the typed API client. Start with vtz create and modify the scaffolded code.

Using signal() instead of let

The Vertz compiler transforms let declarations into signals automatically and auto-unwraps .value in JSX. Using signal() directly bypasses these optimizations.
// Wrong — manual signal, requires .value everywhere
import { signal } from '@vertz/ui';
const count = signal(0);
return <span>{count.value}</span>;

// Right — compiler handles reactivity
let count = 0;
return <span>{count}</span>;
Why it matters: When you use let, the compiler tracks dependencies automatically — queries re-fetch, DOM updates, and effects re-run without manual .value reads. With signal(), you must manage everything yourself.

Using () => true instead of rules.public

Entity access rules should use declarative rules.* descriptors, not callback functions.
// Wrong — opaque function, can't be serialized or inspected
access: {
  list: () => true,
  create: () => true,
}

// Right — declarative descriptor
import { entity, rules } from '@vertz/server';

access: {
  list: rules.public,
  create: rules.authenticated(),
}
Why it matters: rules.* descriptors are plain objects that the framework can serialize for the client SDK, display in generated OpenAPI docs, and potentially compile to database-level policies. Callback functions are opaque. See Entities for the full access control API.

Using raw fetch() instead of the generated SDK

Vertz auto-generates a typed SDK from your entity and service definitions. Always use it.
// Wrong — bypasses type safety, SSR, caching, and optimistic updates
const res = await fetch('/api/tasks');
const data = await res.json();

// Right — fully typed, SSR-integrated, cached
import { query } from '@vertz/ui';
import { api } from '../client';

const tasks = query(api.tasks.list());
Why it matters: The generated SDK provides type safety (request and response types checked at compile time), SSR integration (queries run on the server during SSR), automatic cache invalidation (mutations refetch related queries), and optimistic updates. See Data Fetching for the full guide.

Manual refetch() instead of reactive queries

When queries read reactive state inside their thunk, they automatically re-fetch when that state changes. Manual refetch() calls are usually unnecessary.
// Wrong — manual state tracking and refetch
const filter = signal('all');
const tasks = query(() => api.tasks.list({ status: filter.value }));
// Later: tasks.refetch() after filter changes

// Right — compiler auto-tracks dependencies
let filter = 'all';
const tasks = query(() => api.tasks.list({ status: filter }));
// Changing filter automatically re-fetches
See Data Fetching for how dependency tracking works.

Calling components as functions

Always use JSX syntax for components. Calling them as functions bypasses the compiler’s reactive prop generation.
// Wrong — no reactive props
const card = TaskCard({ task, onClick: handleClick });

// Right — compiler generates getters for reactive props
<TaskCard task={task} onClick={handleClick} />;

Annotating component return types

Let TypeScript infer return types from JSX. Annotating : HTMLElement is a lossy upcast.
// Wrong
export function TaskCard({ task }: Props): HTMLElement {

// Right
export function TaskCard({ task }: Props) {

Running SQLite scripts with node instead of vtz

The vtz runtime includes a built-in SQLite driver powered by Rust — no extra dependencies needed. Running scripts that use @vertz/db with SQLite under plain node will fail because Node.js doesn’t have the native driver.
# Wrong — plain Node doesn't have the built-in SQLite driver
node ./scripts/ensure-db.ts

# Right — vtz includes a native SQLite driver
vtz run ensure-db
Why it matters: The vtz runtime embeds SQLite via Rust, so any script run through vtz (including vtz run <script>, vtz dev, and vtz test) has SQLite available with zero extra dependencies. Running the same script with node bypasses the runtime entirely. If you need to run database scripts outside of vtz, use PostgreSQL instead of SQLite, or install better-sqlite3 as a fallback.

Using .references() on columns for foreign keys

Foreign keys are declared via d.ref.one() on models, not .references() on columns.
// Wrong — this is a pattern from other ORMs, not Vertz
const postsTable = d.table('posts', {
  authorId: d.uuid().references('users.id'), // Does not exist
});

// Right — use d.ref.one() on the model
const postsModel = d.model(postsTable, {
  author: d.ref.one(() => usersTable, 'authorId'),
});
See Schema for the full relations API.