# Content Source Router

> Use when dispatching a content-fetch task across the multiple sources the site reads from — local markdown under `content/`, MDX with React components under `content/mdx/`, and a headless-CMS sync under `lib/cms/`. Activate this skill whenever the task says 'render this content' or 'where does this post come from' without naming a specific source, or when adding a new source to the routing surface. Do NOT use for the actual rendering of one source (use the per-source skill — `markdown-post-frontmatter-validation`, an MDX rendering skill, or a CMS-sync skill) or for chasing a specific routing bug (use debugging).

- Skill: `jacob-balslev/content-source-router` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add jacob-balslev/content-source-router`
- Raw SKILL.md: https://api.skillmd.com/api/skills/jacob-balslev/content-source-router/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- License: MIT
- Author: jacob-balslev (https://skillmd.com/u/jacob-balslev)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/jacob-balslev/content-source-router

---


# Content Source Router

## Concept of the skill

**What it is:** The project-specific dispatch contract that decides which content source reads a requested page or post.
**Mental model:** Treat each source as an adapter behind one explicit router; the router owns selection, not rendering.
**Why it exists:** A static site with local markdown, MDX, and CMS-synced content needs one place where source precedence is visible and testable.
**What it is NOT:** It is not the source-specific parser, the CMS sync job, or contributor-facing documentation.
**Adjacent concepts:** Content source adapters, route matching, fallback policy, routing audit logs.
**One-line analogy:** It is the switchboard that connects a content request to the right reader.
**Common misconception:** A router can safely fall back to "markdown" when uncertain; unknown source selection should surface as a coverage gap.

## Coverage

- File-extension dispatch — `.md` routes to the markdown source, `.mdx` routes to the MDX source, no extension or `.cms.json` routes to the CMS source
- Content-path prefix dispatch — `content/posts/**` routes to local sources; `content/cms-synced/**` routes to the CMS source even if the file extension is `.md`
- Explicit source hints — internal callers (preview tools, manual reconciliation) pass an explicit `source` parameter that bypasses inspection
- Coverage-gap surfacing — when no detection rule matches a request, the router returns a structured "unknown source" result; it never silently picks a default
- Adding a new source — the workflow for landing a fourth source (Notion API, Sanity, etc.) without breaking the existing three (registration, routing precedence, fixture test, end-to-end content-fetch sanity)

## Philosophy of the skill

A content router is a dispatch surface that has to be exactly right or the rest of the site reads the wrong content. Every misroute is either a 404 (the user sees nothing) or a wrong-content render (the user sees a different post than the URL implies). The discipline is the same anti-default doctrine the `skill-router` applies to skills: prefer an explicit signal over an inferred one, prefer an unambiguous match over a "best guess," and prefer surfacing a coverage gap loudly over silently routing to a default.

## Routing Rules

The router evaluates four signals in priority order. The first signal that produces an unambiguous winner stops the chain.

| Priority | Signal | Source | Match rule |
|---|---|---|---|
| 1 | Explicit `source` parameter | Internal callers (preview, manual reconciliation) | Exact match against the `Source` enum. Bypasses all subsequent inspection. |
| 2 | Content-path prefix | Inbound request path | First matching prefix wins: `content/posts/` → markdown; `content/mdx/` → mdx; `content/cms-synced/` → cms. |
| 3 | File extension | Resolved file path | `.md` → markdown; `.mdx` → mdx; `.cms.json` → cms. |
| 4 | Explicit `source_hint` in query string | Trusted internal callers | Last-resort hint; surfaced as a warning in the router's audit log. |

### Coverage-gap behavior

If no signal produces a match, the router returns `{ ok: false, reason: 'unknown_source', evidence: {...} }`. It does NOT fall back to a default source. The caller must handle the unknown-source case explicitly — typically by responding HTTP 404 and logging the full request shape for human triage.

### Adding a new source

1. Add the new source's path prefix and file extension to the priority-2 and priority-3 detection tables
2. Add a source implementation in `lib/content/sources/<source>.ts` that mirrors the markdown source's interface
3. Add the new source to the `Source` enum used at priority 1
4. Add an end-to-end test that requests a fixture path matched by the new source and asserts the router selects it — without this, the router will silently fall through

## Verification

- Run unit cases for every configured source and every unknown-source branch.
- Add a fixture request for each new path prefix, file extension, and explicit source hint.
- Confirm unknown inputs return a structured failure instead of selecting a default source.

## Do NOT Use When

| Use instead | When |
|---|---|
| `markdown-post-frontmatter-validation` | The task is the actual frontmatter parsing for the markdown source — the router decides which source to read from, the source-specific skill validates and parses |
| `debugging` | A specific routing decision is wrong in production logs and you need to reproduce |
| `documentation` | The task is writing a contributor doc about the routing architecture |

