Next.js
The viewer and the editor run in the browser. In Next.js, they go in a client component, which a page can render whether it's a server component or not.
Install
sh
npm install @docmentis/udoc-viewersh
npm install @docmentis/udoc-editorThe component
tsx
"use client";
import { useEffect, useRef } from "react";
import { UDocViewer } from "@docmentis/udoc-viewer";
/** A viewer filling its element, opening `src`. */
export function DocumentViewer({ src }: { src: string }) {
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
let viewer: UDocViewer | undefined;
// An effect that is cleaned up before `create` resolves, as StrictMode's
// first run is, destroys the viewer it made.
let gone = false;
UDocViewer.create(ref.current!)
.then(async (created) => {
if (gone) return created.destroy();
viewer = created;
await viewer.open(src);
})
.catch((error: unknown) => console.error("The viewer didn't open the document:", error));
return () => {
gone = true;
viewer?.destroy();
};
}, [src]);
return <div ref={ref} className="pane" />;
}tsx
"use client";
import { useEffect, useRef } from "react";
import { UDocEditor } from "@docmentis/udoc-editor";
/** An editor filling its element, opening `src`. */
export function DocumentEditor({ src }: { src: string }) {
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
let editor: UDocEditor | undefined;
// An effect that is cleaned up before `create` resolves, as StrictMode's
// first run is, destroys the editor it made.
let gone = false;
UDocEditor.create(ref.current!)
.then(async (created) => {
if (gone) return created.destroy();
editor = created;
await editor.open(src);
})
.catch((error: unknown) => console.error("The editor didn't open the document:", error));
return () => {
gone = true;
editor?.destroy();
};
}, [src]);
return <div ref={ref} className="pane" />;
}"use client"makes it a client component. Next.js still renders it on the server for the page's first HTML, but an effect runs only in the browser, so the viewer is created only there.- The effect creates the viewer once the element is on the page, and its cleanup destroys it.
createis asynchronous, so the cleanup can run before it resolves, as StrictMode's first mount does in development.gonerecords it, and the viewer created after it is destroyed at once. - A new
srcruns the effect again, which destroys the viewer and creates another for the new document.
Use it
A page renders the component like any other. The viewer fills its element, so give the element a height, here through its pane class:
tsx
import { DocumentEditor } from "@/components/DocumentEditor";
import { DocumentViewer } from "@/components/DocumentViewer";
export default function Home() {
return (
<main className="panes">
<DocumentViewer src="/sample.pdf" />
<DocumentEditor src="/sample.docx" />
</main>
);
}css
.pane {
height: 100vh;
}The engine's files
Turbopack, which next dev and next build use, and webpack, with --webpack, both put the engine's binary and fonts beside your app, so there's nothing to set up. The viewer needs no server of its own, so a static export (output: "export") works as well as an app with a server.
The examples
examples/nextjs-turbopack and examples/nextjs-webpack show the viewer and the editor side by side in a Next.js app, the same code built each way.