Build a Simple Next.js Interactive PDF Viewer App

Written by

in

In today’s digital world, PDFs are ubiquitous. From official documents and eBooks to brochures and reports, we encounter them daily. As web developers, the ability to seamlessly integrate PDF viewing capabilities into our applications is invaluable. Imagine a user-friendly interface where users can easily view, navigate, and even interact with PDF documents directly within your website. This article will guide you through building precisely that: a simple, yet effective, interactive PDF viewer application using Next.js.

Why Build a PDF Viewer with Next.js?

Next.js offers several advantages for this project:

  • Server-Side Rendering (SSR) and Static Site Generation (SSG): Improve SEO and initial load times. PDFs often contain important information, and SSR ensures that search engines can crawl and index the content.
  • React-Based: Leverage the power and flexibility of React for building interactive UI components.
  • Optimized Performance: Next.js automatically optimizes images and other assets, leading to a faster and more responsive user experience.
  • Easy Deployment: Deploy your application effortlessly to various platforms like Vercel, Netlify, or AWS.

This project will provide a solid foundation for understanding how to handle and display PDF documents within a web application, and it opens doors to more advanced functionalities like annotation, form filling, and PDF manipulation.

Prerequisites

Before we begin, make sure you have the following installed:

  • Node.js and npm (or yarn): These are essential for managing project dependencies and running the development server.
  • A code editor: Visual Studio Code, Sublime Text, or any other editor you prefer.
  • Basic understanding of HTML, CSS, and JavaScript: Familiarity with React will be helpful.

Setting Up the Next.js Project

Let’s start by creating a new Next.js project. Open your terminal and run the following command:

npx create-next-app pdf-viewer-app

Navigate into the project directory:

cd pdf-viewer-app

Now, install the necessary dependencies. We’ll be using a library called pdfjs-dist for rendering PDFs. This is the official Mozilla PDF.js library packaged for use in JavaScript environments. Run the following command:

npm install pdfjs-dist

Building the PDF Viewer Component

Create a new file called PDFViewer.js inside the components directory (you may need to create this directory). This will be our main component for displaying the PDF.

Here’s the basic structure:

// components/PDFViewer.js
import { useState, useEffect } from 'react';
import { pdfjs } from 'pdfjs-dist';

