Markdown Pipeline

Stati uses Markdown-It as its core markdown processor, enhanced with carefully selected plugins and customization options. The pipeline is designed to be fast, extensible, and developer-friendly while supporting modern markdown features.

Core Features

Standard Markdown

Stati supports all standard Markdown syntax out of the box:

# Headings

## Subheadings

### And so on...

**Bold text** and _italic text_

[Links](https://example.com) and [internal links](/getting-started/introduction/)

- Unordered lists
- With multiple items

1. Ordered lists
2. With numbers

> Blockquotes for emphasis
> Can span multiple lines

`Inline code` and:

```javascript
// Code blocks with syntax highlighting
function hello() {
  console.log('Hello, Stati!');
}
```

### Front Matter

Every markdown file can include YAML front matter:

```markdown
---
title: 'My Page Title'
description: 'A brief description for SEO'
date: '2024-01-15'
tags: ['stati', 'markdown', 'documentation']
author: 'John Doe'
draft: false
layout: 'post'
---

# Your content starts here...

Syntax Highlighting

Code blocks automatically get syntax highlighting using Prism.js:

```typescript
interface StatiConfig {
  site: {
    title: string;
    baseUrl: string;
  };
  markdown?: MarkdownConfig;
}
```

```css
.highlight {
  background: #f0f8ff;
  border-left: 4px solid #0066cc;
  padding: 1rem;
}
```

```bash
npm install @stati/core
npm run build
```

Enhanced Features

Table Support

Create tables using standard markdown table syntax:

| Feature     | Stati          | Other SSGs  |
| ----------- | -------------- | ----------- |
| TypeScript  | ✅ First-class | ⚠️ Add-on   |
| ISG         | ✅ Built-in    | ❌ Manual   |
| Fast Builds | ✅ Integrated  | ⚠️ Optional |
| Performance | ⚡ Fast        | 🐌 Varies   |

Renders as:

Feature Stati Other SSGs
TypeScript ✅ First-class ⚠️ Add-on
ISG ✅ Built-in ❌ Manual
Fast Builds ✅ Integrated ⚠️ Optional
Performance ⚡ Fast 🐌 Varies

Task Lists

Create interactive checkboxes:

- [x] Completed task
- [ ] Pending task
- [x] Another completed task
- [ ] Future task
  • [x] Completed task
  • [ ] Pending task
  • [x] Another completed task
  • [ ] Future task

Auto-linking

URLs and email addresses are automatically converted to links:

Visit https://stati.dev for more information.
Contact us at hello@stati.dev for support.

Typography Enhancements

Smart quotes, em-dashes, and ellipses:

"Smart quotes" and 'apostrophes'
En-dash (--) and em-dash (---)
Ellipsis (...)

Markdown Configuration

Base Configuration

Stati uses Markdown-It with the following default configuration:

  • HTML tags - Enabled for raw HTML in markdown
  • Linkify - Auto-convert URLs to clickable links
  • Typographer - Smart quotes and other typography enhancements
// stati.config.js
import { defineConfig } from '@stati/core';

export default defineConfig({
  markdown: {
    plugins: [
      'anchor',           // markdown-it-anchor
      'footnote',        // markdown-it-footnote
      ['mark', { /* options */ }], // markdown-it-mark with options
    ]
  },
});

Note: You need to install the plugins separately via npm:

npm install markdown-it-anchor markdown-it-footnote markdown-it-mark

Configuration

Basic Configuration

Configure markdown processing in stati.config.js:

import { defineConfig } from '@stati/core';

export default defineConfig({
  markdown: {
    // Markdown-It options
    options: {
      html: true, // Enable HTML tags in source
      linkify: true, // Auto-convert URL-like text to links
      typographer: true, // Enable smart quotes and other typographic substitutions
      breaks: false, // Convert '\n' in paragraphs into <br>
      xhtmlOut: false, // Use '/' to close single tags (<br />)
    },

    // Plugin configuration
    plugins: {
      anchor: {
        permalink: true,
        permalinkBefore: true,
        permalinkSymbol: '🔗',
      },
      toc: {
        includeLevel: [1, 2, 3],
        containerClass: 'table-of-contents',
      },
    },
  },
});

Advanced Configuration

export default defineConfig({
  markdown: {
    // Custom renderer modifications
    setup(md) {
      // Add custom rules
      md.renderer.rules.code_inline = (tokens, idx) => {
        const token = tokens[idx];
        return `<code class="inline-code">${token.content}</code>`;
      };

      // Add custom plugins
      md.use(customPlugin, options);

      // Modify existing rules
      const defaultCodeBlockRule = md.renderer.rules.code_block;
      md.renderer.rules.code_block = (tokens, idx, options, env) => {
        const token = tokens[idx];
        const langClass = token.info ? ` class="language-${token.info}"` : '';
        return `<pre${langClass}><code>${token.content}</code></pre>`;
      };
    },
  },
});

Custom Plugins

Creating a Plugin

// plugins/custom-alerts.js
function alertPlugin(md, options = {}) {
  const defaultRender =
    md.renderer.rules.blockquote_open ||
    function (tokens, idx, options, env, renderer) {
      return renderer.renderToken(tokens, idx, options);
    };

  md.renderer.rules.blockquote_open = function (tokens, idx, options, env, renderer) {
    const token = tokens[idx];
    const nextToken = tokens[idx + 1];

    if (nextToken && nextToken.content.startsWith('[!')) {
      const match = nextToken.content.match(/^\[!(.*?)\]/);
      if (match) {
        const alertType = match[1].toLowerCase();
        return `<div class="alert alert-${alertType}">`;
      }
    }

    return defaultRender(tokens, idx, options, env, renderer);
  };
}

// Usage in config
export default defineConfig({
  markdown: {
    setup(md) {
      md.use(alertPlugin);
    },
  },
});

Using Custom Alerts

> [!NOTE]
> This is a note alert.

> [!WARNING]
> This is a warning alert.

> [!TIP]
> This is a tip alert.

Content Processing

Front Matter Processing

Front matter is parsed and made available to templates:

---
title: 'Advanced Guide'
lastModified: '2024-01-15'
contributors: ['John', 'Jane']
difficulty: 'advanced'
estimatedTime: '30 minutes'
---

# Content here...

Access in templates:

<article class="<%= stati.propValue('difficulty', `difficulty-${stati.page.difficulty}`) %>">
  <header>
    <h1><%= stati.page.title %></h1>
    <div class="meta">
      <span>Difficulty: <%= stati.page.difficulty %></span>
      <span>Est. time: <%= stati.page.estimatedTime %></span>
      <span>Last updated: <%= new Date(stati.page.lastModified).toLocaleDateString() %></span>
    </div>
  </header>

  <div class="content">
    <%~ stati.content %>
  </div>
</article>

Content Excerpts

Automatically generate excerpts:

---
title: 'My Post'
---

This is the excerpt. It appears before the first <!--more--> comment.

<!--more-->

This is the full content that appears after the excerpt separator.

Markdown in Templates

Process markdown within templates:

<%
// Process markdown at build time
const additionalContent = `
## Dynamic Section

This content is **processed** at build time.
`;
%>

<div class="dynamic-content">
  <%~ stati.renderMarkdown(additionalContent) %>
</div>

Performance Optimization

Caching

Markdown processing is cached by default:

  • Parsed content is cached based on file modification time
  • Rendered HTML is cached until source changes
  • Plugin outputs are cached when possible

Build-time Processing

All markdown processing happens at build time:

// This runs during build, not at runtime
export default defineConfig({
  markdown: {
    setup(md) {
      // Expensive processing is done once during build
      md.use(expensivePlugin, {
        // Pre-computed options
        dictionary: loadLargeDictionary(),
        rules: compileComplexRules(),
      });
    },
  },
});

Incremental Regeneration

With ISG, only changed markdown files are reprocessed:

export default defineConfig({
  isg: {
    ttlSeconds: 3600, // Cache for 1 hour

    // Invalidation rules
    invalidation: {
      // Invalidate when markdown files change
      patterns: ['**/*.md'],

      // Invalidate dependent pages
      dependencies: {
        'blog/index.md': ['blog/**/*.md'],
      },
    },
  },
});

Advanced Features

Custom Markdown Extensions

Create domain-specific extensions:

// Extension for API documentation
function apiDocPlugin(md) {
  md.inline.ruler.before('emphasis', 'api_endpoint', function (state, silent) {
    const match = state.src.slice(state.pos).match(/^@(GET|POST|PUT|DELETE)\s+(.+?)@/);
    if (!match) return false;

    if (!silent) {
      const token = state.push('api_endpoint', '', 0);
      token.content = match[0];
      token.meta = { method: match[1], endpoint: match[2] };
    }

    state.pos += match[0].length;
    return true;
  });

  md.renderer.rules.api_endpoint = function (tokens, idx) {
    const token = tokens[idx];
    const { method, endpoint } = token.meta;
    return `<span class="api-endpoint method-${method.toLowerCase()}">
      <code class="method">${method}</code>
      <code class="endpoint">${endpoint}</code>
    </span>`;
  };
}

Usage:

Use the @GET /api/users@ endpoint to fetch user data.
Send a @POST /api/users@ request to create a new user.

Content Transformation

Transform content based on context:

export default defineConfig({
  markdown: {
    setup(md) {
      // Transform relative images to absolute
      const defaultImageRule = md.renderer.rules.image;
      md.renderer.rules.image = function (tokens, idx, options, env) {
        const token = tokens[idx];
        const src = token.attrGet('src');

        if (src && !src.startsWith('http') && !src.startsWith('/')) {
          token.attrSet('src', `/images/${src}`);
        }

        return defaultImageRule(tokens, idx, options, env);
      };
    },
  },
});

Testing and Debugging

Markdown Debugging

export default defineConfig({
  markdown: {
    setup(md) {
      if (process.env.NODE_ENV === 'development') {
        // Log markdown processing
        md.core.ruler.push('debug', function (state) {
          console.log('Processing:', state.src.slice(0, 100));
          return true;
        });
      }
    },
  },
});

Content Validation

// Validate markdown content during build
export default defineConfig({
  hooks: {
    beforeRender(page) {
      // Check for common issues
      if (page.content.includes('TODO')) {
        console.warn(`TODO found in ${page.path}`);
      }

      // Validate front matter
      if (!page.title) {
        throw new Error(`Missing title in ${page.path}`);
      }
    },
  },
});

The markdown pipeline is one of Stati’s core strengths, providing powerful content processing while maintaining excellent performance. Next, learn about Incremental Static Generation to understand how Stati optimizes builds and caching.