DEV Community

sunshey
sunshey

Posted on

How to Convert Word to PDF in the Browser with Vue 3, mammoth, and html2pdf.js

Converting Word documents to PDFs on the server is the classic approach: upload the file, run LibreOffice or a cloud API, send the result back. But that means your users’ resumes, contracts, and reports touch your infrastructure.

I wanted something simpler for en.sotool.top: pick a .docx file in the browser, preview the parsed content, and download a PDF. No server involved.

Here is how I built it with Vue 3, mammoth, and html2pdf.js.


Why Client-Side?

The main reason is privacy. Resumes, contracts, tax documents — users do not want them on a stranger’s server. Client-side conversion also means:

  • No upload bandwidth limits
  • No file size caps from your server
  • No storage to clean up
  • Works offline after the page loads

The trade-off is that very complex documents are limited by the browser’s rendering capabilities. For typical office documents, that is fine.


The Stack

  • Vue 3 — UI, file handling, and reactive state
  • mammoth — Parse .docx files into clean HTML
  • html2pdf.js — Render the HTML into a PDF using html2canvas + jsPDF
  • Native File API — File selection
npm install mammoth html2pdf.js
Enter fullscreen mode Exit fullscreen mode

Loading the Word Document

The first step is reading the uploaded .docx file into an ArrayBuffer, then converting it to HTML with mammoth.

import mammoth from 'mammoth';

async function convertDocxToHtml(file) {
  const arrayBuffer = await file.arrayBuffer();
  const result = await mammoth.convertToHtml({ arrayBuffer });
  return result.value;
}
Enter fullscreen mode Exit fullscreen mode

mammoth intentionally produces simple, clean HTML. It ignores complex formatting like text boxes and embedded fonts, which makes the output predictable.

I keep the HTML in a reactive ref and render it in a preview panel:

<template>
  <div ref="previewRef" class="word-preview" v-html="htmlContent"></div>
</template>

<script setup>
import { ref } from 'vue';

const htmlContent = ref('');
const previewRef = ref(null);
</script>
Enter fullscreen mode Exit fullscreen mode

Generating the PDF

Once the user is happy with the preview, html2pdf.js turns the preview element into a PDF. It uses html2canvas to rasterize the HTML and jsPDF to create the PDF file.

import html2pdf from 'html2pdf.js';

async function convertToPdf() {
  const element = previewRef.value;
  if (!element) return;

  await html2pdf()
    .from(element)
    .set({
      margin: [10, 10, 10, 10],
      filename: 'document.pdf',
      image: { type: 'jpeg', quality: 0.98 },
      html2canvas: { scale: 2, useCORS: true },
      jsPDF: { unit: 'mm', format: 'a4', orientation: 'portrait' },
    })
    .save();
}
Enter fullscreen mode Exit fullscreen mode

The scale: 2 setting gives crisp text and images on high-DPI screens. useCORS: true helps if the preview contains external images.


Styling the Preview

html2pdf.js renders whatever is in the DOM, so the preview styles directly affect the output. I use scoped CSS with :deep() to style the converted HTML:

.word-preview :deep(h1) {
  font-size: 1.5rem;
  font-weight: 700;
  margin-bottom: 1rem;
}

.word-preview :deep(p) {
  margin-bottom: 0.75rem;
  line-height: 1.7;
}

.word-preview :deep(table) {
  width: 100%;
  border-collapse: collapse;
  margin-bottom: 1rem;
}

.word-preview :deep(td),
.word-preview :deep(th) {
  border: 1px solid #d1d5db;
  padding: 0.5rem;
}
Enter fullscreen mode Exit fullscreen mode

Keep the preview background white. Dark mode UI is nice for the app, but a white page produces a clean PDF.


Handling Errors Gracefully

Not every file is a valid .docx. I wrap the conversion in a try/catch and reset the state on failure:

async function handleFile(file) {
  try {
    const arrayBuffer = await file.arrayBuffer();
    const result = await mammoth.convertToHtml({ arrayBuffer });
    htmlContent.value = result.value;
  } catch (e) {
    console.error(e);
    alert('Failed to parse the document. Please check the file format.');
  }
}
Enter fullscreen mode Exit fullscreen mode

You can also surface mammoth warnings if you want to show the user which styles or elements were ignored.


Lessons Learned

1. Mammoth is opinionated by design
It strips complex Word features to produce clean HTML. If your users need pixel-perfect fidelity, a server-side converter is still a better fit.

2. Page size matters
If the document contains wide tables or landscape sections, A4 portrait may clip them. Consider letting users choose the output format.

3. html2canvas is heavy on large documents
Very long documents with many images can take several seconds and block the main thread. For heavy usage, consider chunking or using a Web Worker.

4. Keep the original file around
If users want to adjust settings and re-convert, you will need the original File object or ArrayBuffer because mammoth does not expose an editable model.


Try It

The Word to PDF tool is live at en.sotool.top/word-to-pdf.

Free, no signup, and nothing uploads to a server.

Full source code is on GitHub. The conversion logic lives in src/views/WordToPdf.vue.


Want More Advanced PDF Features?

If you need OCR, form editing, digital signatures, or batch processing beyond what a browser tool can do, Wondershare PDFelement is a solid desktop option. It keeps everything local and adds professional tools on top.

This post contains affiliate links.


Have you built client-side document converters? What did you use — mammoth, pdf-lib, or a server-side tool?

Top comments (0)