Markdown Configuration
Stati uses markdown-it as its markdown processor. The engine is configured with sensible defaults, and you can extend it with plugins or customize rendering behavior.
Configuration Options
Stati provides two options for markdown customization:
// stati.config.js
import { defineConfig } from '@stati/core';
export default defineConfig({
markdown: {
// Array of markdown-it plugins
plugins: [
'anchor', // Plugin name only
['prism', { options }], // Plugin with options
],
// Custom configuration function
configure: (md) => {
// Receive markdown-it instance
// Customize rendering, add plugins, etc.
},
},
});
Default Settings
Stati creates markdown-it with these hardcoded settings (not configurable):
- HTML enabled: HTML tags are allowed in markdown source
- Linkify enabled: URLs are automatically converted to links
- Typographer enabled: Smart quotes and typography replacements
These defaults provide a good balance of functionality and security for most use cases.
Markdown Plugins
The plugins option accepts an array of markdown-it plugins. Stati automatically prepends markdown-it- to plugin names.
Plugin Format
Simple plugin (string):
export default defineConfig({
markdown: {
plugins: ['anchor', 'footnote', 'emoji'],
},
});
Plugin with options (array):
export default defineConfig({
markdown: {
plugins: [
['anchor', {
slugify: (s) => s.toLowerCase().trim().replace(/[\s\W-]+/g, '-'),
}],
['prism', {
defaultLanguage: 'javascript',
}],
],
},
});
Plugin Installation
Plugins must be installed as npm dependencies:
npm install markdown-it-anchor
npm install markdown-it-footnote
npm install @widgetbot/markdown-it-prism
Common Plugins
| Plugin | Package | Purpose |
|---|---|---|
anchor |
markdown-it-anchor |
Add IDs to headings |
prism |
@widgetbot/markdown-it-prism |
Syntax highlighting |
toc-done-right |
markdown-it-toc-done-right |
Table of contents |
footnote |
markdown-it-footnote |
Footnote support |
emoji |
markdown-it-emoji |
Emoji shortcuts :smile: |
external-links |
markdown-it-external-links |
External link attributes |
container |
markdown-it-container |
Custom containers |
attrs |
markdown-it-attrs |
Add attributes to elements |
Example: Complete Plugin Setup
export default defineConfig({
markdown: {
plugins: [
// Add IDs to headings for deep linking
['anchor', {
slugify: (s) => s.toLowerCase().trim().replace(/[\s\W-]+/g, '-'),
permalink: false,
}],
// Generate table of contents
'toc-done-right',
// Syntax highlighting with Prism.js
['prism', {
defaultLanguage: 'javascript',
}],
// Add target="_blank" to external links
['external-links', {
externalTarget: '_blank',
internalDomains: ['stati.build'],
}],
// Support for ::: container syntax
['container', 'warning'],
['container', 'tip'],
// Footnote support
'footnote',
// Emoji shortcuts
'emoji',
],
},
});
Custom Configuration
The configure option accepts a function that receives the markdown-it instance. This runs after plugins are loaded, allowing you to override plugin behavior.
Basic Customization
export default defineConfig({
markdown: {
configure: (md) => {
// Modify markdown-it options
md.set({ breaks: true });
// Configure linkify
md.linkify.set({ fuzzyEmail: false });
// Add plugins programmatically
md.use(somePlugin, options);
},
},
});
Custom Renderer Rules
Customize how specific markdown elements are rendered:
export default defineConfig({
markdown: {
configure: (md) => {
// Customize heading rendering
md.renderer.rules.heading_open = (tokens, idx) => {
const level = tokens[idx].tag;
const label = tokens[idx + 1].content;
return `<${level} class="heading heading-${level}" data-label="${label}">`;
};
// Customize link rendering
const defaultLinkOpen = md.renderer.rules.link_open ||
((tokens, idx, options, env, self) => self.renderToken(tokens, idx, options));
md.renderer.rules.link_open = (tokens, idx, options, env, self) => {
const aIndex = tokens[idx].attrIndex('href');
if (aIndex >= 0) {
const href = tokens[idx].attrs[aIndex][1];
// Add class to external links
if (href.startsWith('http')) {
tokens[idx].attrPush(['class', 'external-link']);
tokens[idx].attrPush(['rel', 'noopener noreferrer']);
}
}
return defaultLinkOpen(tokens, idx, options, env, self);
};
},
},
});
Custom Block Syntax
Create custom container syntax:
import container from 'markdown-it-container';
export default defineConfig({
markdown: {
plugins: [
['container', 'warning'],
['container', 'tip'],
['container', 'danger'],
],
},
});
Usage in markdown:
::: warning
This is a warning message
:::
::: tip
This is a helpful tip
:::
Syntax Highlighting
Stati doesn’t provide built-in syntax highlighting. You have two options:
Option 1: Client-Side (Recommended)
Use the markdown-it-prism plugin with Prism.js:
Install:
npm install @widgetbot/markdown-it-prism prismjs
Configure:
export default defineConfig({
markdown: {
plugins: [
['prism', { defaultLanguage: 'javascript' }],
],
},
});
Add to layout:
<link rel="stylesheet" href="/path/to/prism.css">
<script src="/path/to/prism.js"></script>
Pros: Simple setup, many themes available, works well for static sites Cons: Requires JavaScript, flash of unstyled code before JS loads
Option 2: Server-Side
Use any server-side highlighter in the configure function:
import { getHighlighter } from 'shiki';
export default defineConfig({
markdown: {
configure: async (md) => {
const highlighter = await getHighlighter({
theme: 'nord',
langs: ['javascript', 'typescript', 'python', 'html', 'css'],
});
md.options.highlight = (code, lang) => {
if (lang && highlighter.getLoadedLanguages().includes(lang)) {
return highlighter.codeToHtml(code, { lang });
}
return ''; // Return empty string for unsupported languages
};
},
},
});
Pros: No JavaScript required, no flash of unstyled code Cons: More complex setup, increases build time
Front Matter
Stati automatically parses YAML front matter from markdown files using gray-matter. This is not configurable.
Example:
---
title: My Page Title
description: Page description
date: 2024-01-15
tags: [stati, markdown]
author: John Doe
---
# Your content here
All front matter fields are available in templates:
<h1><%= stati.page.title %></h1>
<p><%= stati.page.description %></p>
<time><%= new Date(stati.page.date).toLocaleDateString() %></time>
Best Practices
Plugin Selection
- Install only what you need - Each plugin adds processing overhead
- Check compatibility - Ensure plugins work with your markdown-it version
- Test thoroughly - Some plugins may conflict with each other
Custom Rendering
- Use configure for overrides - The configure function runs after plugins
- Preserve defaults - Store default renderer before overriding
- Handle edge cases - Check for null/undefined values in custom rules
Performance
- Limit plugins - Too many plugins slow down builds
- Cache compiled results - Stati caches in production automatically
- Optimize images - Large images slow down markdown parsing
Complete Example
A production-ready markdown configuration:
import { defineConfig } from '@stati/core';
export default defineConfig({
markdown: {
plugins: [
// Heading anchors
['anchor', {
slugify: (s) => s.toLowerCase().trim().replace(/[\s\W-]+/g, '-'),
}],
// Table of contents
'toc-done-right',
// Syntax highlighting
['prism', { defaultLanguage: 'javascript' }],
// External links
['external-links', {
externalTarget: '_blank',
internalDomains: ['stati.build'],
}],
// Custom containers
['container', 'warning'],
['container', 'tip'],
// Footnotes
'footnote',
],
configure: (md) => {
// Add custom CSS classes to links
const defaultLinkOpen = md.renderer.rules.link_open ||
((tokens, idx, options, env, self) => self.renderToken(tokens, idx, options));
md.renderer.rules.link_open = (tokens, idx, options, env, self) => {
const aIndex = tokens[idx].attrIndex('href');
if (aIndex >= 0) {
const href = tokens[idx].attrs[aIndex][1];
if (href.startsWith('http')) {
tokens[idx].attrPush(['class', 'external-link']);
} else {
tokens[idx].attrPush(['class', 'internal-link']);
}
}
return defaultLinkOpen(tokens, idx, options, env, self);
};
// Enable line breaks
md.set({ breaks: true });
},
},
});
Next Steps
- Learn about Templates & Layouts for rendering markdown
- Explore Markdown concepts for content authoring
- See API Reference for programmatic usage