Convert Word Documents to HTML in the Browser with JavaScript

2026-09-30 09:17:34 Allen Yang
AI Summarize:
ChatGPT
ChatGPT ✓
Claude ✓
Grok ✓
Perplexity ✓
Quick
Quick
Concise overview
Highlights
Key takeaways
Detailed
Structured explanation
Brief
One sentence summary
Summarize |

Convert Word to HTML in the browser

Word documents are often the starting point for web content — articles, product specs, and compliance docs all need to live on a website eventually. Getting from .docx to clean HTML without a backend conversion service is the challenge. Spire.Doc for JavaScript makes this possible by running a full document-processing engine on WebAssembly, reading the Word file through a virtual file system (VFS), performing the conversion locally, and letting you download the resulting HTML — all client-side, with no server round-trip.

Two export strategies dominate the workflow, and choosing between them is the real decision:

  • Embedded mode bundles CSS and images directly into the HTML file, producing a single self-contained document that opens anywhere.
  • External mode writes CSS and images to separate files, giving you smaller HTML, reusable stylesheets, and individual image assets you can manage independently.

This article walks through both approaches in a React project and compares them side by side. For setup, refer to Integrating Spire.Doc for JavaScript in a React Project. The examples below assume Spire.Doc is installed and the WebAssembly module is initialized.


Basic Conversion: Embed Everything in One File

The simplest way to publish a Word document as a web page is to produce a single HTML file that contains everything — markup, styles, and images — in one self-contained package. This is ideal when you need a portable artifact that renders correctly no matter where it is opened, with no missing-file references or broken links.

The conversion follows three steps. First, load the font file and the source Word document into the WASM virtual file system using FetchFileToVFS. Second, create a Document instance, load the file, configure HtmlExportOptions to embed both CSS and images, and call SaveToFile to write the HTML. Third, read the generated file back from VFS, wrap it in a Blob, and trigger a browser download.

