Next.js
Set up a Next.js DOCX editor in the App Router. Dynamic import with ssr: false, the window is not defined fix, file upload, and saving back to .docx.
This guide shows an App Router route that opens a .docx from disk, edits it in the browser, and downloads the result as a .docx.
Install
npm install @docx-editor.dev/react @docx-editor.dev/coreMount the editor
Two files. The route stays a client route shell; the editor itself loads from a separate component with dynamic() and ssr: false. This is the structure of examples/nextjs.
// app/page.tsx
'use client';
import dynamic from 'next/dynamic';
const Editor = dynamic(() => import('./components/Editor').then((m) => m.Editor), {
ssr: false,
loading: () => <div>Loading editor...</div>,
});
export default function Page() {
return <Editor />;
}// app/components/Editor.tsx
'use client';
import { useRef, useState } from 'react';
import { DocxEditor, type DocxEditorRef } from '@docx-editor.dev/react';
import '@docx-editor.dev/core/styles/editor.css';
export function Editor() {
const editorRef = useRef<DocxEditorRef>(null);
const [buffer, setBuffer] = useState<ArrayBuffer | null>(null);
const [fileName, setFileName] = useState('document.docx');
async function onFileSelect(e: React.ChangeEvent<HTMLInputElement>) {
const file = e.target.files?.[0];
if (!file) return;
setBuffer(await file.arrayBuffer());
setFileName(file.name);
}
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 = fileName;
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>
<div style={{ flex: 1, minHeight: 0 }}>
<DocxEditor ref={editorRef} document={buffer ?? 'blank'} />
</div>
</div>
);
}document={buffer ?? 'blank'} starts the user on an empty page, and replaces it with their file when they pick one. Leaving document undefined is not the same thing. It means no document at all. The editor shows its loading screen, and every control reports no document is loaded until bytes arrive. Use undefined only while your fetch is still running.
The stylesheet import is required once, and the editor fills its parent so it needs a box with a real height.
Why this pattern
The editor reads the DOM and measures text at mount, so it cannot run during server rendering. If you import DocxEditor directly in a route, next build fails during prerender with:
ReferenceError: window is not defineddynamic(..., { ssr: false }) is the fix: the editor module is never evaluated on the server,
and it stays out of the route's initial bundle. Keep the rest of the page (nav, footer) as server
components; only the editor needs the client boundary.
Run the example
The example resolves @docx-editor.dev/* from the monorepo workspace, so build the packages once first:
git clone https://github.com/eigenpal/docx-editor.git
cd docx-editor
bun install
bun run build:packages
bun run dev:nextjs # http://localhost:3000Next steps
- Quickstart: the load, edit, save flow in detail
- Loading and saving: URLs, autosave, upload to your API
- Next.js DOCX editor guide: the detailed walkthrough
- Next.js example on GitHub