npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

simple-magazine-flip-book

v0.2.0

Published

SimpleMagazineFlipBook — magazine-style flip book viewer for publication archives, built for Next.js

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-dist

pdfjs-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-dist

Usage

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 color as a bottom border and active-state fill
  • The active tab is the last bookmark whose page is 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=N jumps 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 with deepLinkParam="p"

Clickable links on pages

While each page is rendered to JPEG, the library also:

  1. PDF link annotations — native PDF hyperlinks
  2. Text URLshttps://, www., and common domain patterns in text layers
  3. 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:

  1. Only the initial spread is loaded from cache first (near-instant when cached).
  2. 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 dev

Open 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 build

License

MIT — see LICENSE.