TutorialMarch 20, 2026·2 min read

Astro DOCX Editor: Edit Word Documents in an Astro Site

Add a browser-based DOCX editor to your Astro project. Client-side Word document editing with React islands. Includes live demo, full code, and deployment tips.

Live demo

Upload a .docx or edit the sample below. Nothing leaves your browser.

Install

npx astro add react
npm install @eigenpal/docx-js-editor

Editor component

Standard React component. Astro hydrates it as an island on the client:

// src/components/DocxEditor.tsx
import { useState, useEffect, useRef, useCallback } from "react";
import {
  DocxEditor,
  createEmptyDocument,
} from "@eigenpal/docx-js-editor";
import type { DocxEditorRef, Document } from "@eigenpal/docx-js-editor";
import "@eigenpal/docx-js-editor/styles.css";
 
export function Editor() {
  const editorRef = useRef<DocxEditorRef>(null);
  const [documentBuffer, setDocumentBuffer] = useState<ArrayBuffer | null>(null);
  const [currentDocument, setCurrentDocument] = useState<Document | null>(null);
  const [fileName, setFileName] = useState("sample.docx");
 
  useEffect(() => {
    fetch("/sample.docx")
      .then((res) => res.arrayBuffer())
      .then((buf) => {
        setDocumentBuffer(buf);
        setFileName("sample.docx");
      })
      .catch(() => {
        setCurrentDocument(createEmptyDocument());
        setFileName("Untitled.docx");
      });
  }, []);
 
  const handleSave = useCallback(async () => {
    const saved = await editorRef.current?.save();
    if (!saved) return;
    const blob = new Blob([saved], {
      type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
    });
    const url = URL.createObjectURL(blob);
    Object.assign(document.createElement("a"), {
      href: url,
      download: fileName,
    }).click();
    URL.revokeObjectURL(url);
  }, [fileName]);
 
  return (
    <div style={{ height: "100vh", display: "flex", flexDirection: "column" }}>
      <DocxEditor
        ref={editorRef}
        document={documentBuffer ? undefined : currentDocument}
        documentBuffer={documentBuffer}
        showToolbar
        showRuler
        showZoomControl
      />
    </div>
  );
}

Use it in an Astro page

client:only="react" skips SSR and renders only in the browser. The editor uses window, document, and FileReader so this is required:

---
// src/pages/editor.astro
import { Editor } from "../components/DocxEditor";
---
 
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>DOCX Editor</title>
  </head>
  <body>
    <Editor client:only="react" />
  </body>
</html>

Don't use client:load. It attempts SSR first and fails on browser globals.

Astro config

// astro.config.mjs
import { defineConfig } from "astro/config";
import react from "@astrojs/react";
 
export default defineConfig({
  integrations: [react()],
});

File uploads

function handleUpload(e: React.ChangeEvent<HTMLInputElement>) {
  const file = e.target.files?.[0];
  if (!file) return;
  file.arrayBuffer().then((buf) => {
    setDocumentBuffer(buf);
    setFileName(file.name);
  });
}

Save to an API endpoint

// src/pages/api/documents.ts
import type { APIRoute } from "astro";
 
export const POST: APIRoute = async ({ request }) => {
  const data = await request.arrayBuffer();
  // upload to S3, save to DB, etc.
  return new Response(JSON.stringify({ ok: true }), { status: 200 });
};
const saved = await editorRef.current?.save();
await fetch("/api/documents", { method: "POST", body: saved });

Requires output: "server" or output: "hybrid" in your Astro config.

Common errors

ErrorFix
window is not defined at buildUse client:only="react", not client:load
Styles not renderingImport @eigenpal/docx-js-editor/styles.css inside the React component
React integration missingRun npx astro add react

What you get

The editor parses OOXML on the client and renders via ProseMirror. Out of the box: bold/italic/underline, tables with cell merging, inline images, headers and footers, page breaks, tracked changes, threaded comments, zoom, and document outline. It exports back to valid .docx. MIT licensed, ~200KB gzipped, no server dependency.

Next steps