Astro
Run an Astro DOCX editor as a React island with client:only. Why client:load crashes, how styles load, and the path from file upload to .docx download.
By the end of this page you have an Astro page that mounts the editor as a React island, opens a .docx from disk, and downloads the edited result.
Install
npm install @eigenpal/docx-editor-react
npx astro add reactastro add react wires @astrojs/react into astro.config.mjs for you.
Mount the editor
The page stays static HTML; the editor is a single island with client:only="react". This is the structure of examples/astro:
---
// src/pages/index.astro
import { Editor } from '../components/Editor';
---
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>DOCX editor</title>
</head>
<body>
<Editor client:only="react" />
</body>
</html>// src/components/Editor.tsx
import { useRef, useState } from 'react';
import { DocxEditor, type DocxEditorRef } from '@eigenpal/docx-editor-react';
import '@eigenpal/docx-editor-react/styles.css';
export function Editor() {
const editorRef = useRef<DocxEditorRef>(null);
const [buffer, setBuffer] = useState<ArrayBuffer | null>(null);
async function onFileSelect(e: React.ChangeEvent<HTMLInputElement>) {
const file = e.target.files?.[0];
if (file) setBuffer(await file.arrayBuffer());
}
async function onSave() {
const out = await editorRef.current?.save();
if (!out) return;
const blob = new Blob([out], {
type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'document.docx';
a.click();
URL.revokeObjectURL(url);
}
return (
<div style={{ height: '100vh', display: 'flex', flexDirection: 'column' }}>
<div style={{ padding: 8, display: 'flex', gap: 8 }}>
<input type="file" accept=".docx" onChange={onFileSelect} />
<button onClick={onSave}>Save .docx</button>
</div>
<DocxEditor ref={editorRef} documentBuffer={buffer} showToolbar />
</div>
);
}Why this pattern
client:only="react" is the key directive. client:load would still server-render the component once to produce static HTML, and the editor crashes on window during that pass. client:only skips SSR for the island entirely and renders it in the browser only. The rest of the page stays zero-JS static HTML.
Styles need no extra setup: the styles.css import sits inside the island component, and Astro's Vite pipeline bundles CSS imported from client:only components like any other module.
Run the example
The example resolves @eigenpal/* from built output, so build the workspace packages once first:
git clone https://github.com/eigenpal/docx-editor.git
cd docx-editor
bun install
bun run build:packages
bun run dev:astro # http://localhost:4321Next steps
- Quickstart: the load, edit, save flow in detail
- Loading and saving: URLs, autosave, upload to your API
- Astro DOCX editor guide: the long-form walkthrough
- Astro example on GitHub
Remix
Add a Remix DOCX editor with a mount check and React.lazy. Keeps server rendering and hydration intact while the editor loads in the browser only.
Nuxt
Set up a Nuxt DOCX editor with the official module. One line in nuxt.config.ts gives you an auto-imported, SSR-safe DocxEditor component on Nuxt 3 and 4.