simple-magazine-flip-book
v0.2.0
Published
SimpleMagazineFlipBook — magazine-style flip book viewer for publication archives, built for Next.js
Maintainers
Readme
SimpleMagazineFlipBook
npm package: simple-magazine-flip-book
Magazine-style flip book viewer for publication archives. Converts a PDF URL to JPEG pages in the browser, opens as soon as the initial spread is ready, and renders (or loads from cache) the rest in the background.
Features
- Themed header, body, and footer (background + text colors)
- Header logo, optional Media Kit link, share current URL
- PDF URL as the primary prop (always a string)
- Cover shown alone; inner pages as two-page spreads on desktop
- Single-page view on viewports ≤ 768px
- Thumbnail grid ("Pages" in the header)
- Color-coded bookmark tabs — optional quick-jump tabs for section starts (e.g. cover, A1, B1)
- Deep linking via
?page=N(shareable URLs, browser back/forward) - Invisible click hotspots for links detected in PDF annotations, text URLs, and QR codes
- IndexedDB page cache — after the first visit, repeat loads reuse rendered JPEGs (no full re-processing on refresh)
- Works in Next.js App Router and Pages Router (client-only)
Install
npm install simple-magazine-flip-book pdfjs-distpdfjs-dist is a direct dependency of this package but must be resolvable in your app (hoisted). With pnpm, add to .npmrc:
public-hoist-pattern[]=pdfjs-distUsage
App Router (Next.js 13+)
// app/magazine/page.tsx
import { FlipBook } from "simple-magazine-flip-book";
import "simple-magazine-flip-book/styles.css";
export default function MagazinePage() {
return (
<FlipBook
pdfUrl="https://cdn.example.com/archive/spring-2024.pdf"
headerBackgroundColor="#1a1a2e"
bodyBackgroundColor="#eaeaea"
footerBackgroundColor="#1a1a2e"
headerTextColor="#ffffff"
footerTextColor="#cccccc"
headerLogo={<img src="/logo.svg" alt="Publication" />}
footerCopyrightText="© 2024 Davis Media. All rights reserved."
mediaKitUrl="https://example.com/media-kit"
/>
);
}FlipBook is a Client Component ("use client"). Import it from a Server Component page as above, or from your own client wrapper.
Pages Router
Disable SSR so PDF.js only runs in the browser:
// pages/magazine/[slug].tsx
import dynamic from "next/dynamic";
import "simple-magazine-flip-book/styles.css";
const FlipBook = dynamic(
() => import("simple-magazine-flip-book").then((m) => m.FlipBook),
{ ssr: false }
);
export default function MagazinePage() {
return (
<FlipBook
pdfUrl="/editions/spring-2024.pdf"
headerBackgroundColor="#1a1a2e"
bodyBackgroundColor="#eaeaea"
footerBackgroundColor="#1a1a2e"
headerTextColor="#ffffff"
footerTextColor="#cccccc"
headerLogo={<img src="/logo.svg" alt="" />}
footerCopyrightText="© 2024 Davis Media"
/>
);
}PDF.js worker (recommended for production)
By default the legacy worker loads from unpkg. For CSP or offline builds, copy the worker to public/ and pass pdfWorkerSrc:
cp node_modules/pdfjs-dist/legacy/build/pdf.worker.min.mjs public/pdf.worker.min.mjs<FlipBook
pdfWorkerSrc="/pdf.worker.min.mjs"
pdfUrl="..."
{/* ...other props */}
/>next.config (optional)
If you see build errors related to canvas:
/** @type {import('next').NextConfig} */
const nextConfig = {
webpack: (config) => {
config.resolve.alias.canvas = false;
return config;
},
};
module.exports = nextConfig;Props
| Prop | Type | Required | Description |
|------|------|----------|-------------|
| pdfUrl | string | yes | URL to the magazine PDF |
| headerBackgroundColor | string | yes | Header bar background |
| bodyBackgroundColor | string | yes | Main reading area background |
| footerBackgroundColor | string | yes | Footer background |
| headerTextColor | string | yes | Header text and button borders |
| footerTextColor | string | yes | Footer text |
| headerLogo | ReactNode | yes | Logo or branding in the header |
| footerCopyrightText | string | yes | Copyright line in the footer |
| mediaKitUrl | string | no | Opens as "Media Kit" in the header |
| pdfWorkerSrc | string | no | Custom PDF.js worker URL |
| pageQuality | number | no | JPEG quality 0–1 (default 0.92) |
| initialView | "reader" \| "thumbnails" | no | Starting view (default "reader") |
| enableDeepLinking | boolean | no | Sync page to URL query (default true) |
| deepLinkParam | string | no | Query param name (default "page") |
| enablePageCache | boolean | no | Store rendered pages in IndexedDB (default true) |
| pageBookmarks | FlipBookBookmark[] | no | Color-coded header tabs that jump to specific pages |
| className | string | no | Root wrapper class |
FlipBookBookmark
Exported from the package for typing your config:
import type { FlipBookBookmark } from "simple-magazine-flip-book";
const bookmarks: FlipBookBookmark[] = [
{ page: 1, color: "#4a8b94", label: "Cover" },
{ page: 5, color: "#c4a035", label: "A1" },
{ page: 17, color: "#8b4a94", label: "B1" },
];| Field | Type | Required | Description |
|-------|------|----------|-------------|
| page | number | yes | 1-based PDF page to open when the tab is clicked |
| color | string | yes | Hex accent for the tab (e.g. #4a8b94, #f60) |
| label | string | no | Tab text; defaults to the page number |
Page bookmarks
Magazines often restart page numbering mid-document (e.g. front matter 1–4, then section A at a1, a2, …). Pass pageBookmarks to show clickable tabs above the page controls so readers can jump to each section start.
<FlipBook
pdfUrl="/editions/spring-2024.pdf"
{/* ...required theme props */}
pageBookmarks={[
{ page: 1, color: "#4a8b94", label: "Cover" },
{ page: 5, color: "#c4a035", label: "A1" },
{ page: 17, color: "#8b4a94", label: "B1" },
]}
/>- Tabs appear in the header when at least one valid bookmark is provided
- Each tab uses its
coloras a bottom border and active-state fill - The active tab is the last bookmark whose
pageis less than or equal to the current page - Out-of-range pages and invalid hex colors are ignored; duplicate pages keep the last entry
Deep links still use physical PDF page numbers (?page=5), not printed labels like A1.
Deep linking
The current page is synced to the query string (default ?page=3). This works in both App Router and Pages Router without Next-specific APIs.
https://yoursite.com/magazine/spring-2024?page=12- Opening a URL with
?page=Njumps to that page after the PDF loads - Prev/next navigation updates the URL (
history.replaceState) - Share copies/opens a URL that includes the current page
- Disable with
enableDeepLinking={false}or customize withdeepLinkParam="p"
Clickable links on pages
While each page is rendered to JPEG, the library also:
- PDF link annotations — native PDF hyperlinks
- Text URLs —
https://,www., and common domain patterns in text layers - QR codes — decoded from the rendered page image
Matching regions get invisible <a> overlays (open in a new tab). Hotspots use normalized coordinates so they stay aligned when the page image scales.
Spread layout
| Step | Desktop view | |------|----------------| | 0 | Page 1 (cover), centered | | 1 | Pages 2–3 | | 2 | Pages 4–5 | | … | Pairs until the last page; odd final page shows alone on the left |
Mobile always shows one PDF page per step.
Page cache
Rendered full pages and thumbnails are stored in IndexedDB (JPEG Blobs plus link metadata for full pages). On reload:
- Only the initial spread is loaded from cache first (near-instant when cached).
- The PDF is still opened once to confirm page count; remaining pages hydrate from cache or render in the background.
Cache entries are keyed by PDF URL, render settings, and a HEAD-request fingerprint (ETag / Content-Length / Last-Modified when CORS allows) so replacing the file at the same URL invalidates stale data.
Disable with enablePageCache={false}. Check support at runtime with isPageCacheAvailable() from the package.
Browser support
Tested targets: Chrome, Firefox, Edge, and Safari (macOS and iOS), current versions.
| Capability | Notes |
|------------|--------|
| PDF.js + Web Worker | Required; host pdf.worker for strict CSP |
| Canvas → JPEG (toBlob / toDataURL) | Safari supported; falls back to data: URLs if toBlob fails |
| blob: image URLs | Used for cached images (Safari 6+) |
| IndexedDB | Used for the page cache; unavailable in some private browsing modes → cache is skipped, viewer still works |
| fetch HEAD | Optional cache busting when the PDF CDN sends CORS headers for HEAD |
| crypto.subtle (SHA-256) | Used for cache keys when available; non-secure contexts use a fallback hash |
| Deep linking (history.replaceState) | Supported in all modern browsers |
| Fullscreen API | Used for the fullscreen toolbar button where available |
Safari-specific: keep the worker on the same origin as the app; very large editions may hit memory limits on older iOS devices (same as any canvas-heavy viewer).
CORS
The PDF URL must be reachable from the user's browser. Cross-origin PDFs need appropriate Access-Control-Allow-Origin headers (or serve the PDF from the same origin). For cache invalidation, HEAD responses should include ETag or Last-Modified when possible.
Demo (Next.js App Router)
npm run build
cd demo
npm install
npm run setup # copies PDF from Downloads + logo + pdf.worker
npm run devOpen the URL shown in the terminal (default http://localhost:3000). See demo/README.md for large-PDF notes.
From the repo root you can also run npm run demo after setup.
Development
npm install
npm run buildLicense
MIT — see LICENSE.
