DOCX fidelity and security
The Next.js export endpoint generates a native WordprocessingML package locally. This matrix is tied to the repository’s live package-level fidelity test; it is not a claim that arbitrary browser CSS has a one-to-one Word equivalent.
Tested mappings
| Area | Native DOCX output |
|---|---|
| Document structure | Headings, paragraphs, block quotes, nested bullets and decimal lists, fixed-layout tables |
| Inline formatting | Bold, italic, underline, strike, code, font family and size, text/background color, subscript and superscript |
| Paragraph layout | Alignment, margins, line height, first-line indent, page-break-before and explicit page breaks |
| Page layout | Letter or A4, portrait or landscape, bounded margins, default headers and footers, current/total page fields |
| Tables | Exact DXA grid, repeating header rows, column span, row span, cell shading and vertical alignment |
| Review semantics | Native DOCX insertions, deletions, comments, footnotes and endnotes |
| Equations | LaTeX converted to native OMML — fractions, scripts, radicals, n-ary operators, delimiters, functions, limits, accents, bars and boxes; editable in Word's equation editor, not an image |
| Links and media | HTTP/HTTPS/mailto links; PNG, JPEG, GIF and BMP data URIs; trusted server-resolved image map |
Request contract
POST /api/export/docx
{
"html": "<h1>Report</h1><p>Body</p>",
"title": "Report",
"fileName": "report.docx",
"headerHtml": "<p>Private report</p>",
"footerHtml": "<p>Page <span data-page-number="current"></span></p>",
"page": { "size": "a4", "orientation": "landscape",
"margin": { "top": 1080, "right": 900, "bottom": 1080, "left": 900 } },
"imageMap": { "https://trusted.example/chart.png": "data:image/png;base64,..." }
}Margins use twentieths of a point (twips) and are bounded from 360 to 2880. The request is capped at 1 MiB. An image map accepts at most eight HTTP(S) keys, data-image values only, and 768 KiB decoded in total.
Semantic HTML conventions
<ins data-author="Ada" data-date="2026-07-16T12:00:00Z">added</ins>
<del data-author="Ada">removed</del>
<span data-comment="Check the source" data-comment-author="Reviewer">claim</span>
<sup data-footnote="Source citation"></sup>
<span data-endnote="Appendix detail"></span>
<hr class="page-break">Importing DOCX
Import runs entirely in the browser, with no library and no server round-trip. A .docx is a ZIP of XML: the ZIP central directory is read directly and DecompressionStream("deflate-raw") handles the inflate. Nothing is uploaded anywhere.
| Imported | How, and what a naive converter loses |
|---|---|
| Hyperlink URLs | w:hyperlink carries only an r:id; the address lives in word/_rels/document.xml.rels. Resolved — without this, every link imports as plain text with the URL discarded. |
| Images | w:drawing → a:blip → word/media/*, inlined as data URIs. Set documentImportImages: false to skip them on image-heavy files. |
| Numbered vs bulleted lists | document.xml stores every list identically; only word/numbering.xml distinguishes them. Read, so numbered lists stay <ol> instead of collapsing to bullets. |
| List nesting | w:ilvl produces real nesting, and the sublist is placed inside its parent <li> so the markup is valid. |
| Footnotes & endnotes | Word keeps note bodies in separate word/footnotes.xml and word/endnotes.xml parts; document.xml holds only an id. All are read and rebuilt as live notes — insert one above an imported note and everything renumbers. The two parts share a schema but not an element name (w:footnote vs w:endnote), so a parser written for one silently returns nothing for the other. Word’s separator entries are skipped. |
| Text boxes | Text box content nests six levels below the run (w:pict → v:shape → v:textbox → w:txbxContent), so a converter that reads only direct children drops every word of it. Recovered as a .rte-imported-textbox block in document order, paragraphs and tables intact. An mc:AlternateContent block carries the same box twice — a DrawingML version and a VML fallback — and only one is emitted. |
| Tracked changes & comments | w:ins and w:del import as <ins>/<del> with author and date — the same conventions the export emits, so review markup round-trips. Deleted text lives in w:delText, not w:t. Comment ranges become data-comment spans with the body pulled from word/comments.xml. |
| Equations | Word stores equations as an m:oMath tree in its own math namespace, interleaved between the runs — a converter that walks w:r elements never sees it, and the equation disappears while the sentence around it imports fine. Translated to LaTeX and emitted as the editor’s own rte-math-inline span, so it is editable in the math dialog and renders through whatever renderer the page loads. Fractions, sub/superscripts, radicals, n-ary operators (the operator is an attribute, not text), delimiters, functions, limits, accents, bars, matrices and aligned arrays are mapped; anything unrecognised keeps its literal text rather than vanishing. |
| SmartArt | The drawing in document.xml holds only relationship ids; every word of the diagram lives in word/diagrams/dataN.xml under a separate namespace, so a reader looking for a:blip finds nothing and drops the graphic entirely. The layout cannot be reproduced in HTML, but the content is recovered as a .rte-imported-smartart outline — an org chart that imports as a list is usable; one that imports as blank space is a hole. Word’s presentation and connector points (pres, parTrans, sibTrans) carry no content and are skipped. |
| WMF/EMF images | Windows metafiles have no browser-renderable form, so they cannot be inlined. Rather than disappearing without trace, each leaves a named placeholder carrying data-source, so the reader knows a figure was there and which one to look up in the original. |
| Character formatting | Colour (w:color), highlight, superscript/subscript, and size — w:sz is in half-points, so 36 becomes 18pt rather than 36pt. |
| Structure & formatting | Headings from w:pStyle, tables, paragraph alignment, bold, italic, underline, strike-through, tabs and line breaks. |
Markdown, HTML, plain text and Word’s HTML-flavoured .docare handled the same way. For anything beyond this — or to route conversion through your own service — supply config.documentImportResolver(file), which takes precedence for every format.
Known limits — and they are limits of shape, not of content. Nothing in a .docxis now dropped silently. What HTML cannot reproduce degrades visibly and says so: text boxes come back as content rather than floating frames (browser HTML has no equivalent of Word’s absolute page anchoring), SmartArt as an outline rather than a laid-out graphic, and WMF/EMF images as a named placeholder. Equations arrive as LaTeX, which needs KaTeX or MathJax on the page to render as maths rather than as their source. Those are import gaps, not export gaps — the table above covers what round-trips today. Equations are verified as a genuine loop rather than two independent features: fifteen formulas exported to OOXML and re-imported come back byte-identical, including the three visually identical Unicode angle brackets that different generators emit.
Security boundary
The converter never performs network fetches. A URL in an img element is ignored unless the calling server supplies an exact URL-to-data-URI entry in imageMap. This keeps DNS, redirects, private addresses, credentials and tenant authorization outside the converter’s trust boundary.
Intentional limits
- Word is paginated and browser HTML is flow layout. Floats, CSS grid, filters, scripts, forms, animation and arbitrary styles are not translated.
- Only default section headers and footers are exposed; first-page/even-page variants and multiple HTML-defined sections require an application-specific template.
- The automated suite inspects the generated OOXML parts and relationships. Visual sign-off in Microsoft Word or LibreOffice remains a release step for branded templates.