Content Collections

Content collections

Write content in Markdown with frontmatter, and x automatically turns it into pages. Perfect for blogs, documentation, and any content-driven site.

Configuration

Point the content directory in x.config.ts to a folder with your markdown files.

x.config.ts
import { defineConfig } from "@thexjs/core";export default defineConfig({  contentDir: "content",});

Markdown with frontmatter

Each markdown file starts with frontmatter (YAML between --- delimiters) followed by markdown content.

content/posts/hello-world.md
---title: Hello Worlddate: 2026-03-15tags: [getting-started, tutorial]author: Jane Doe---## Welcome to x!This is your first post using x's content collection system.You can write **markdown** with all the usual syntax:- Lists- **Bold** and *italic* text- `inline code` and code blocks```tsconst greeting = "Hello from x!";console.log(greeting);```

Reading content in a loader

Use scanContent to discover files and renderMarkdown to convert markdown to HTML in your loaders.

src/pages/blog/[slug].tsx
import type { RouteProps, LoaderArgs } from "@thexjs/core";import { scanContent, renderMarkdown } from "@thexjs/core";export async function loader({ params }: LoaderArgs) {  const posts = await scanContent("posts");  const post = posts.find((p) => p.slug === params.slug);  if (!post) throw new Response(null, { status: 404 });  const html = await renderMarkdown(post.body);  return { post: { ...post, html } };}export default function BlogPost({ loaderData }: RouteProps<typeof loader>) {  return (    <article className="prose max-w-none">      <h1 className="text-4xl font-bold">{loaderData.post.title}</h1>      <p className="text-sm text-muted-foreground">        {loaderData.post.date}  {loaderData.post.author}      </p>      <div className="mt-6 flex gap-2">        {loaderData.post.tags?.map((tag: string) => (          <span key={tag} className="rounded-full bg-primary/10 px-3 py-1 text-xs text-primary">            {tag}          </span>        ))}      </div>      <div        className="mt-8 leading-relaxed"        dangerouslySetInnerHTML={{ __html: loaderData.post.html }}      /> >    </article>  );}

scanContent API

scanContent(directory) scans a subdirectory of your content folder and returns an array of content entries. Each entry includes slug, frontmatter, and content.

renderMarkdown API

renderMarkdown(markdownString) converts markdown to an HTML string. It supports syntax highlighting via Shiki and handles all standard markdown features.