Markdown to PDF
Overview
Section titled “Overview”Markdown to PDF is a full-stack web application that turns Markdown into polished, print-ready PDF and PNG documents. Paste your text or drag-and-drop a .md file, pick your output format, and download — all from the browser. The server-side conversion pipeline uses Puppeteer to render styled HTML into pixel-perfect output, complete with syntax-highlighted code blocks and smart page-break handling so images and tables never get sliced across pages.
Live Demo
Section titled “Live Demo”Try it directly in the browser — no install required: jadujoel.github.io/markdown-to-pdf
Features
Section titled “Features”- Dual Output Formats — Convert to PDF (A4, print-ready) or full-page PNG in a single click. The download button bundles both formats at once using parallel conversion.
- Paste or Upload — Switch between a live Markdown editor with monospace font and syntax placeholder, or drag-and-drop / browse for
.mdfiles. File type validation keeps things clean. - Syntax-Highlighted Code Blocks — Code fences are rendered with highlight.js using a One Dark–inspired theme, supporting auto-detection across dozens of languages.
- Smart Page Break Control — A configurable setting prevents images, tables, code blocks, and blockquotes from splitting across PDF pages using CSS
break-inside: avoidandpage-break-inside: avoidrules. - Persistent Settings — User preferences (like the image-split toggle) are saved in
localStorageand restored on return visits. - Live Preview — Open a rendered preview in a new browser tab before committing to a download.
- Beautiful Typography — The PDF output uses Inter and Fira Code fonts, styled blockquotes, rounded code blocks, and a carefully tuned color palette for a professional look.
- Hot Module Reloading — Development uses Bun’s HMR with a custom
import.meta.hot.datapattern infrontend.tsxto preserve React root across reloads.
How It Works
Section titled “How It Works”The frontend React app in App.tsx sends Markdown to a /api/convert endpoint via FormData. The server in index.ts accepts both JSON and multipart payloads with a 5 MB size limit. The actual conversion happens in convert.ts:
// convert.ts — core pipelineconst htmlBody = await marked.parse(markdown);const fullHtml = htmlTemplate(htmlBody, resolvedOptions);
const browser = await getBrowser();const page = await browser.newPage();await page.setContent(fullHtml, { waitUntil: "networkidle0" });
if (format === "pdf") { const pdf = await page.pdf({ format: "A4", printBackground: true, margin: { top: "20mm", bottom: "20mm", left: "15mm", right: "15mm" }, }); return Buffer.from(pdf);}A single Puppeteer browser instance is kept alive and reused across requests for fast throughput — each conversion only opens and closes a page, not an entire browser process.
For PNG output, the renderer dynamically measures the body height and sets the viewport to match, producing a seamless full-length screenshot:
const bodyHandle = await page.$("body");const { height } = await bodyHandle!.boundingBox() as { height: number };await page.setViewport({ width: 900, height: Math.ceil(height) + 40 });const png = await page.screenshot({ type: "png", fullPage: true });Tech Stack
Section titled “Tech Stack”| Language | TypeScript |
| Runtime | Bun |
| Frontend | React 19 |
| Markdown | marked + marked-highlight |
| Syntax Highlighting | highlight.js |
| PDF / PNG Rendering | Puppeteer (headless Chrome) |
| Styling | Custom CSS with Inter & Fira Code fonts |
| Deployment | GitHub Pages via GitHub Actions |
Source Code
Section titled “Source Code”The source code is available on the project’s GitHub repository.