function App() {
  const wordToHtml = async () => {
    // Get the Spire.Doc WASM module
    const docModule = window.wasmModule?.spiredoc;

    // Check if the module is ready
    if (!docModule) {
      alert('Spire.Doc is not ready yet');
      return;
    }

    // Load fonts and the Word file into VFS
    await window.spire.FetchFileToVFS('ARIALUNI.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/static/font/`);
    const inputFileName = 'ToHtml.docx';
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/static/data/`);

    // Load the Word document
    const wordDocument = new docModule.Document();
    wordDocument.LoadFromFile(inputFileName);

    // Embed the CSS styles into the HTML and embed images as Base64
    wordDocument.HtmlExportOptions.CssStyleSheetType = docModule.CssStyleSheetType.Internal;
    wordDocument.HtmlExportOptions.ImageEmbedded = true;

    // Convert the document to HTML
    const outputFileName = 'ToHtml-result.html';
    wordDocument.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Html });

    // Read the converted file from VFS and trigger download
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'text/html;charset=utf-8' });
    const url = URL.createObjectURL(blob);
    const a = window.document.createElement('a');
    a.href = url;
    a.download = outputFileName;
    a.click();
    URL.revokeObjectURL(url);

    // Release resources
    wordDocument.Dispose();
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Convert Word To HTML</h1>
      <button onClick={wordToHtml}>
        Generate
      </button>
    </div>
  );
}

export default App;

HTML page generated from a Word document via SaveToFile

HTML page generated from a Word document via SaveToFile


Export Options: Separate CSS and Images

Embedding everything into one file is convenient, but it has trade-offs. A large document with many images produces a very big HTML file, and every page that shares the same styling carries its own duplicate copy of the CSS. When you want to maintain styles centrally, reuse image assets across pages, or keep the HTML payload small for faster initial rendering, you should export CSS and images as separate files instead.

HtmlExportOptions gives you fine-grained control over how each resource type is written. You can direct the CSS to a named stylesheet file, send images to a dedicated directory, and even control how form fields are serialized. The result is no longer a single file but a directory structure containing the HTML, the stylesheet, and the image files.

The workflow mirrors the embedded approach, with two additions. Before conversion, create an output directory in VFS and use CssStyleSheetFileName and ImagesPath to tell Spire.Doc where to write each resource type. After conversion, read the entire output directory recursively, package everything into a zip archive using JSZip, and download it in one operation.

import JSZip from 'jszip';

function App() {
  const wordToHtmlWithOptions = async () => {
    // Get the Spire.Doc WASM module
    const docModule = window.wasmModule?.spiredoc;

    // Check if the module is ready
    if (!docModule) {
      alert('Spire.Doc is not ready yet');
      return;
    }

    // Load fonts and the Word file into VFS
    await window.spire.FetchFileToVFS('ARIALUNI.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/static/font/`);
    const inputFileName = 'ToHtml.docx';
    await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/static/data/`);

    // Create the output directory in VFS
    const outputDirectoryName = 'ToHTMLFolder/';
    window.dotnetRuntime.Module.FS.mkdirTree(outputDirectoryName);

    // Load the Word document
    const wordDocument = new docModule.Document();
    wordDocument.LoadFromFile(inputFileName);

    // Export the CSS styles to a separate file
    wordDocument.HtmlExportOptions.CssStyleSheetFileName = outputDirectoryName + 'sample.css';
    wordDocument.HtmlExportOptions.CssStyleSheetType = docModule.CssStyleSheetType.External;

    // Export images to a separate directory
    wordDocument.HtmlExportOptions.ImageEmbedded = false;
    wordDocument.HtmlExportOptions.ImagesPath = outputDirectoryName + 'Demo/';

    // Export form fields as plain text
    wordDocument.HtmlExportOptions.IsTextInputFormFieldAsText = true;

    // Convert the document to HTML
    const outputFileName = 'ToHtmlExportOption-out.html';
    wordDocument.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Html });

    // Release resources
    wordDocument.Dispose();

    // Read the output directory recursively and write each level of files into the zip
    const zip = new JSZip();
    const addFilesToZip = async (folderPath, zipFolder) => {
      let items = await window.dotnetRuntime.Module.FS.readdir(folderPath);
      items = items.filter((item) => item !== '.' && item !== '..');
      for (const item of items) {
        const itemPath = `${folderPath}/${item}`;
        try {
          const fileData = await window.dotnetRuntime.Module.FS.readFile(itemPath);
          zipFolder.file(item, fileData);
        } catch (error) {
          const zipSubFolder = zipFolder.folder(item);
          await addFilesToZip(itemPath, zipSubFolder);
        }
      }
    };

    // Package the HTML file together with the resource directory
    zip.file(outputFileName, window.dotnetRuntime.Module.FS.readFile(outputFileName));
    await addFilesToZip(outputDirectoryName, zip);
    const zipBlob = await zip.generateAsync({ type: 'blob' });
    const url = URL.createObjectURL(zipBlob);

    // Trigger download
    const a = window.document.createElement('a');
    a.href = url;
    a.download = 'ToHTMLFolder.zip';
    a.click();
    URL.revokeObjectURL(url);
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Convert Word To HTML With Export Options</h1>
      <button onClick={wordToHtmlWithOptions}>
        Generate
      </button>
    </div>
  );
}

export default App;

HTML, CSS, and image files generated after configuring the export options

HTML, CSS, and image files generated after configuring the export options

One detail worth noting: Spire.Doc does not place images directly in the directory specified by ImagesPath. Instead, it creates an external_images subfolder inside that directory to hold the image files. The resulting structure looks like Demo/external_images/*.png, which is why addFilesToZip walks the directory tree recursively rather than reading a flat list of files.


Embedded vs. External: Choosing the Right Strategy

Both export modes produce valid HTML from the same Word document, but they serve different publishing needs. The table below summarizes the key differences to help you decide which approach fits your workflow.

Aspect Embedded (Single File) External (Separate Files)
Output One .html file with inline CSS and Base64 images HTML + .css + image files in a directory
File size Larger — all assets are Base64-encoded into the HTML Smaller HTML; total size is similar but assets are individual files
Portability Fully self-contained; opens correctly anywhere with no dependencies Requires all files to stay together; relative paths must be preserved
Download mechanism Single file download via Blob Zip archive download (e.g., with JSZip)
Style reuse Each document carries its own copy of the CSS Multiple pages can share one stylesheet file
Image management Images are Base64 strings inside the HTML; cannot be referenced or cached separately Images are individual files that can be cached, lazy-loaded, or reused
Initial render speed Slower for large documents — the browser must parse one big file Faster initial HTML parse; CSS and images load in parallel
Best for Email attachments, one-off previews, archival snapshots, sharing a single document CMS content migration, multi-page publishing, knowledge bases, sites with shared styling
Maintainability Low — changing a style means regenerating the entire file High — edit the CSS file once and all linked pages update

Quick decision guide:

  • Choose embedded when you need a single, portable artifact — for example, generating a preview that a user downloads and opens offline, or attaching a converted document to an email.
  • Choose external when you are publishing to a web platform where multiple documents share the same design system, where you want to cache or lazy-load images, or where the HTML file size matters for performance.

FAQ

Fonts in the exported HTML do not match the original document

If the fonts in your converted HTML look different from the source Word file, the cause is almost always missing font data in the WASM virtual file system. Spire.Doc relies on fonts loaded into VFS to perform accurate layout calculations and font-name resolution during conversion. When a required font is not available, the engine substitutes a fallback font, and the font-family declarations in the output CSS will not match what the original document specifies. For documents that use symbol fonts such as Wingdings, the affected characters may also render as garbled text.

The fix is straightforward: preload the necessary font files into VFS via FetchFileToVFS before you run the conversion. For documents containing Chinese, Japanese, or Korean text, use a font with broad Unicode coverage such as ARIALUNI.TTF:

await window.spire.FetchFileToVFS(
  'ARIALUNI.TTF', '/Library/Fonts/', '/'
);

Exported HTML loses its styles and images when opened

When you use external mode (CssStyleSheetType.External with ImageEmbedded = false), the CSS and image files are written to separate locations, and the HTML references them through relative paths. If you download only the HTML file without its accompanying resources, the browser cannot resolve those paths and the page falls back to unstyled plain text with broken images.

To avoid this, always package the HTML together with its resource directory — the addFilesToZip approach shown in the export-options section handles this by bundling everything into a single zip download. Alternatively, if you do not actually need separate resource files, switch to embedded mode so everything stays in one self-contained HTML file:

wordDocument.HtmlExportOptions.CssStyleSheetType = docModule.CssStyleSheetType.Internal;
wordDocument.HtmlExportOptions.ImageEmbedded = true;

See Also