# How the Best Practices Content System Is Organized and Rendered Using Tiptap in Developer Roadmap

> Discover how the developer roadmap uses Tiptap to organize best practices content as Markdown and render interactive React JSX from a JSONContent AST.

- Repository: [Kamran Ahmed/developer-roadmap](https://github.com/kamranahmedse/developer-roadmap)
- Tags: best-practices
- Published: 2026-02-24

---

**The Developer Roadmap repository organizes best practices as Markdown files with front-matter metadata and renders them statically by parsing the content through Tiptap to generate a JSONContent AST, which is then transformed into interactive React JSX via the `GuideRenderer` class.**

The `kamranahmedse/developer-roadmap` project manages its educational best practices collection through a static-first architecture that combines file-based content storage with the Tiptap editor framework. This system leverages Markdown for authoring while using Tiptap as the canonical AST format to power rich, interactive rendering across the site.

## Content Storage and File Structure

Each best practice lives in its own folder under `src/data/best-practices/`. The system uses a hierarchical file structure where a top-level `.md` file holds global metadata and a nested `content/` directory contains individual topic files.

The file layout follows this pattern:

- `src/data/best-practices/<practice-id>/<practice-id>.md` — Contains front-matter metadata including the title, SEO description, JSON URL for interactive diagrams, and dimension settings
- `src/data/best-practices/<practice-id>/content/*.md` — Individual Markdown files for each specific topic within the practice

This organization allows the build system to import entire best practice collections as typed objects while maintaining clear separation between configuration metadata and actual content.

## Data Loading and Type Safety

The project uses **Vite’s** `import.meta.glob` to import all Markdown files at build time. Helper functions in [`src/lib/best-practice.ts`](https://github.com/kamranahmedse/developer-roadmap/blob/main/src/lib/best-practice.ts) and [`src/lib/best-practice-topic.ts`](https://github.com/kamranahmedse/developer-roadmap/blob/main/src/lib/best-practice-topic.ts) handle the conversion from file system paths to typed data structures.

The `getAllBestPractices()` function in [`src/lib/best-practice.ts`](https://github.com/kamranahmedse/developer-roadmap/blob/main/src/lib/best-practice.ts) globs all practice definitions and returns an array of **`BestPracticeFileType`** objects. Similarly, `getAllBestPracticeTopicFiles()` handles the individual topic content files. These loaders strip file extensions to generate IDs and prepare the content for rendering.

```ts
// src/lib/best-practice.ts
export async function getBestPracticeIds() {
  const files = await import.meta.glob<BestPracticeFileType>(
    '/src/data/best-practices/*/*.md',
    { eager: true },
  );
  // Convert file path → ID (strip .md)
  return Object.keys(files).map((p) => p.split('/').pop()!.replace('.md', ''));
}

```

## Tiptap Rendering Architecture

The rendering layer converts raw Markdown into interactive UI components through a three-stage pipeline: parsing to AST, sanitization, and JSX generation.

### From Markdown to TipTap AST

The system uses the **`tiptap-markdown`** extension (version 0.8.10) to parse raw Markdown into a **TipTap `JSONContent`** AST. This AST serves as the canonical intermediate representation, enabling consistent handling of complex nodes like tables, code blocks, and custom `qaSection` elements.

Astro treats each imported `.md` file as a component exposing a `<File.Content />` export. When rendered, these components feed their Markdown into the Tiptap parser, producing a structured AST that represents the document’s semantic structure rather than just its string content.

### Pre-processing and Sanitization

Because `tiptap-markdown` does not natively understand escaped brackets (`\[` and `\]`), the system runs a sanitization step before parsing. The `sanitizeMarkdown()` function in [`src/lib/markdown.ts`](https://github.com/kamranahmedse/developer-roadmap/blob/main/src/lib/markdown.ts) uses a regex to convert escaped link syntax into standard Markdown format that the parser can handle.

```ts
// src/lib/markdown.ts
export function sanitizeMarkdown(markdown: string) {
  // Turn \[link\](url) → [link](url) so tiptap-markdown can parse it
  return markdown.replace(/\\\[([^\\]+)\\\]\(([^\\]+)\)/g, '[$1]($2)');
}

```

### JSX Generation Strategies

Two renderer classes walk the TipTap AST to produce output:

- **`GuideRenderer`** ([`src/lib/guide-renderer.tsx`](https://github.com/kamranahmedse/developer-roadmap/blob/main/src/lib/guide-renderer.tsx)) — Builds a full-featured React element tree, handling custom nodes like `qaSection`, tables, and code blocks with specific UI components
- **`MarkdownRenderer`** ([`src/lib/markdown-renderer.tsx`](https://github.com/kamranahmedse/developer-roadmap/blob/main/src/lib/markdown-renderer.tsx)) — Produces plain Markdown strings for copy-to-clipboard functionality or lightweight server-side generation

Both renderers rely on an **`ordered marksOrder`** array to correctly nest marks (underline → bold → italic → textStyle → link), ensuring the final output preserves the original author’s formatting intent.

The `GuideRenderer` uses dynamic dispatch to handle different node types through a unified `renderNode()` method:

```tsx
// src/lib/guide-renderer.tsx
private renderNode(node: JSONContent): JSX.Element | null {
  const type = node.type || '';
  if (type in this) {
    // Dynamic dispatch to the method named after the node type
    // e.g. this.paragraph(node), this.heading(node), etc.
    return (this as any)[type](node);
  }
  console.warn(`Node type "${type}" is not supported.`);
  return null;
}

// Example: rendering a heading node with slug generation
private heading(node: JSONContent): JSX.Element {
  const level = node.attrs?.level || 1;
  const Tag = `h${level}` as keyof JSX.IntrinsicElements;
  const text = this.getText(node);
  const slug = slugify(text);
  return <Tag id={slug}>{this.content(node)}</Tag>;
}

```

## Static Generation with Astro

The best practices pages are generated statically at build time through Astro’s file-based routing system.

The `getStaticPaths()` function in `src/pages/best-practices/[bestPracticeId]/index.astro` calls `getAllBestPractices()` to collect every best-practice markdown file and generate the route parameters. The page receives a `bestPractice` prop containing the front-matter and a virtual component (`bestPractice.Content`).

During rendering, the system decides between two display modes:

1. **Interactive Mode** — If `bestPracticeData.jsonUrl` exists and the content is not upcoming, it renders the `FrameRenderer` component to display Balsamiq-style diagrams
2. **Markdown Mode** — Otherwise, it wraps the Tiptap-rendered content in a `<MarkdownFile>` component

```astro
---
// src/pages/best-practices/[bestPracticeId]/index.astro
import { getAllBestPractices } from '../../../lib/best-practice';
export const prerender = true;

export async function getStaticPaths() {
  const bestPractices = await getAllBestPractices();
  return bestPractices.map(p => ({
    params: { bestPracticeId: p.id },
    props: { bestPractice: p },
  }));
}
---

<BestPracticeHeader title={bestPracticeData.title} ... />
{!bestPracticeData.isUpcoming && bestPracticeData.jsonUrl ? (
  <FrameRenderer resourceType="best-practice" resourceId={bestPracticeId} />
) : (
  <MarkdownFile>
    <bestPractice.Content />
  </MarkdownFile>
)}

```

Individual topic pages follow the same pattern in `src/pages/best-practices/[bestPracticeId]/[...topicId].astro`, using `getAllBestPracticeTopicFiles()` to load specific content files and rendering them via `<file.Content />`.

## Summary

- **File-based storage**: Best practices are stored as Markdown files with front-matter in `src/data/best-practices/`, with separate folders for metadata and topic content
- **Build-time loading**: Vite’s `import.meta.glob` imports all files at build time, converting them to typed `BestPracticeFileType` objects via helper functions in [`src/lib/best-practice.ts`](https://github.com/kamranahmedse/developer-roadmap/blob/main/src/lib/best-practice.ts)
- **Tiptap parsing**: The `tiptap-markdown` extension converts Markdown to a `JSONContent` AST, with [`src/lib/markdown.ts`](https://github.com/kamranahmedse/developer-roadmap/blob/main/src/lib/markdown.ts) handling escaped bracket sanitization
- **Dual rendering strategy**: `GuideRenderer` produces rich React JSX for the UI, while `MarkdownRenderer` generates plain strings for lightweight use cases
- **Static generation**: Astro pre-renders all pages at build time using `getStaticPaths()`, dynamically choosing between interactive diagram rendering or Markdown content based on front-matter flags

## Frequently Asked Questions

### Why does the system use Tiptap instead of rendering Markdown directly?

The **Tiptap AST** (`JSONContent`) provides a structured, semantic representation of the content that enables complex UI features beyond basic HTML conversion. By parsing Markdown into this AST first, the system can handle custom nodes like `qaSection`, apply specific styling to code blocks, and manage mark nesting order (bold, italic, links) through the `marksOrder` array. This architecture separates content structure from presentation, allowing the same source Markdown to render differently depending on context.

### How does the system handle escaped Markdown characters?

Before Tiptap parses the content, the `sanitizeMarkdown()` function in [`src/lib/markdown.ts`](https://github.com/kamranahmedse/developer-roadmap/blob/main/src/lib/markdown.ts) runs a regex replacement to convert escaped brackets (`\[` and `\]`) into standard Markdown link syntax. This workaround is necessary because `tiptap-markdown@0.8.10` does not recognize escaped brackets as valid link delimiters, which would otherwise cause parsing failures for content containing literal bracket characters intended as links.

### What is the difference between `GuideRenderer` and `MarkdownRenderer`?

**`GuideRenderer`** ([`src/lib/guide-renderer.tsx`](https://github.com/kamranahmedse/developer-roadmap/blob/main/src/lib/guide-renderer.tsx)) is the full-featured renderer that walks the TipTap AST and produces React JSX elements, handling custom components like tables, headings with slugified IDs, and specialized `qaSection` layouts. **`MarkdownRenderer`** ([`src/lib/markdown-renderer.tsx`](https://github.com/kamranahmedse/developer-roadmap/blob/main/src/lib/markdown-renderer.tsx)) provides a lightweight alternative that converts the AST back to Markdown strings, useful for copy-to-clipboard features or generating plain text summaries where React component overhead is unnecessary.

### How are best practice pages generated at build time?

During the Astro build process, `getStaticPaths()` in `src/pages/best-practices/[bestPracticeId]/index.astro` executes `getAllBestPractices()` to discover all available content. It returns a mapping of route parameters and props for each best practice. Astro then renders each page, executing the virtual `<bestPractice.Content />` component which internally runs the Markdown through the Tiptap parser and `GuideRenderer` to produce the final static HTML.