Problem
Several MDX files fall back to @mdx-js/mdx due to markdown-rs parse errors when JSX components contain markdown content:
Markdown parse error: 147:2: Unexpected closing slash `/` in tag, expected an open tag first
Affected patterns:
<Steps>
1. First step with **bold** text
2. Second step
</Steps>
<FileTree>
- folder/
- nested file
</FileTree>
Affected files (~24 in withastro-docs):
concepts/islands.mdx (13 translations)
guides/cms/builderio.mdx (5 translations)
guides/media/mux.mdx (3 translations)
guides/cms/keystatic.mdx (2 files)
Root Cause
markdown-rs's JSX parser has strict rules. When it parses <Steps>, it enters "JSX flow content" mode and cannot recognize markdown syntax (like numbered lists) inside the tag body. When it encounters </Steps>, it fails because it lost track of the opening tag context.
Existing Preprocessing
Markflow already has preprocessing in jsx_normalize.rs that handles:
- JSX near markdown (inserting blank lines)
- Multiline wrapper tags (collapsing
<p> wrappers)
- Tab components in list contexts
The gap: These don't handle markdown inside JSX.
Proposed Solution: Extend Masking Pattern
The existing mask_raw_html_blocks function successfully masks <script> and <style> blocks before parsing. The same pattern can be extended for markdown-container components:
- Before parsing, mask
<Steps>...</Steps>, <FileTree>...</FileTree>, etc. with placeholders
- Parse the remaining markdown normally
- Parse the masked content separately as markdown
- Restore the parsed HTML into the component slots
Implementation location: crates/core/src/renderer/mdast/mod.rs
Components to support:
Steps (wrap in <ol>)
FileTree (wrap in <ul>)
Box, Tabs, TabItem (pass through)
Estimated Effort
2-4 hours of Rust work:
- ~150-200 lines in
mod.rs
- Test cases for masking/unmasking
- No changes to JSX normalization, rendering, or codegen layers
Alternatives Considered
| Approach |
Effort |
Risk |
Notes |
| Masking pattern (recommended) |
Low |
Low |
Reuses proven infrastructure |
| Container component registry |
Medium |
Medium |
More code changes across pipeline |
| Extend directive system |
Low |
Low |
Requires users to change syntax |
| Extract and reparse |
High |
High |
Complex state management |
Current Workaround
The fallback to @mdx-js/mdx works correctly. These files represent ~0.5% of withastro-docs.
Problem
Several MDX files fall back to
@mdx-js/mdxdue to markdown-rs parse errors when JSX components contain markdown content:Affected patterns:
Affected files (~24 in withastro-docs):
concepts/islands.mdx(13 translations)guides/cms/builderio.mdx(5 translations)guides/media/mux.mdx(3 translations)guides/cms/keystatic.mdx(2 files)Root Cause
markdown-rs's JSX parser has strict rules. When it parses
<Steps>, it enters "JSX flow content" mode and cannot recognize markdown syntax (like numbered lists) inside the tag body. When it encounters</Steps>, it fails because it lost track of the opening tag context.Existing Preprocessing
Markflow already has preprocessing in
jsx_normalize.rsthat handles:<p>wrappers)The gap: These don't handle markdown inside JSX.
Proposed Solution: Extend Masking Pattern
The existing
mask_raw_html_blocksfunction successfully masks<script>and<style>blocks before parsing. The same pattern can be extended for markdown-container components:<Steps>...</Steps>,<FileTree>...</FileTree>, etc. with placeholdersImplementation location:
crates/core/src/renderer/mdast/mod.rsComponents to support:
Steps(wrap in<ol>)FileTree(wrap in<ul>)Box,Tabs,TabItem(pass through)Estimated Effort
2-4 hours of Rust work:
mod.rsAlternatives Considered
Current Workaround
The fallback to
@mdx-js/mdxworks correctly. These files represent ~0.5% of withastro-docs.