Getting Started
Yamblog is a file-based, type-safe, framework-agnostic markdown blog engine.
Write posts as .md files. Get a fully-featured blog. No database, no CMS.
Install
Pick the adapter for your framework:
# Next.jsnpm install @yamblog/core @yamblog/next
# Astronpm install @yamblog/core @yamblog/astro
# React (Vite / CRA)npm install @yamblog/core @yamblog/reactIf you want an LLM to do the integration for you, use the dedicated Prompt for LLMs guide instead of improvising a one-line install request.
Create your first post
Posts live in a directory of your choice — content/posts/ is the convention:
content/ posts/ hello-world.mdEvery post needs a frontmatter block:
---title: "Hello, World!"date: "2026-01-15"author: "Your Name"tags: ["intro"]excerpt: "My very first post."draft: false---
Welcome to my blog! This is the body of the post in **markdown**.Required fields: title, date.
Optional but recommended: author, tags, excerpt, draft.
The slug is derived from the filename and sanitized into a URL-safe form —
hello-world.md becomes /blog/hello-world, and My Post.md becomes /blog/my-post.
Do not put slug in frontmatter; it is a system field.
Create a blog instance
import { createBlog } from '@yamblog/core';
export const blog = createBlog({ contentDir: './content/posts',});createBlog returns a Blog object. All methods return Promises and are safe
to call in parallel — results are cached after the first load. In development
(NODE_ENV=development) the cache is refreshed whenever a content file
changes, so edits show up without restarting the dev server.
Useful options:
export const blog = createBlog({ contentDir: './content/posts', siteUrl: 'https://example.com', // base for RSS / sitemap / JSON-LD links basePath: '/blog', // URL prefix where posts are served (default '/blog', '' for site root) includeDrafts: false, // set true to preview posts marked draft: true in queries});Query your posts
const posts = await blog.getPosts(); // all published postsconst post = await blog.getPostBySlug('hello-world'); // throws PostNotFoundError if missingconst maybe = await blog.findPostBySlug('hello-world'); // null if missingconst featured = await blog.getFeaturedPosts();const tags = await blog.getTags();const results = await blog.search('hello');Every method also has a synchronous twin (getPostsSync(),
generateRssSync(...), …) for non-async contexts — Pages Router
getStaticProps, module-scope constants, or standalone build scripts.
Missing posts are catchable without string-matching:
import { PostNotFoundError } from '@yamblog/core';
try { await blog.getPostBySlug(slug);} catch (err) { if (err instanceof PostNotFoundError) notFound(); // err.slug available else throw err;}Extend the schema
Use Zod to add custom frontmatter fields — the schema’s inferred type flows to every method that returns posts, so custom fields are typed with no casts:
import { createBlog, defaultSchema } from '@yamblog/core';import { z } from 'zod';
const blog = createBlog({ contentDir: './content/posts', schema: defaultSchema.extend({ videoUrl: z.string().url().optional(), }),});
const post = await blog.getPostBySlug('hello-world');post.videoUrl; // string | undefined — typed automaticallyNext steps
- Architecture overview — how the packages fit together
- Next.js recipe — App Router integration
- Astro recipe — Content Layer integration
- React recipe — Vite SPA integration
- Extensibility — stable IDs, comments, analytics
- AI agent authoring — agent-first workflow
- Prompt for LLMs — copy-paste integration prompt for coding agents