function PDFViewer({ pdfUrl }) {
  const [pdf, setPdf] = useState(null);
  const [pageNumber, setPageNumber] = useState(1);
  const [numPages, setNumPages] = useState(null);

  useEffect(() => {
    async function loadPdf() {
      if (!pdfUrl) return;
      pdfjs.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.min.js`;
      const loadingTask = pdfjs.getDocument(pdfUrl);
      try {
        const pdf = await loadingTask.promise;
        setPdf(pdf);
        setNumPages(pdf.numPages);
      } catch (error) {
        console.error('Error loading PDF:', error);
      }
    }
    loadPdf();
  }, [pdfUrl]);

  const renderPage = async (num) => {
    if (!pdf) return null;
    const page = await pdf.getPage(num);
    const viewport = page.getViewport({ scale: 1.5 }); // Adjust scale as needed
    const canvas = document.createElement('canvas');
    const context = canvas.getContext('2d');
    canvas.height = viewport.height;
    canvas.width = viewport.width;
    const renderContext = {
      canvasContext: context,
      viewport: viewport,
    };
    await page.render(renderContext);
    return canvas;
  };

  const handlePageChange = (newPage) => {
    if (newPage >= 1 && newPage <= numPages) {
      setPageNumber(newPage);
    }
  };

  if (!pdf) {
    return <p>Loading PDF...</p>;
  }

  return (
    <div>
      <div>
        <button> handlePageChange(pageNumber - 1)} disabled={pageNumber === 1}>Previous</button>
        <span>Page {pageNumber} of {numPages}</span>
        <button> handlePageChange(pageNumber + 1)} disabled={pageNumber === numPages}>Next</button>
      </div>
      <div style="{{">
        <div id="pdf-container"></div>
      </div>
    </div>
  );
}

export default PDFViewer;

Let’s break down this component:

  • Import Statements: We import the necessary modules from React and the pdfjs-dist library.
  • State Variables:
    • pdf: Stores the PDF document object.
    • pageNumber: Tracks the current page number.
    • numPages: Stores the total number of pages in the PDF.
  • useEffect Hook: This hook handles the PDF loading and is triggered when the pdfUrl prop changes. Inside, we:
    • Set the worker source for pdfjs.
    • Use pdfjs.getDocument() to load the PDF from the provided URL.
    • Update the pdf and numPages state variables.
    • Handle any errors that occur during the loading process.
  • renderPage Function: This asynchronous function renders a specific page of the PDF.
    • It retrieves a page object using pdf.getPage().
    • It creates a viewport to define the size and orientation of the page. The scale value can be adjusted for zoom.
    • It creates a canvas element and renders the page onto it.
    • Returns the created canvas element.
  • handlePageChange Function: Updates the pageNumber state when the user navigates between pages.
  • Conditional Rendering: Displays “Loading PDF…” while the PDF is being loaded.
  • UI Structure: Includes buttons for navigation (previous and next), a display of the current page, and a container for the PDF content.

Integrating the PDF Viewer into Your Page

Now, let’s integrate the PDFViewer component into our main page (pages/index.js). Replace the content of pages/index.js with the following:

// pages/index.js
import PDFViewer from '../components/PDFViewer';

export default function Home() {
  // Replace with the actual URL of your PDF
  const pdfUrl = '/example.pdf';

  return (
    <div>
      <h1>PDF Viewer Example</h1>
      
    </div>
  );
}

In this code:

  • We import the PDFViewer component.
  • We define a pdfUrl variable that holds the URL of your PDF file. **Important:** You’ll need to replace '/example.pdf' with the actual URL of your PDF. For local testing, you’ll need to put a PDF file in the public directory of your Next.js project.
  • We render the PDFViewer component and pass the pdfUrl as a prop.

Adding a Sample PDF

For testing purposes, you’ll need a PDF file. You can either use an existing PDF or create a simple one. Place the PDF file in the public directory of your Next.js project. Make sure the pdfUrl in pages/index.js matches the filename (e.g., /my-document.pdf if your file is named my-document.pdf).

Running the Application

Start the development server by running the following command in your terminal:

npm run dev

Open your browser and navigate to http://localhost:3000. You should see your PDF viewer with the first page of your PDF displayed. Use the “Previous” and “Next” buttons to navigate through the pages.

Styling the PDF Viewer (Optional)

You can customize the appearance of the PDF viewer by adding CSS styles. For example, you can add styles to the PDFViewer component to control the size, layout, and appearance of the PDF display area. You can either add inline styles or create a separate CSS file (e.g., PDFViewer.module.css) and import it into your component.

Here’s an example of how you might style the PDF container:

// PDFViewer.module.css
.pdfContainer {
  width: 80%; /* Adjust as needed */
  margin: 20px auto;
  border: 1px solid #ccc;
  overflow: auto; /* Enable scrolling if the PDF is larger than the container */
}

Then, in your PDFViewer.js component, import the CSS file and apply the class to your PDF container div:

// components/PDFViewer.js
import styles from './PDFViewer.module.css';

 // ... inside the return statement
 <div className={styles.pdfContainer} id="pdf-container"></div>

Handling Errors

Error handling is crucial for a robust application. The pdfjs-dist library can throw errors during PDF loading or rendering. You should implement error handling to provide informative feedback to the user. For instance, you can display an error message if the PDF fails to load or if there’s an issue with the file format.

In the useEffect hook of your PDFViewer component, you already have a try...catch block to handle errors during PDF loading. You can extend this to display an error message to the user if an error occurs.

useEffect(() => {
  async function loadPdf() {
    if (!pdfUrl) return;
    pdfjs.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.min.js`;
    const loadingTask = pdfjs.getDocument(pdfUrl);
    try {
      const pdf = await loadingTask.promise;
      setPdf(pdf);
      setNumPages(pdf.numPages);
    } catch (error) {
      console.error('Error loading PDF:', error);
      // Display an error message to the user
      setError('Error loading PDF. Please ensure the URL is correct and the file is valid.');
    }
  }
  loadPdf();
}, [pdfUrl]);

You’ll need to add an error state variable and conditionally render an error message in your component’s UI.

Common Mistakes and How to Fix Them

  • Incorrect PDF URL: The most common issue is providing an incorrect URL for the PDF file. Double-check the path and filename. Ensure the PDF is accessible from your Next.js application (e.g., in the public directory).
  • CORS Issues: If you’re loading a PDF from a different domain, you might encounter CORS (Cross-Origin Resource Sharing) errors. Make sure the server hosting the PDF allows requests from your domain. For local development, you might need to use a proxy or configure CORS settings in your browser.
  • Missing PDF.js Worker: The pdfjs-dist library requires a worker file to handle PDF processing. Ensure that you correctly set the pdfjs.GlobalWorkerOptions.workerSrc to the correct path, as shown in the code examples.
  • Rendering Issues: If the PDF isn’t rendering correctly, check the scale factor in the getViewport method. Adjust the scale to control the zoom level and ensure the PDF fits within the container. Also, ensure that your CSS is not interfering with the display of the canvas element.
  • Dependency Conflicts: Make sure you have the correct versions of pdfjs-dist and other dependencies. Incompatible versions can lead to unexpected behavior.

Key Takeaways

  • Component-Based Architecture: Break down your application into reusable components for better organization and maintainability.
  • Asynchronous Operations: Handle asynchronous tasks (like loading the PDF) using async/await to keep your code clean and readable.
  • State Management: Use React’s state management to manage the PDF document, page number, and other relevant data.
  • Error Handling: Implement robust error handling to provide a better user experience and debug issues effectively.
  • Optimization: Consider optimizing the rendering process for large PDFs to improve performance. Techniques like lazy loading pages or pre-rendering can be helpful.

Advanced Features (Optional)

Once you have a working PDF viewer, you can explore adding more advanced features:

  • Zoom and Pan: Implement zoom and pan functionality to allow users to explore the PDF in more detail.
  • Annotations: Allow users to add annotations (e.g., highlights, comments, drawings) to the PDF.
  • Form Filling: Enable users to fill out interactive PDF forms.
  • Search: Add a search feature to allow users to search for text within the PDF.
  • PDF Manipulation: Provide options for users to rotate, crop, or merge PDFs.
  • Print Functionality: Add a button to allow users to print the PDF.

FAQ

Here are some frequently asked questions about building a PDF viewer in Next.js:

  1. Can I use a PDF from a remote URL? Yes, you can load PDFs from remote URLs, but you might need to handle CORS issues if the server hosting the PDF doesn’t allow cross-origin requests.
  2. How do I handle large PDFs? For large PDFs, consider techniques like lazy loading pages or pre-rendering to improve performance.
  3. Can I add annotations to the PDF? Yes, you can add annotations using libraries that build on top of PDF.js or by implementing your own annotation functionality.
  4. Is this project SEO-friendly? The SSR and SSG capabilities of Next.js can help with SEO. Make sure the content of the PDFs is accessible to search engines.

Building a PDF viewer in Next.js is a practical project that combines web development skills with the ability to handle PDF documents. By following this guide, you should have a solid understanding of how to display and navigate PDFs within your web application. Remember to adapt the code and features to meet your specific project requirements, and don’t hesitate to experiment with the advanced features to enhance your application’s capabilities. With this foundation, you can create a versatile and user-friendly PDF viewing experience for your users.