Broken content shouldn't be able to reach production. A missing meta description, a malformed date, a required field left blank - on most sites these slip straight through, and you find out from a customer, a broken page, or a search-console warning weeks later. Each one is small; collectively they're the reason "just a content change" still causes incidents.
There's a quietly excellent tool for closing that gap, and almost nobody talks about it. Velite takes your Markdown, YAML, and JSON files, validates them against a schema at build time, transforms them, and emits a typed, importable bundle. No runtime, no database, no API - files in, typed data out. Here's why it's a great fit for dev-owned content, and the one limitation that eventually made us move on.
The problem: typed content without a CMS or a runtime
You want content as files in your repo (versioned, reviewable, fast) - but you also want guarantees. That a post always has a title under 60 characters. That every required field is present. That a typo in frontmatter is caught before deploy, not after.
A hosted CMS gives you an editor but adds a runtime dependency and a bill. Raw Markdown gives you files but no guarantees. Velite sits exactly in the middle: your files stay files, and a schema enforces the guarantees at build.
A 20-line config
You define collections with a schema - Velite uses Zod - and a bad frontmatter field fails the build instead of production:
// velite.config.ts
import { defineConfig, s } from "velite";
export default defineConfig({
collections: {
posts: {
name: "Post",
pattern: "posts/**/*.md",
schema: s.object({
title: s.string().max(60),
description: s.string().min(120).max(155),
date: s.isodate(),
slug: s.slug("posts"),
body: s.markdown(),
toc: s.toc(),
metadata: s.metadata(), // reading time, word count
}),
},
},
});
s.slug(), s.markdown(), s.toc(), and s.metadata() are built in - you get slugs, compiled Markdown, a table of contents, and reading time without writing any of it.
The payoff: a typed import that fails loudly
Consumers read content through a single typed import:
import { posts } from "#velite";
// posts is fully typed - autocomplete on every field.
export const published = posts.filter((p) => p.date <= today);
Two things fall out of this that matter to a team, not just a developer:
- Bad content fails the build. Forget a description, mistype a date, blow the title length - CI goes red. That's a whole category of "we found out from a customer" turned into a check that blocks the merge.
- Renames are safe. Change a field name and TypeScript immediately shows you every page that breaks. No guessing, no grep-and-pray.
The custom-lint trick
Because it's your build step, you can bolt on your own rules. We added an SEO lint - title ≤ 60 chars, meta description 120–155 - and ran velite build --strict in CI so a merge couldn't land with sloppy metadata. The house style stops being a thing people remember and becomes a thing the pipeline enforces.
Where it stops: the honest section
Velite is a compiler, not a CMS. That's the ceiling, and it's worth being clear-eyed about:
- No editor. A non-developer can't change content without touching the repo and its frontmatter. The moment you want a real editing UI, you add a second tool - and now you have two schemas to keep in sync.
- That two-schema drift is exactly why we left. We paired Velite with a Git-based CMS, then realised the CMS's own Reader API could replace Velite entirely and collapse everything back to one schema. (That's the other half of the story - how Keystatic's Reader API replaced our build step.)
Verdict
For content that engineers own, Velite is close to ideal: typed, validated, zero-runtime, and it makes bad content a build failure instead of a production surprise. Reassess the moment a non-developer needs to edit - that's the signal to look at a CMS, and to ask whether it can also be your content source so you don't end up maintaining two.
Choosing the right content layer looks like a small decision and quietly shapes how reliably a team can ship. Weighing exactly these trade-offs - build-time safety, who owns the content, how many moving parts you're signing up to maintain - is the kind of judgement we bring when we build custom software at Bruvora.
Frequently asked questions
- What is Velite?
- Velite is a build-time content layer. It reads your Markdown, YAML, or JSON files, validates them against a schema, transforms them, and outputs a typed bundle you import directly in your app - with no runtime, database, or content API involved.
- How is Velite different from a CMS?
- A CMS gives non-technical users an interface to edit content. Velite gives developers a typed, validated build step. It has no editor - content is edited as files in the repo - so it's ideal for dev-owned content and stops short the moment a non-developer needs to make changes.
- Does Velite add runtime overhead?
- No. All the work happens at build time and the output is static typed data, so pages just read a plain object. There's nothing extra to run or scale in production.
Keep reading
How Keystatic gives non-technical editors a real UI that commits to your Git repo - and a Reader API that replaces your content build step. One schema, and the five gotchas that cost us an afternoon.
Corporate email scanners pre-click magic links and burn the one-time token, so your B2B users hit dead links. Here's why OTP is the safer default for business software.
The nine React UI libraries we reach for to ship beautiful, accessible interfaces fast - and a simple four-tier way to pick the right one for what you're building.