Implement automatic TypeScript type checking for code examples in MDX documentation during Astro build and dev. This catches type errors in docs before they go live.
Approach: Astro Integration that runs at astro:config:setup (before build/dev)
Flow:
- Scan all MDX files in
src/content/docs/ - Extract TypeScript/TS code blocks with line numbers
- Filter out blocks marked with
@nocheck - Write each to staging project's
snippets/directory - Run single
tsc --noEmiton all snippets - Map errors back to source MDX files
- Fail build/dev if any type errors exist
Replace the current mdxAnnotations JSON format with a simple space-separated syntax:
Format:
```{lang}
```{lang} {title}
```{lang} @nocheck
```{lang} {title} @nocheck
Rules:
- First token is always the language (required)
@nocheckflag can appear anywhere after the language- Any other token is treated as the title
- Only one title allowed (no spaces in titles, use kebab-case)
Examples:
| Code Fence | Language | Title | Nocheck |
|---|---|---|---|
```ts |
ts | - | false |
```ts server.ts |
ts | server.ts | false |
```ts @nocheck |
ts | - | true |
```ts server.ts @nocheck |
ts | server.ts | true |
```bash install.sh |
bash | install.sh | false |
website/
├── scripts/
│ └── typecheck-staging/ # NEW
│ ├── package.json # rivetkit + typescript deps
│ ├── tsconfig.json # Strict TS config
│ ├── ambient.d.ts # Declares common doc patterns
│ └── snippets/ # Generated at build (gitignored)
├── src/
│ └── integrations/
│ └── typecheck-code-blocks.ts # NEW: Astro integration
website/scripts/typecheck-staging/package.json
{
"name": "docs-typecheck-staging",
"private": true,
"type": "module",
"dependencies": {
"rivetkit": "workspace:*",
"hono": "^4.7.0",
"zod": "^4.1.0"
},
"devDependencies": {
"typescript": "^5.7.3",
"@types/node": "^22.13.1"
}
}website/scripts/typecheck-staging/tsconfig.json
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"skipLibCheck": true,
"noEmit": true,
"esModuleInterop": true,
"lib": ["ESNext", "DOM"],
"types": ["node"]
},
"include": ["snippets/**/*.ts", "ambient.d.ts"]
}website/scripts/typecheck-staging/ambient.d.ts
// Ambient declarations for common doc patterns
declare module "./registry" {
export const registry: import("rivetkit").Registry<any>;
}
declare module "./actors" {
export const registry: import("rivetkit").Registry<any>;
}website/src/integrations/typecheck-code-blocks.ts
Key functions:
extractCodeBlocks(content, sourceFile)- Parse MDX for TS code blocks, detect@nocheckwrapCodeForTypecheck(code)- Add common imports, wrap partial snippetsgenerateSnippetFilename(sourceFile, lineNumber)- Create unique filenamesparseTscOutput(output, mapping)- Map tsc errors to source files
The integration:
- Runs during both
buildanddev(atastro:config:setup) - Cleans/creates
snippets/directory - Extracts all TS code blocks from MDX files
- Filters out blocks with
@nocheckannotation - Wraps code and writes to staging project
- Runs
npx tsc --noEmit - Reports errors with source file + line number
- Throws to fail build/dev on errors
Modify website/src/mdx/rehype.ts:
Update rehypeParseCodeBlocks() to parse the new format:
function rehypeParseCodeBlocks() {
return (tree) => {
visit(tree, "element", (node, _nodeIndex, parentNode) => {
if (node.tagName === "code") {
// Parse language from className
if (node.properties.className) {
parentNode.properties.language =
node.properties.className[0]?.replace(/^language-/, "");
}
// Parse annotation format: {title}? @nocheck?
// Language is already parsed from className above
const info = parentNode.properties?.annotation || node.data;
if (info && typeof info === "string") {
const tokens = info.trim().split(/\s+/);
for (const token of tokens) {
if (token === "@nocheck") {
parentNode.properties.nocheck = true;
} else if (token && !token.startsWith("@")) {
// Non-flag token is the title
parentNode.properties.title = token;
}
}
}
}
});
};
}Also remove from website/src/mdx/rehype.ts:
- Remove
transformerTemplateVariablesimport - Remove autofill transformer logic from
rehypeShiki()
Also remove these files:
website/src/mdx/transformers.ts(template variables transformer)website/src/components/v2/AutofillCodeBlock.tsx
Update website/src/components/v2/Code.tsx:
- Remove autofill prop handling
- Remove AutofillCodeBlock wrapper
Modify website/astro.config.mjs:
import { typecheckCodeBlocks } from './src/integrations/typecheck-code-blocks';
export default defineConfig({
integrations: [
typecheckCodeBlocks(), // First - runs during config setup
generateRoutes(),
mdx({ /* ... */ }),
// ... rest
],
});Modify website/package.json:
{
"scripts": {
"postinstall": "cd scripts/typecheck-staging && pnpm install"
}
}scripts/typecheck-staging/snippets/
scripts/typecheck-staging/node_modules/
| Case | Solution |
|---|---|
| Partial code snippets | Auto-wrap in async IIFE, prepend common imports |
Relative imports (./registry) |
Declare in ambient.d.ts |
| Intentional non-compiling code | Add @nocheck to code fence |
| JavaScript blocks | Only check typescript and ts language tags |
Authors can skip type checking by adding @nocheck to the code fence:
```ts @nocheck
// This won't be type checked
const incomplete =
```Or with a title:
```ts broken-example.ts @nocheck
// This won't be type checked
const incomplete =
```[typecheck-code-blocks] Type errors found in documentation code blocks:
/website/src/content/docs/actors/state.mdx:46
Property 'foo' does not exist on type '{ count: number; }'.
| File | Action |
|---|---|
website/scripts/typecheck-staging/package.json |
Create |
website/scripts/typecheck-staging/tsconfig.json |
Create |
website/scripts/typecheck-staging/ambient.d.ts |
Create |
website/src/integrations/typecheck-code-blocks.ts |
Create |
website/src/mdx/rehype.ts |
Modify - new annotation parsing, remove autofill |
website/src/mdx/transformers.ts |
Delete |
website/src/components/v2/AutofillCodeBlock.tsx |
Delete |
website/src/components/v2/Code.tsx |
Modify - remove autofill |
website/astro.config.mjs |
Modify - add integration |
website/package.json |
Modify - add postinstall |
website/.gitignore |
Modify - add staging dirs |
- Run
pnpm installin website/ (triggers staging setup) - Run
pnpm build- should pass with current docs - Introduce a type error in a code block in
actors/state.mdx - Run
pnpm build- should fail with clear error message - Add
@nocheckto the code fence - should pass - Remove the intentional error and verify clean build
- Run
pnpm dev- verify type checking also runs in dev mode