---
# System prepended metadata

title: o3-on-complex-markdown-parsing-in-next

---

# 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!
