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