The Evolution Continues
A few weeks ago, I introduced MdBin—a free utility for sharing beautifully rendered markdown. The response was incredible, and people were using it exactly how I'd hoped: sharing LLM outputs, documentation snippets, and formatted notes without the friction of paywalls or mangled formatting.
But here's the thing about building in public: you never stop iterating.
Today I'm excited to share a significant upgrade under the hood—migrating from a custom markdown-it + Shiki pipeline to Streamdown, Vercel's new drop-in replacement for react-markdown.
The Old Setup: It Worked, But
My original implementation was solid. I used markdown-it-async with @shikijs/markdown-it for syntax highlighting:
import { fromAsyncCodeToHtml } from '@shikijs/markdown-it/async'
import MarkdownItAsync from 'markdown-it-async'
import { codeToHtml } from 'shiki'
const md = MarkdownItAsync({
html: true,
xhtmlOut: true,
linkify: true,
typographer: true,
breaks: true,
})
md.use(
fromAsyncCodeToHtml(codeToHtml, {
themes: {
light: 'github-light',
dark: 'github-dark',
},
defaultColor: false,
cssVariablePrefix: '--shiki-',
})
)
export async function renderMarkdown(content: string): Promise<string> {
const html = await md.renderAsync(content)
return html
}
Then in my page component, I'd render with the classic dangerouslySetInnerHTML:
<article
className="markdown-content"
dangerouslySetInnerHTML={{ __html: renderedContent }}
/>
This approach had several drawbacks:
-
Manual security handling —
dangerouslySetInnerHTMLis exactly what it sounds like - No built-in controls — I had to build copy buttons, download functionality myself
- Mermaid was a nightmare — SSR rendering just didn't work reliably
- Bundle size creep — Every language highlight added weight
- Incomplete block handling — Malformed markdown could break the entire render
Enter Streamdown
When I discovered Streamdown, I initially dismissed it. "It's for AI streaming," I thought. "MdBin renders static content SSR."
But then I looked closer at the feature set:
- 🎯 Code syntax highlighting with Shiki (what I was already using)
- 📈 Mermaid diagrams that actually work SSR
- 🛡️ Security-first with
rehype-harden - 📊 GitHub Flavored Markdown out of the box
- 🔢 Math rendering via KaTeX
- ⚡ Built-in controls for copy, download, fullscreen
Wait. This is exactly what a markdown sharing tool needs.
The Migration: Surprisingly Painless
Here's what the new implementation looks like:
import { Streamdown } from 'streamdown'
export default async function PastePage({ params }) {
const { id } = await params
const { decompressedContent, createdAt } = await cachedGetPaste(id)
return (
<Streamdown
className="markdown-content"
parseIncompleteMarkdown
shikiTheme={['github-light', 'github-dark']}
mode="streaming"
isAnimating={false}
controls={{
table: true,
code: true,
mermaid: {
download: true,
copy: true,
fullscreen: true,
panZoom: true,
},
}}
>
{decompressedContent}
</Streamdown>
)
}
That's it. No separate render function. No dangerouslySetInnerHTML. No manual security sanitization.
The Tailwind setup is one line in globals.css:
@source "../../node_modules/streamdown/dist/index.js";
Why v2.0.0 is a Game Changer
The timing couldn't have been better. Streamdown just shipped v2.0.0 with some massive improvements:
🚀 98% Bundle Size Reduction via CDN
This was the big one. Previously, bundling all those Shiki language grammars and themes bloated your build. Now, language-specific highlighting and KaTeX CSS are loaded from their CDN on-demand.
Your build stays lean. Users only fetch what they need.
📈 Mermaid SSR Actually Works
This was a blocker for me before. Mermaid diagrams in markdown are incredibly useful—flowcharts, sequence diagrams, architecture docs—but SSR rendering was broken. Now it works beautifully, with viewport-based lazy loading that prevents page freezing when you have multiple diagrams.
🛡️ Enhanced Security
rehype-harden + rehype-sanitize means I don't have to worry about XSS attacks from malicious markdown. The component handles sanitization automatically.
✨ Built-in UX Polish
All those controls I would have had to build myself? Built-in:
- Code blocks: Copy button, language indicator
- Tables: Download as CSV
- Mermaid: Download as PNG, copy SVG, fullscreen view with pan/zoom
What I Gained
| Feature | Before | After |
|---|---|---|
| Security | Manual with dangerouslySetInnerHTML
|
Built-in rehype-harden
|
| Code copy | Custom CopyButton component |
✅ Built-in |
| Mermaid SSR | ❌ Broken | ✅ Works |
| Table download | ❌ Not implemented | ✅ Built-in |
| Math rendering | ❌ Not implemented | ✅ KaTeX built-in |
| Bundle size | Growing with each language | CDN-loaded on demand |
| Incomplete markdown | Could break render | Graceful handling |
The "Streaming" Part
You might wonder: "Why use a streaming-optimized library for static content?"
Fair question. The mode="streaming" and isAnimating={false} props tell Streamdown this is pre-rendered content—no typing effects, no progressive reveal. But all the other benefits still apply:
-
parseIncompleteMarkdown: Handles edge cases where users paste malformed markdown (unclosed code blocks, incomplete tables). Instead of crashing or showing garbage, it renders gracefully. - Memoized rendering: Even without streaming, the performance optimizations help with re-renders.
Shoutout to the Vercel Team
The Streamdown team has done an incredible job packaging what could have been a complex, multi-library setup into a single, well-designed component. It powers their AI Elements Message component, but it's genuinely useful for any markdown rendering use case.
The documentation is solid, the defaults are sensible, and it just works.
Try It Out
Head over to mdbin.sivaramp.com and paste some markdown. Try:
- Code blocks in any language—notice the copy button and language badge
- Mermaid diagrams—they actually render now! Try the fullscreen + pan/zoom
- Tables—check out the download button
- Math equations—LaTeX just works
Here's a quick Mermaid example to paste:
graph TD
A[Paste Markdown] --> B[Streamdown Renders]
B --> C{What type?}
C -->|Code| D[Shiki Highlighting]
C -->|Diagram| E[Mermaid SVG]
C -->|Math| F[KaTeX Render]
D --> G[Beautiful Output]
E --> G
F --> G
What's Next
With the rendering layer now handled by Streamdown, I can focus on features users actually want:
- Expiration options — 1 hour, 1 day, 1 week, or permanent
- Password protection — For sensitive content
- Edit links — Update pastes without creating new ones
- Custom themes — Beyond light/dark mode
The foundation is solid. Now it's time to build.
TL;DR: Migrated MdBin from markdown-it + Shiki to Vercel's Streamdown. Got built-in code copy, Mermaid rendering (that actually works SSR!), math support, enhanced security, and a 98% smaller bundle—all from one component. The Vercel team knocked it out of the park with this one.
Check out the upgrade at mdbin.sivaramp.com and the Streamdown repo.
Top comments (0)