Last month I shipped MD2PDF Online — a free tool that converts Markdown to PDF, Word, HTML and Mind Map, with zero server-side processing. Everything runs client-side.
Here's what I learned building it and why the approach matters.
The Problem
Most online converters upload your file to a server, process it, then send back the result. This works, but:
- Your document content leaves your device
- There are file size limits
- Server costs scale with usage
- Privacy concerns for sensitive documents
I wanted a converter where the browser does all the work. Your Markdown never leaves your computer.
The Architecture
┌──────────────────────────────────────────────┐
│ Browser │
│ │
│ ┌─────────┐ ┌──────────┐ ┌─────────┐ │
│ │ Code │───▶│ Parser │───▶│ Export │ │
│ │ Mirror │ │ (remark)│ │ (html2pdf)│ │
│ └─────────┘ └──────────┘ └─────────┘ │
│ │
│ No server calls. No file uploads. │
└──────────────────────────────────────────────┘
1. Markdown Parsing
I used the unified ecosystem with remark-parse, remark-gfm, and rehype-stringify. This gives me GitHub-Flavored Markdown support including tables, task lists, and strikethrough.
import { unified } from "unified";
import remarkParse from "remark-parse";
import remarkGfm from "remark-gfm";
import remarkRehype from "remark-rehype";
import rehypeStringify from "rehype-stringify";
async function markdownToHtml(md: string) {
return String(
await unified()
.use(remarkParse)
.use(remarkGfm)
.use(remarkRehype)
.use(rehypeStringify)
.process(md)
);
}
The parser runs synchronously in the browser. No API calls.
2. PDF Generation
Instead of sending the HTML to a server and waiting for a PDF back, I use html2pdf.js — a client-side wrapper around html2canvas and jsPDF:
import html2pdf from "html2pdf.js";
function exportToPdf(htmlContent: string) {
const element = document.createElement("div");
element.innerHTML = htmlContent;
document.body.appendChild(element);
html2pdf().from(element).save();
}
This renders the HTML as a canvas, then converts it to a PDF. The trade-off is that it's a rasterized PDF (not vector), but for most use cases it's perfectly fine and the zero-server approach is worth it.
3. Word Export
For DOCX, I used the docx library:
import { Document, Packer, Paragraph, TextRun } from "docx";
const doc = new Document({
sections: [{ children: paragraphs }],
});
const blob = await Packer.toBlob(doc);
saveAs(blob, "document.docx");
4. Mind Map Generation
This was the most interesting part. I used markmap-lib to parse Markdown headings and lists into a hierarchical data structure, then rendered it with markmap-view as an interactive SVG mind map:
import { Transformer } from "markmap-lib";
const transformer = new Transformer();
const { root, features } = transformer.transform(markdown);
Key Challenges
Client-Side Performance
Running everything in the browser means the main thread can get busy. I solved this by:
- Lazy loading heavy libraries (html2pdf, docx, markmap) only when the user clicks export
- Using useMemo and React.lazy in Next.js to avoid re-parsing
- Debouncing the editor input at 100ms
Internationalization
The tool supports 6 languages (English, Chinese, Japanese, French, German, Spanish). I used next-intl with the new App Router. Each language gets its own URL (/en, /zh, /ja etc.) with proper hreflang tags for SEO.
The PDF-to-Markdown Pipeline
Converting PDF back to Markdown is harder because the browser can't run OCR. I used @opendocsg/pdf2md which works well for PDFs with selectable text. For scanned PDFs, I recommend local tools like Tesseract.
Why This Matters
The "process everything in the browser" approach has real benefits:
| Server-side | Client-side |
|---|---|
| File uploads to remote servers | File stays on your device |
| Server costs scale with traffic | Free to run (static hosting) |
| Privacy concerns | Zero data collection |
| Rate limits needed | No limits |
For a simple converter, there's no reason to send files to a server. Modern browsers are powerful enough to handle the entire pipeline locally.
Want to Try It?
You can use it for free at https://md2dfonline.com. No signup, no tracking, no file uploads. The code is a standard Next.js app — I'm happy to answer any questions about the architecture or tradeoffs in the comments.
What's your favorite browser-based productivity tool?
Top comments (0)