Markdown to PDF: Why I Switched to the Browser's Built-in Print

2 min

Converting Markdown to PDF sounds easy enough — with html2pdf.js, you can get it working in just a few dozen lines of code, and it spits out a PDF in no time. But once I actually started testing it, I ran into a lot of issues. When I was building the Markdown to PDF feature, I spent days going back and forth, rewriting the code over and over. In the end, I ditched html2pdf.js and switched to the browser’s native print instead.


Why Go with the Browser’s Native Print?

If you’re doing everything on the frontend (no backend), there are basically three ways to generate a PDF:

  1. Server-side rendering (Puppeteer/Playwright) — not an option for static sites
  2. Client-side JS generation (html2pdf.js, jsPDF + html2canvas) — outputs a bitmap image
  3. Browser native print (window.print()) — gives you a real vector PDF, and the browser handles page breaks

I started with html2pdf.js, but quickly hit some major pain points:

  • Bitmap output: The PDF is essentially a screenshot — you can’t select or search any text
  • Messy page breaks: Long code blocks and nested lists get chopped right in the middle (this was the dealbreaker)
  • Performance issues: Long documents need a ton of Canvas rendering, which can easily freeze mobile devices

After switching to the browser’s native print, the text-chopping problem was completely gone. Plus, text is selectable and searchable, links still work, file sizes are smaller, and page breaks are way more reliable.


The Core Setup: Overlay + iframe

If you just call window.print() directly, it’ll print your entire page UI — buttons, nav, everything. So we use an iframe to isolate the content. Here’s how it works:

  1. User clicks “Download PDF”
  2. A fullscreen overlay with a toolbar pops up
  3. Print styles are generated from a template
  4. The styled HTML gets written into the iframe
  5. User hits print, which triggers iframe.contentWindow.print()

This way, you get a nice preview without messing up the main page’s styles.


Key CSS Rules for Page Breaks

@media print {
  tr, pre, blockquote, img, .katex-display {
    page-break-inside: avoid;
    break-inside: avoid;
  }
  h1, h2, h3, h4, h5, h6 {
    page-break-after: avoid;
    break-after: avoid;
  }
  table { page-break-inside: auto; }
  thead { display: table-header-group; }
  body {
    -webkit-print-color-adjust: exact;
    print-color-adjust: exact;
  }
}

These rules prevent content from getting cut off, repeat table headers on every page, and preserve background colors.


Guiding Users Through the Print Dialog

It’s a good idea to show users a quick tip in the UI telling them how to set things up:

  1. Destination: Save as PDF
  2. Background graphics: Check this on
  3. Headers and footers: Turn these off
  4. Paper size: A4 or Letter
  5. Margins: Default

Wrapping Up

As long as you’re okay with the print dialog popping up, the browser’s native print is hands down the most stable, hassle-free, and highest-quality option out there. If you’ve got a better approach or any questions, feel free to leave a comment!


Related Links: