Skip to content

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-viewer
sh
npm install @docmentis/udoc-editor

The 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. create is asynchronous, so the cleanup can run before it resolves, as StrictMode's first mount does in development. gone records it, and the viewer created after it is destroyed at once.
  • A new src runs 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.