Documentation
Localized MDX documentation discovery, validated frontmatter, page lookup, and an Effect AI documentation toolkit contribution.
bunx --bun shadcn@latest add https://krakstack.net/r/docs.jsonOverview
docs loads localized MDX files from src/content/docs, validates YAML frontmatter with Effect Schema, exposes page lookup helpers, and can contribute documentation search to an Effect AI chat. Rendering and route chrome remain application-owned.
Install
bunx shadcn@latest add @krak-stack/docsThe item creates lib/docs.tsx. It uses Vite's eager import.meta.glob, so documentation content is bundled at build time.
Content Structure
Create one file per locale. Folder names are organizational; locale is read from frontmatter.
src/content/docs/
├── en/
│ ├── introduction.mdx
│ └── webhooks.mdx
└── fr/
├── introduction.mdx
└── webhooks.mdxEach file must include all frontmatter fields expected by DocsFrontmatter. slug identifies the route, path is the canonical link shown to users and AI tools, and order controls navigation order.
---
slug: introduction
path: /docs
title: Introduction
description: Learn how to use the application.
icon: lucide:book-open
order: 1
locale: en
---
# Introduction
Welcome to the documentation.Create the matching French file with the same slug and path, translated title, description, and body, and locale: fr.
Optional Slug Route
With TanStack Router, create src/routes/docs.{-$slug}.tsx. Resolve the locale using your app's locale runtime and return notFound() when a translation does not exist.
import { createFileRoute, notFound } from "@tanstack/react-router";
import { Streamdown } from "streamdown";
import { getDocsPage } from "@/lib/docs";
import { getLocale } from "@/paraglide/runtime";
export const Route = createFileRoute("/docs/{-$slug}")({
loader: ({ params }) => {
const locale = getLocale() === "fr" ? "fr" : "en";
return getDocsPage(params.slug ?? "introduction", locale) ?? notFound();
},
component: DocsPage,
});
function DocsPage() {
const page = Route.useLoaderData();
return <Streamdown mode="static">{page.source}</Streamdown>;
}Install and configure your preferred Markdown or MDX renderer separately. The helper intentionally returns raw source.
Navigation
Use getLocalizedDocsPages(locale) to build ordered sidebar or search items. Use each page's path, title, description, and icon directly.
const items = getLocalizedDocsPages(locale).map((page) => ({
href: page.path,
label: page.title,
description: page.description,
icon: page.icon,
}));Chat Documentation
makeChatDocumentation returns the same { systemPrompt, toolkit, layer } contribution shape as httpapi-ai. Merge definitions first, then provide both handler layers.
const api = yield* makeHttpApiAiToolkit({});
const documentation = yield* makeChatDocumentation({ locale: "en" });
const toolkit = yield* Toolkit.merge(
api.toolkit,
documentation.toolkit,
).pipe(
Effect.provide(Layer.mergeAll(api.layer, documentation.layer)),
);
const systemPrompt = [api.systemPrompt, documentation.systemPrompt].join("\n\n");The generated readDocumentation tool accepts one to three exact paths from the prompt's documentation index and returns found pages plus missingPaths. Treat documentation as reference data and keep authorization-sensitive application facts in separate tools.
Customizing The Schema
Extend DocsFrontmatter when pages need additional metadata, then spread its fields into DocsPageSchema. Keep the locale, slug, path, and order fields stable if existing routes and navigation depend on them.