newX 1.3 — islands to disk, server-mode islands, image proxy

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 = scanContent("content/posts");  const post = posts.find((p) => p.slug === params.slug);  if (!post) return new Response(null, { status: 404 });  const html = renderMarkdown(post.body);  return { post: { ...post, html } };}export default function BlogPost({ loaderData }: RouteProps) {  const { post } = loaderData as {    post: { frontmatter: Record<string, unknown>; html: string };  };  return (    <article className="prose max-w-none">      <h1 className="text-4xl font-bold">{post.frontmatter.title}</h1>      <p className="text-sm text-muted-foreground">        {String(post.frontmatter.date)}  {String(post.frontmatter.author)}      </p>      <div        className="mt-8 leading-relaxed"        dangerouslySetInnerHTML={{ __html: 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, body (the raw markdown), and frontmatter (parsed YAML). Both this and renderMarkdown are synchronous — no await needed.

content entry
interface ContentEntry {  slug: string;              // "posts/hello-world" — route-safe path  body: string;              // markdown after the frontmatter block  frontmatter: Record<string, string | number | boolean | string[] | null>;}

renderMarkdown API

renderMarkdown(markdownString) converts markdown to an HTML string. It is a lightweight, dependency-free renderer: headings, paragraphs, lists, links, inline code, code blocks, bold/italic, and blockquotes are supported, and all output is HTML-escaped by default (escapeHtml is exported separately too). It does not run a full markdown engine or syntax highlighter, so for heavy-duty content you can swap in your own renderer and feed the result to dangerouslySetInnerHTML.

Auto-routes

Every .md/.mdx file under contentDir becomes a route at its own path during build and dev. The blog template is a working example: content/posts/*.md with a [slug].tsx page that renders each post via renderMarkdown.