# o3-on-complex-markdown-parsing-in-next
You absolutely can—whether you stick with “plain” Markdown or move to MDX, the trick is to parse your document into an AST, split it wherever you see an `h2`, and then render each chunk as its own “card” or “tab.” Here are two equally viable recipes:
---
## 1. Plain-Markdown + `remark` (or `unified`)
1. **Parse & split**
Use `remark-parse` to build a Markdown AST, then walk it and group all nodes between each level-2 heading into an array of “sections.”
2. **Serialize & render**
For each section you now have:
```js
{
title: 'My Section Title', // the text of the H2
nodes: [ ... ], // all AST nodes under that H2 (until the next H2)
}
```
You can then feed each `nodes` back through `remark-rehype` → `rehype-react` (or `rehype-stringify`) to turn it into Reactable JSX (or an HTML string).
3. **Wrap in cards or tabs**
```jsx
{sections.map((sec, i) => (
<div key={i} className="rounded-lg shadow p-4 mb-6">
<h2 className="text-xl font-bold mb-2">{sec.title}</h2>
<div className="prose">
{/* your <RehypeReact> output here */}
</div>
</div>
))}
```
Or, if you prefer tabs, render the `sec.title` in your tab list and the content in the tab panel.
### Minimal example of the splitter plugin
```js
import { visit } from 'unist-util-visit'
// remark plugin to split at each H2
export default function remarkSectionize() {
return (tree) => {
const sections = []
let current = { title: null, nodes: [] }
visit(tree, (node) => {
if (node.type === 'heading' && node.depth === 2) {
// whenever you hit a new H2, push the last section...
if (current.title !== null) sections.push(current)
// and start a fresh one
current = {
title: node.children.map(n => n.value).join(''),
nodes: []
}
} else {
current.nodes.push(node)
}
})
// push the final section
if (current.title !== null) sections.push(current)
// replace tree with a single node that carries your sections
tree.children = [{ type: 'root', data: { sections } }]
}
}
```
In your Next.js `getStaticProps` you’d run:
```js
import remark from 'remark'
import remarkSectionize from './remark-sectionize'
import remarkRehype from 'remark-rehype'
import rehypeStringify from 'rehype-stringify'
export async function getStaticProps() {
const raw = await fs.readFile('docs/mydoc.md', 'utf-8')
const processor = remark()
.use(remarkSectionize)
.use(remarkRehype)
.use(rehypeStringify)
const file = await processor.process(raw)
// file.data.sections → your array of { title, html }
return { props: { sections: file.data.sections } }
}
```
---
## 2. MDX + a wrapper component
If you convert your `.md` to `.mdx`, you can *literally* wrap each `##` block in a React component:
1. **Install**
```bash
npm install @next/mdx @mdx-js/loader
```
2. **next.config.js**
```js
const withMDX = require('@next/mdx')({
extension: /\.mdx?$/
})
module.exports = withMDX({
pageExtensions: ['js', 'jsx', 'md', 'mdx']
})
```
3. **Create a `<Section>` component**
```jsx
// components/Section.js
export function Section({ children }) {
return (
<div className="rounded-lg border p-4 mb-4 bg-white shadow">
{children}
</div>
)
}
```
4. **In your MDX**, interleave `<Section>` around each H2:
```mdx
import { Section } from '../components/Section'
<Section>
## Introduction
Here’s some intro text…
</Section>
<Section>
## Features
- feature one
- feature two
</Section>
<Section>
## Getting Started
1. Step one
2. Step two
</Section>
```
If you hate typing `<Section>` every time, you can author a small remark plugin that auto-wraps each `heading(depth=2)` + its siblings into `<Section>…</Section>` tags in the MDX source.
---
### Tabs instead of cards
Once you have your array of `{ title, content }` (whether card-wrapped or raw), you can feed it into any tabs UI. For example with [Radix UI Tabs](https://www.radix-ui.com/docs/primitives/components/tabs):
```jsx
import { Tabs, TabList, Tab, TabPanels, TabPanel } from '@radix-ui/react-tabs'
<Tabs defaultValue={sections[0].title}>
<TabList>
{sections.map(sec => (
<Tab key={sec.title} value={sec.title}>{sec.title}</Tab>
))}
</TabList>
<TabPanels>
{sections.map(sec => (
<TabPanel key={sec.title} value={sec.title}>
<div className="prose">{sec.content}</div>
</TabPanel>
))}
</TabPanels>
</Tabs>
```
---
### TL;DR
* **Impossible?** No – everything is just AST manipulation.
* **Plain-MD → `remark`**: group AST nodes under each H2, then render sections.
* **MDX → wrapper**: either hand-wrap or auto-wrap H2 sections in a `<Section>` component.
Pick the style that best fits your workflow!