How to Create an Image to PDF Converter Using Next.js, Shadcn, and Tailwind

Shehwar Ahmad - Feb 26 - - Dev Community

Image to PDF Converter Design
In this tutorial, we'll create a modern web-based image-to-PDF converter that works entirely in the browser. Our tool will allow users to drag-and-drop images, preview them, and convert them into a downloadable PDF file. We'll use Next.js for the framework, Shadcn for UI components, and Tailwind CSS for styling.

Prerequisites

  • Basic knowledge of React and Next.js
  • Node.js installed on your system
  • A code editor (VS Code recommended)

Step 1: Set Up Next.js Project

npx create-next-app@latest image-to-pdf
Enter fullscreen mode Exit fullscreen mode

Step 2: Install Dependencies

npm install pdf-lib react-dropzone
Enter fullscreen mode Exit fullscreen mode

Add Shadcn components:

npx shadcn-ui@latest add button
Enter fullscreen mode Exit fullscreen mode

Step 3: Create the ImageToPdf Component

Create a new file components/image-to-pdf.tsx and add the following code:

"use client";
import { useState } from "react";
import { PDFDocument } from "pdf-lib";
import { useDropzone } from "react-dropzone";
import { Button } from "@/components/ui/button";

export default function ImageToPdf() {
  const [files, setFiles] = useState<File[]>([]);
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);

  const { getRootProps, getInputProps, isDragActive } = useDropzone({
    accept: { "image/*": [".jpeg", ".jpg", ".png"] },
    onDrop: (acceptedFiles) => {
      setError(null);
      setFiles((prev) => [...prev, ...acceptedFiles]);
    },
  });

  const convertToPdf = async () => {
    setIsLoading(true);
    try {
      const pdfDoc = await PDFDocument.create();

      for (const file of files) {
        const imageBytes = await file.arrayBuffer();
        const image = file.type.includes("jpeg")
          ? await pdfDoc.embedJpg(imageBytes)
          : await pdfDoc.embedPng(imageBytes);

        const page = pdfDoc.addPage([image.width, image.height]);
        const { width, height } = page.getSize();
        const scale = Math.min(width / image.width, height / image.height);

        page.drawImage(image, {
          x: 0,
          y: 0,
          width: image.width * scale,
          height: image.height * scale,
        });
      }

      const pdfBytes = await pdfDoc.save();
      const blob = new Blob([pdfBytes], { type: "application/pdf" });
      const url = URL.createObjectURL(blob);

      const link = document.createElement("a");
      link.href = url;
      link.download = `converted-${Date.now()}.pdf`;
      document.body.appendChild(link);
      link.click();
      URL.revokeObjectURL(url);
      link.remove();
    } catch (error) {
      console.error(error);
      setError("Conversion failed. Please try again with valid images.");
    } finally {
      setIsLoading(false);
    }
  };

  const removeFile = (index: number) => {
    setFiles((prev) => prev.filter((_, i) => i !== index));
  };

  return (
    <div className="bg-card rounded-xl max-w-[800px] mx-auto shadow-lg p-6 sm:p-8 w-full">
      <div className="mb-8 text-center">
        <h1 className="text-3xl font-bold mb-2">Free Image to PDF Converter</h1>
        <p>
          Convert multiple images to a single PDF file instantly in your browser
        </p>
      </div>

      <div
        {...getRootProps()}
        className={`mb-8 p-8 border-2 border-dashed rounded-lg 
        ${isDragActive ? "border-blue-500 bg-blue-50" : "border-gray-200"}`}
      >
        <input {...getInputProps()} />
        <p>
          {isDragActive
            ? "Drop images here"
            : "Drag & drop images, or click to select"}
        </p>
        <p className="text-sm mt-2">Supports: JPEG, PNG</p>
      </div>

      {files.length > 0 && (
        <div className="mb-8">
          <div className="flex items-center justify-between mb-4">
            <h2 className="text-lg font-semibold">
              Selected Images ({files.length})
            </h2>
            <button
              onClick={() => setFiles([])}
              className="text-red-600 hover:text-red-700 text-sm"
            >
              Clear All
            </button>
          </div>

          <div className="grid grid-cols-2 sm:grid-cols-3 gap-4">
            {files.map((file, index) => (
              <div key={index} className="relative group aspect-square">
                <img
                  src={URL.createObjectURL(file)}
                  alt={`Preview ${index + 1}`}
                  className="w-full h-full object-cover rounded-lg border"
                />
                <button
                  onClick={() => removeFile(index)}
                  className="absolute top-1 right-1 bg-red-500 text-white rounded-full 
                    p-1 opacity-0 group-hover:opacity-100 transition-opacity"
                >
                  
                </button>
              </div>
            ))}
          </div>
        </div>
      )}

      <Button
        variant="default"
        onClick={convertToPdf}
        disabled={!files.length || isLoading}
        className="w-full py-6 font-semibold transition-colors"
      >
        {isLoading ? "Creating PDF..." : "Convert Now"}
      </Button>

      {error && <p className="mt-4 text-red-600 text-center">{error}</p>}

      <div className="mt-8 text-center text-sm text-gray-500">
        <p>100% secure - Files never leave your browser</p>
      </div>
    </div>
  );
}

Enter fullscreen mode Exit fullscreen mode

Step 4: Create Home Page

Update your app/page.tsx:

import ImageToPdf from "@/components/image-to-pdf";

export default function Home() {
  return (
    <div className="flex-1 w-full flex flex-col justify-center">
      <ImageToPdf />
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

You've built a modern image-to-PDF converter with essential features while maintaining security and privacy. This implementation shows how powerful client-side processing can be with modern web technologies.

Ready to try it out? Check out the live demo here:

Live Preview: Image to PDF Converter 🚀

.