skills/markdown-content/SKILL.md
Guide for building markdown content pages (blogs, legal, changelogs) in Next.js using fumadocs-mdx, rehype-pretty-code, and shiki. Covers MDX collections, self-contained prose typography, syntax-highlighted code blocks, and the component mappings that keep rendered markdown and custom React code components visually consistent. No documentation UI framework required.
npx skillsauth add joyco-studio/skills markdown-contentInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
This skill defines how to build content-driven pages (blog posts, changelogs, legal pages, knowledge bases) in a Next.js App Router project using MDX.
The goal is visual and behavioral consistency between:
.mdx files (processed at build time)| Layer | Library | Purpose |
|---|---|---|
| Content framework | fumadocs-mdx | MDX collection definitions, frontmatter schemas, build-time processing |
| Content runtime | fumadocs-core | Source loader, page tree, slug resolution |
| Syntax highlighting | shiki + rehype-pretty-code | Dual-theme code highlighting at build time (MDX) and runtime (components) |
| Schema validation | zod | Typed frontmatter with defaults and optional fields |
| Styling | Tailwind CSS v4 | Utility-first styles, custom prose CSS, code block styles |
| Framework | Next.js (App Router) | File-based routing with catch-all [[...slug]] for content pages |
npm install fumadocs-mdx fumadocs-core shiki rehype-pretty-code zod
├── app/
│ ├── layout.tsx # Root layout
│ ├── styles/
│ │ ├── globals.css # Tailwind + stylesheet imports (order matters)
│ │ ├── prose.css # Self-contained typography for .prose class
│ │ ├── shiki.css # Code block styling (themes, line numbers, highlights)
│ │ └── theming.css # Color variables (light/dark) — imported first
│ └── (content)/ # Route group for content pages
│ ├── layout.tsx # Content layout wrapper
│ └── [[...slug]]/
│ └── page.tsx # Catch-all page rendering MDX content
├── content/
│ ├── meta.json # Top-level page ordering
│ ├── index.mdx # Landing page
│ └── <category>/ # e.g. blog/, legal/, changelog/
│ ├── meta.json # Category page ordering
│ └── <page>.mdx # Individual content pages
├── components/ # Code block components (see Code Block Components)
├── lib/
│ ├── source.ts # fumadocs-core source loader
│ ├── shiki.ts # Shared shiki config, transformers, highlightCode()
│ └── cn.ts # clsx + tailwind-merge utility
├── mdx-components.tsx # MDX element → React component mappings
└── source.config.ts # fumadocs-mdx collection + rehype pipeline
source.config.tsDefines two things:
defineDocs() pointing at the content/ directory with a Zod-extended frontmatter schema.defineConfig() with rehype-pretty-code replacing the default fumadocs highlighter.Key patterns:
plugins.shift() inside rehypePlugins to remove the built-in fumadocs syntax highlighter, then plugins.push() rehype-pretty-code.onVisitTitle to add not-prose to code block title elements so prose typography doesn't affect them.next.config.tsWrap the Next.js config with createMDX() from fumadocs-mdx/next.
lib/source.tsCreate a loader() from fumadocs-core/source with a baseUrl and the collection's .toFumadocsSource(). Export the source and a Page type via InferPageType.
Every .mdx file starts with YAML frontmatter validated by the Zod schema in source.config.ts. Imports go after the closing ---. React components can be used inline alongside standard markdown. Code blocks use fenced syntax with language tags, and a title="filename.tsx" meta string adds a title bar.
meta.jsonControls page ordering within a directory — an array of slug strings:
{
"pages": ["index", "terms-of-service", "privacy-policy"]
}
The [[...slug]]/page.tsx route resolves slugs via source.getPage(params.slug), returns notFound() if missing, and renders:
<article className="prose">
<h1>{page.data.title}</h1>
<p className="text-muted-foreground">{page.data.description}</p>
<MDX components={getMDXComponents()} />
</article>
Export generateStaticParams using source.generateParams() for static generation.
lib/shiki.tsCentralizes all syntax highlighting config. Used by both the MDX pipeline (build-time) and custom code components (runtime).
This module should export:
A language map — Record<string, BuiltinLanguage> mapping file extensions (ts, tsx, sh, etc.) to shiki language identifiers. Plus a getLanguageFromExtension() helper.
Shiki transformers — run during MDX compilation, attaching custom properties to AST nodes that components read at render time. Attach the raw source string (for copy button) so the component mapping can use it.
highlightCode(code, language) — an async function using codeToHtml() with the same dual themes as the MDX pipeline. This is the consistency mechanism — both build-time and runtime highlighting use identical shiki config and produce the same data attributes (data-line-numbers, data-line), ensuring matching visual output everywhere.
mdx-components.tsxMaps HTML elements from the MDX compiler to React components. This is the bridge between raw markdown and the component system.
Key mappings:
code — Renders a styled inline <code> element.
pre — Reads transformer metadata from props. Renders a <pre> with a CopyButton overlay using the raw source text.
img — Optionally wrap with Next.js Image for optimization.
The flow: author writes fenced code block → rehype-pretty-code + shiki produce <pre><code> with syntax tokens → shiki transformers attach metadata → MDX calls these mappings → components read metadata and render accordingly.
Build these client components for the code display system:
CodeBlockSingle code display. Accepts highlightedCode (HTML string), language, optional title, rawCode (for copy), maxHeight, wrap. Renders a <figure data-rehype-pretty-code-figure> with optional <figcaption> title bar, the highlighted HTML via dangerouslySetInnerHTML, and a CopyButton.
CodeBlockTabsMulti-file tabbed code. Accepts an array of { filename, highlightedCode, rawCode } tabs. Uses controlled tab state. Renders tabs in a <figcaption> with a CopyButton for the active tab.
CopyButtonClipboard button with visual feedback. Uses a useCopyToClipboard hook that calls navigator.clipboard.writeText() and shows a checkmark for ~2 seconds. Positioned absolutely (top-right) with opacity transition on group hover.
globals.css — Import Order@import 'tailwindcss';
@import './theming.css';
@import './prose.css';
@import './shiki.css';
theming.css must come first — it defines the color variables that prose and shiki consume.
theming.css — Color VariablesDefine light and dark mode variables consumed by shiki.css and component classes:
--color-code — code block background--color-code-foreground — code block text--color-code-highlight — highlighted line backgroundprose.css — Self-Contained TypographyA hand-rolled .prose class that doesn't depend on @tailwindcss/typography or any UI framework. Use :where() selectors to keep specificity at zero. Use .not-prose as an escape hatch for embedded components.
Cover at minimum: body color and line-height, headings (h1–h6), paragraphs, links, strong, lists (ul/ol/li), blockquotes, horizontal rules, tables, and code figures (figure[data-rehype-pretty-code-figure]).
Design decisions:
--foreground via color-mix) for softer reading--foreground with tighter line-heightshiki.css — Code Block StylesStyle all code blocks uniformly — MDX-rendered and component-rendered both use the same data attributes.
Key rules:
.dark, swap color to var(--shiki-dark) on [data-rehype-pretty-code-figure] span. Light theme is the default; shiki embeds both color sets as CSS variables.[data-rehype-pretty-code-figure] gets background from --color-code, rounded corners, overflow hidden.[data-line-numbers] [data-line]::before. Sticky left position so they stay visible on horizontal scroll.[data-highlighted-line] gets a subtle background and left border.[data-rehype-pretty-code-title] styled with mono font, muted color, bottom border.[data-wrap='true'] code enables white-space: pre-wrap.The system ensures a fenced markdown code block and a <CodeBlock> React component produce identical visual output. This is achieved through:
highlightCode() use the same dual-theme pair.[data-rehype-pretty-code-figure] elements styled by shiki.css.data-line, data-line-numbers, data-highlighted-line, data-wrap are used by both paths.--color-code family from theming.css applies uniformly.CopyButton component.fumadocs-mdx, fumadocs-core, shiki, rehype-pretty-code, zodsource.config.ts with frontmatter schema and rehype-pretty-code pipelinenext.config.ts with createMDX()lib/source.ts with the source loaderlib/shiki.ts with language map, transformers, and highlightCode()mdx-components.tsx mapping pre and code to custom componentsCodeBlock, CodeBlockTabs, CopyButton)prose.css (self-contained typography) and shiki.css (code block styling)theming.css including --color-code familymeta.json files[[...slug]]/page.tsx routeglobals.css in correct order (theming → prose → shiki)tools
Add sound effects, UI audio, and ambient sound to a web app using the @joycostudio/suno library. Use when the user wants to play audio on button clicks, hover states, game events, or ambient loops, and when they mention @joycostudio/suno, Suno, AudioSource, Voice, or Mixer.
tools
Analyze a Chrome DevTools Performance trace JSON file for performance anomalies, producing a structured audit report with critical issues, warnings, metrics, timeline hotspots, and actionable recommendations.
development
Analyze a bye-thrash layout thrashing report array. Parses stack traces, identifies user-code functions causing forced reflows, locates the offending style-write → layout-read pairs in source files, and produces a structured fix-suggestion report.
development
Author or refactor a skill in this repo. Use when the user asks to "create a skill", "write a skill", "add a new skill", "document this as a skill", or to restructure an existing SKILL.md (split it up, slim it down, fix the frontmatter). Covers frontmatter conventions, file layout, and the rule for splitting deep reference material into linked docs instead of bloating SKILL.md.