Production & Security
Step-by-step guide to concurrency control, security enforcement, failure retries, performance scaling, and system limitations.
Deploying Content Engine models to production environments requires understanding concurrency protections, database security, performance indexing, and architectural boundaries.
Prerequisites & Context
Production hardening applies to both your client definition articleContentType and compiled server model articleContent:
- Concurrency Locks:
ContentVersionConflictis thrown when updating aneditorial: { enabled: true }content type whoseversionhas changed concurrently. - Staff Permissions: Enforced automatically by
buildContentAdminModulefor all management routes. - Diagnostics:
contentEngineDiagnostics(c)checks registered content types, background effect queues, and search provider drift.
Step-by-Step Production Best Practices
Step 1: Handle Concurrency & Optimistic Lock Retries
When editorial: { enabled: true } is configured, update operations require passing the expected version integer. When a concurrent modification occurs, the engine throws ContentVersionConflict (which maps to HTTP 409 Conflict in generated routes):
import { ContentVersionConflict } from "@vitnode/core/content"
try {
await service.update(articleId, { title: "New Title", version: 2 })
} catch (err) {
if (err instanceof ContentVersionConflict) {
// Current database version and expected version are available
console.warn(`Conflict on item ${err.itemId}: current version is ${err.currentVersion}, expected ${err.expectedVersion}`)
// Refetch latest record version and retry update
const latest = await service.findById(articleId)
if (latest && "version" in latest) {
await service.update(articleId, {
title: "New Title",
version: latest.version,
})
}
}
}Step 2: Enforce Staff Permissions and Public Allowlists
Ensure all custom administrative actions verify staff permissions, and verify that publicApi.fields lists only non-sensitive attributes:
import { defineContentType, field } from "@vitnode/core/content"
export const articleContentType = defineContentType({
id: "example.article",
tableName: "example_articles",
publication: { enabled: true },
publicApi: {
enabled: true,
path: "articles",
fields: ["id", "title", "slug", "code", "excerpt"],
},
fields: {
title: field.text({ required: true }),
slug: field.slug({ source: "title" }),
code: field.text({ required: true }),
excerpt: field.textarea({ nullable: true }),
internalNotes: field.textarea({ nullable: true }), // Private internal column
},
})internalNotes is omitted from publicApi.fields, preventing it from ever being exposed via public endpoints.
Step 3: Inspect Engine Diagnostics
Inspect engine health and drift in internal routes or monitoring probes:
import { contentEngineDiagnostics } from "@vitnode/core/content/server"
// Inside a Hono route handler:
const diagnostics = await contentEngineDiagnostics(c)
console.log(`Healthy: ${diagnostics.healthy}`)
console.log(`Search sync: ${diagnostics.searchHealthy}`)
console.log(`Background effects: ${diagnostics.effectsHealthy}`)
console.log(`Registered content types: ${diagnostics.contentTypes.length}`)Architectural Boundaries & System Limitations
- Code-First Architecture: Content types must be declared in TypeScript source control and compiled before migrations are generated. There is no runtime no-code UI for creating tables dynamically.
- No Direct Schema Polymorphism: A single field cannot reference multiple unrelated tables. Use structured field groups or separate relation models instead.
AdminCP Integration
Configure zero-code AdminCP management screens, custom form layouts, list columns, and screen overrides.
Widgets Overview
Plugins define widgets, VitNode collects them into a registry, and content types or editable page zones store an ordered list of widgets that moderators can visually edit in place.