Building blocks for PDF viewers
Whole readers assembled from pdfviewcn's components — a toolbar, sidebars, search, and annotation on embedpdf's engine. Preview one, then copy the source and make it yours.
Files
"use client";
import dynamic from "next/dynamic";
// embedpdf's plugin packages and the PDFium worker are browser-only, so the
// whole app is loaded client-side behind an `ssr: false` boundary.
const PdfViewerApp = dynamic(
() =>
import("@/components/pdf-viewer-app").then(
(mod) => mod.PdfViewerApp,
),
{
ssr: false,
loading: () => <div className="bg-muted h-full w-full" />,
},
);
export default function Page() {
return (
// The viewer fills its container and scrolls inside itself, so pin an
// explicit height here rather than letting the document grow the page.
<div className="h-svh w-full overflow-hidden">
<PdfViewerApp documents={[{ url: "/sample.pdf" }]} />
</div>
);
}
Files
"use client";
import dynamic from "next/dynamic";
// embedpdf's plugin packages and the PDFium worker are browser-only, so the
// whole app is loaded client-side behind an `ssr: false` boundary.
const PdfAnnotatorApp = dynamic(
() =>
import("@/components/pdf-annotator-app").then(
(mod) => mod.PdfAnnotatorApp,
),
{
ssr: false,
loading: () => <div className="bg-muted h-full w-full" />,
},
);
export default function Page() {
return (
// The editor fills its container and scrolls inside itself, so pin an
// explicit height here rather than letting the document grow the page.
<div className="h-svh w-full overflow-hidden">
<PdfAnnotatorApp documents={[{ url: "/sample.pdf" }]} />
</div>
);
}
Files
"use client";
import dynamic from "next/dynamic";
// embedpdf's plugin packages and the PDFium worker are browser-only, so the
// whole app is loaded client-side behind an `ssr: false` boundary.
const PdfWorkspaceApp = dynamic(
() =>
import("@/components/pdf-workspace-app").then(
(mod) => mod.PdfWorkspaceApp,
),
{
ssr: false,
loading: () => <div className="bg-muted h-full w-full" />,
},
);
export default function Page() {
return (
// The workspace fills its container and scrolls inside itself, so pin an
// explicit height here rather than letting the document grow the page.
<div className="h-svh w-full overflow-hidden">
{/* Two documents so the tab strip and the split panes have something to
work with from the first frame. */}
<PdfWorkspaceApp
documents={[
{ url: "/sample.pdf", name: "sample.pdf" },
{ url: "/form.pdf", name: "form.pdf" },
]}
/>
</div>
);
}
Files
"use client";
import dynamic from "next/dynamic";
// embedpdf's plugin packages and the PDFium worker are browser-only, so the
// whole app is loaded client-side behind an `ssr: false` boundary.
const PdfCompareApp = dynamic(
() =>
import("@/components/pdf-compare-app").then(
(mod) => mod.PdfCompareApp,
),
{
ssr: false,
loading: () => <div className="bg-muted h-full w-full" />,
},
);
export default function Page() {
return (
// The panes fill their container and scroll inside themselves, so pin an
// explicit height here rather than letting the document grow the page.
<div className="h-svh w-full overflow-hidden">
{/* Two revisions of the same eight pages, a handful of sentences apart —
both generated by `scripts/build-sample-pdf.ts` from one set of page
specs, so the panes line up and the edits are the only difference to
spot. */}
<PdfCompareApp
documents={[
{ url: "/sample.pdf", name: "sample.pdf" },
{ url: "/sample-revised.pdf", name: "sample-revised.pdf" },
]}
/>
</div>
);
}
Files
"use client";
import dynamic from "next/dynamic";
// embedpdf's plugin packages and the PDFium worker are browser-only, so the
// whole app is loaded client-side behind an `ssr: false` boundary.
const PdfLibraryApp = dynamic(
() =>
import("@/components/pdf-library-app").then(
(mod) => mod.PdfLibraryApp,
),
{
ssr: false,
loading: () => <div className="bg-muted h-full w-full" />,
},
);
export default function Page() {
return (
// The library fills its container and scrolls inside itself, so pin an
// explicit height here rather than letting the document grow the page.
<div className="h-svh w-full overflow-hidden">
{/* Three documents, so the grid opens on a shelf rather than a single
card. */}
<PdfLibraryApp
documents={[
{ url: "/sample.pdf", name: "sample.pdf" },
{ url: "/form.pdf", name: "form.pdf" },
{ url: "/sample-with-attachments.pdf", name: "attachments.pdf" },
]}
/>
</div>
);
}
Files
"use client";
import dynamic from "next/dynamic";
// embedpdf's plugin packages and the PDFium worker are browser-only, so the
// whole app is loaded client-side behind an `ssr: false` boundary.
const PdfLibraryBrowser = dynamic(
() =>
import("@/components/pdf-library-browser").then(
(mod) => mod.PdfLibraryBrowser,
),
{
ssr: false,
loading: () => <div className="bg-muted h-full w-full" />,
},
);
export default function Page() {
return (
// The browser fills its container and scrolls inside itself, so pin an
// explicit height here rather than letting the document grow the page.
<div className="h-svh w-full overflow-hidden">
{/* Three documents, so the grid opens on a shelf rather than a single
card. */}
<PdfLibraryBrowser
documents={[
{ url: "/sample.pdf", name: "sample.pdf" },
{ url: "/form.pdf", name: "form.pdf" },
{ url: "/sample-with-attachments.pdf", name: "attachments.pdf" },
]}
/>
</div>
);
}