# How Astro Markdown Files Are Parsed and Transformed in 30-seconds-of-code: A Complete Build Pipeline Guide

> Learn how Astro markdown files are parsed and transformed in the 30-seconds-of-code repository. Discover the custom build pipeline that converts MD to HTML for efficient routing and page injection.

- Repository: [Angelos Chalaris/30-seconds-of-code](https://github.com/Chalarangelo/30-seconds-of-code)
- Tags: deep-dive
- Published: 2026-02-25

---

**In the Chalarangelo/30-seconds-of-code repository, Astro does not parse Markdown at request time; instead, a custom build-time extraction pipeline converts every Markdown snippet into pre-rendered HTML strings stored as JSON, which Astro routes inject directly into pages using `set:html`.**

The 30-seconds-of-code project manages hundreds of code snippets as individual Markdown files under `content/snippets/…`, yet delivers them as optimized static HTML. This architecture separates content transformation from page rendering, allowing complex remark/rehype processing to occur during the build rather than at runtime.

## The Content Extraction Pipeline

Before Astro generates any pages, a dedicated extraction process orchestrated by [`src/lib/contentUtils/extractor.js`](https://github.com/Chalarangelo/30-seconds-of-code/blob/main/src/lib/contentUtils/extractor.js) processes every snippet file. This pipeline begins with `FileHandler.read` loading `*.md` files matching globs defined in [`src/lib/contentUtils/config.js`](https://github.com/Chalarangelo/30-seconds-of-code/blob/main/src/lib/contentUtils/config.js), then transforms them through a sophisticated Unified.js processor chain.

### Unified Processor Chain Implementation

The core transformation logic resides in [`src/lib/contentUtils/markdownParser/markdownParser.js`](https://github.com/Chalarangelo/30-seconds-of-code/blob/main/src/lib/contentUtils/markdownParser/markdownParser.js), where the `MarkdownParser.parse` method constructs a sequential processor:

1. **`remark-parse`** converts raw Markdown into a **MDAST** (Markdown Abstract Syntax Tree)
2. **`remark-gfm`** applies GitHub-Flavored Markdown extensions for tables, task lists, and strikethrough syntax
3. **Custom remark plugins** enrich the AST with syntax highlighting, CodePen embeds, and article embeds
4. **`remark-rehype`** transforms the MDAST into a **HAST** (HTML Abstract Syntax Tree)
5. **Rehype plugins** perform post-processing:
   - `rehype-unwrap-images` removes paragraph wrappers from images
   - `loadWebComponents` prepares custom element hydration
   - `linkInlineCode` converts inline code references into cross-linked snippet URLs
   - `safeguardExternalLinks` injects `rel="noopener noreferrer"` and `target="_blank"` attributes
   - `transformAdmonitions` converts `!!! note` and `!!! warning` blocks into styled `<div>` elements
   - `transformHeadings` assigns deterministic `id` attributes for anchor links
   - `transfomImagePaths` prefixes asset URLs with `/assets/…` paths
   - `wrapTables` encloses tables in `<div class="table-wrapper">` for responsive styling
6. **`rehype-stringify`** serializes the final HAST into HTML strings

### JSON Storage Strategy

The pipeline output is captured by [`src/lib/contentUtils/modelWorkers/snippet.js`](https://github.com/Chalarangelo/30-seconds-of-code/blob/main/src/lib/contentUtils/modelWorkers/snippet.js), which generates two HTML fields—`descriptionHtml` and `fullDescriptionHtml`—alongside metadata, structured data, and table-of-contents arrays. This payload is written to `.content/pages/[lang]/s/[snippet].json`, creating a static data layer that decouples content processing from page rendering.

## Astro Static Site Generation

Astro routes consume the pre-generated JSON rather than raw Markdown, eliminating runtime parsing overhead.

### Route-Level Data Loading

The dynamic route at `src/astro/pages/[lang]/s/[snippet].astro` implements `getStaticPaths` to load the JSON payloads at build time:

```astro
---
export async function getStaticPaths() {
  const pagePath = path.join(
    process.cwd(),
    '.content',
    'pages',
    '[lang]',
    's',
    '[snippet].json'
  );
  const pageData = await fs.readFile(pagePath, 'utf8').then(JSON.parse);
  return Object.values(pageData);
}
---

```

This approach generates static paths for every language-specific snippet variant during the build, ensuring zero server-side computation on request.

### Component HTML Injection

The `SnippetContent.astro` component (`src/astro/components/SnippetContent.astro`) receives the snippet object and injects the sanitized HTML directly:

```astro
---
const { descriptionHtml, fullDescriptionHtml } = Astro.props.snippet;
---
<div class="description" set:html={descriptionHtml} />
<div class="full-description" set:html={fullDescriptionHtml} />

```

Because the extraction pipeline has already processed security-sensitive transformations—such as safeguarding external links and unwrapping images—Astro can render this markup without additional sanitization.

## Transformation Examples

### Raw Markdown Input

A snippet author writes standard Markdown with frontmatter:

```markdown

# Array Shuffle

Shuffle an array in place using the Fisher‑Yates algorithm.

```js
function shuffle(arr) {
  for (let i = arr.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [arr[i], arr[j]] = [arr[j], arr[i]];
  }
  return arr;
}

```

> **Note:** This modifies the original array.

```

### Processed HTML Output

After `MarkdownParser.parse` processes the file through the Unified chain, the output becomes:

```html
<h2 id="array-shuffle">Array Shuffle</h2>
<p>Shuffle an array in place using the Fisher‑Yates algorithm.</p>
<pre class="shiki" style="background:#1e1e1e"><code><span class="line"><span class="token keyword">function</span> shuffle(arr) {</span>
<span class="line">  <span class="token keyword">for</span> (<span class="token keyword">let</span> i = arr.length - <span class="token number">1</span>; i > <span class="token number">0</span>; i--) {</span>
<span class="line">    <span class="token keyword">const</span> j = <span class="token builtin">Math</span>.floor(<span class="token builtin">Math</span>.random() * (i + <span class="token number">1</span>));</span>
<span class="line">    [arr[i], arr[j]] = [arr[j], arr[i]];</span>
<span class="line">  }</span>
<span class="line">  <span class="token keyword">return</span> arr;</span>
<span class="line">}</span></code></pre>
<div class="admonition note"><p><strong>Note:</strong> This modifies the original array.</p></div>

```

## Summary

- **Pre-processing architecture**: Markdown files in `content/snippets/` are transformed at build time by [`src/lib/contentUtils/markdownParser/markdownParser.js`](https://github.com/Chalarangelo/30-seconds-of-code/blob/main/src/lib/contentUtils/markdownParser/markdownParser.js), not by Astro at runtime.
- **Unified plugin pipeline**: The parser chains `remark-parse`, `remark-gfm`, custom plugins, `remark-rehype`, and multiple rehype transforms including `transformAdmonitions`, `safeguardExternalLinks`, and `wrapTables`.
- **Static JSON generation**: Processed HTML is stored in `.content/pages/[lang]/s/[snippet].json` alongside metadata and structured data.
- **Direct HTML injection**: Astro routes in `src/astro/pages/[lang]/s/[snippet].astro` load this JSON and render content via `set:html` in `SnippetContent.astro`, ensuring optimal performance.

## Frequently Asked Questions

### Why doesn't Astro parse Markdown directly in this architecture?

Direct Markdown parsing would require running the entire Unified.js processor chain (remark-parse, rehype plugins, syntax highlighting) at request time or during on-demand generation, significantly increasing server load. By pre-processing in [`src/lib/contentUtils/extractor.js`](https://github.com/Chalarangelo/30-seconds-of-code/blob/main/src/lib/contentUtils/extractor.js), the project serves static HTML instantly while maintaining complex transformations like admonition blocks and cross-linking between snippets.

### Where are the custom remark and rehype plugins located?

Custom transformation plugins reside in `src/lib/contentUtils/markdownParser/plugins/`, including implementations for `transformAdmonitions`, `linkInlineCode`, and `transfomImagePaths`. These plugins manipulate the MDAST and HAST trees before `rehype-stringify` generates the final HTML output.

### How does the pipeline handle syntax highlighting?

The `MarkdownParser.parse` method integrates syntax highlighting during the rehype phase using either **Shiki** or **Prism**, depending on configuration. The highlighter transforms code blocks into styled `<pre><code>` elements with proper token spans and background themes, storing the result in the `descriptionHtml` and `fullDescriptionHtml` fields of the generated JSON.

### What is the purpose of the `.content` directory?

The `.content` directory acts as a build cache containing pre-rendered JSON representations of every snippet. Files like `.content/pages/[lang]/s/[snippet].json` decouple the content extraction phase from Astro's rendering phase, allowing the site to rebuild pages quickly without re-parsing Markdown, and enabling the `getStaticPaths` function to load data synchronously from the filesystem.