Scanbot SDK has been acquired by Apryse! Learn more

Learn more
Skip to content

Building a Next.js Document Scanner web app with TypeScript

Kevin June 16, 2025 11 mins read
Web Document Scanner SDK

In this tutorial, we’ll create a web app for scanning documents and exporting them as PDF files using Next.js.

We’ll write the app in TypeScript and use the Scanbot Web Document Scanner SDK for the actual document scanning functionalities and app UI.

Scanning a document and exporting it as a PDF with our Next.js Document Scanner web app

To create our app, we’ll be following these steps:

  1. Setting up the project
  2. Initializing the SDK
  3. Configuring the document scanner
  4. Implementing the PDF export feature

Let’s get started!

Want to see the final code right away? Click here.

page.tsx:

"use client";

import { useEffect, useState, useRef } from "react";
import type ScanbotSDK from "scanbot-web-sdk/ui";
import type { DocumentScannerUIResult, PdfConfiguration } from "scanbot-web-sdk/@types";

export default function Home() {
  const [scanResult, setScanResult] = useState<DocumentScannerUIResult | null>(null);
  const scanbotSdkRef = useRef<typeof ScanbotSDK | null>(null);
  const [isSdkReady, setIsSdkReady] = useState(false);  // New state for loading

  useEffect(() => {
    const initializeSDK = async () => {
      try {
        const sdk = (await import('scanbot-web-sdk/ui')).default;

        if (sdk) {
          await sdk.initialize({
            licenseKey: "", // Empty for 60-second trial mode
            enginePath: "/wasm/",
          });
          setIsSdkReady(true); // Set SDK to ready after successful initialization
          scanbotSdkRef.current = sdk; // Set ref to the initialized SDK instance
        }
      } catch (error) {
        console.error("Failed to initialize Scanbot SDK:", error);
        setIsSdkReady(false);
      }
    };

    initializeSDK();
  }, []);

  // Function to launch the document scanner UI
  const runDocumentScanner = async () => {
    const scanbotSDK = scanbotSdkRef.current;

    if (!scanbotSDK) {
      console.warn("Scanbot SDK not ready yet.");
      return;
    }

    try {
      const config = new scanbotSDK.UI.Config.DocumentScanningFlow();
      const result = await scanbotSDK.UI.createDocumentScanner(config);

      if (result) {
        setScanResult(result);
      }
    } catch (error) {
      console.error("Error during document scanning:", error);
    }
  };

  // Function to handle the PDF export process
  const handleDocumentExport = async () => {
    if (!scanResult) return;

    try {
      const options: Partial<PdfConfiguration> & { runOcr: boolean } = {
        pageSize: "A4",
        pageDirection: "PORTRAIT",
        pageFit: "FIT_IN",
        dpi: 72,
        jpegQuality: 80,
        runOcr: false
      };

      const document = await scanResult.document.createPdf(options);
      await exportPdf(document);
    } catch (error) {
      console.error("Error exporting PDF:", error);
    }
  };

  const exportPdf = async (documentData: ArrayBuffer) => {
    const a = document.createElement("a");
    document.body.appendChild(a);
    a.style.display = "none";
    const blob = new Blob([documentData], { type: 'application/pdf' });
    const url = window.URL.createObjectURL(blob);
    a.href = url;
    a.download = 'scanned-document.pdf';
    a.click();
    window.URL.revokeObjectURL(url);
    document.body.removeChild(a);
  };

  return (
    <div>
      {isSdkReady ? (
        <>
          <button onClick={runDocumentScanner}>Run Scanner</button>
          {scanResult && <button onClick={handleDocumentExport}>Export PDF</button>}
        </>
      ) : (
        <p>Loading Scanbot SDK...</p>
      )}
    </div>
  );
}

Step 1: Set up the project

We’ll be using Next.js with Turbopack to set up our project. Next.js is a popular React framework for building server-rendered and statically generated web applications.

Open a terminal and create a new Next.js project with the following command:

npx create-next-app@latest

You will be asked to name your project. For this tutorial, let’s go with “scanbot-next-doc-tut”.

When prompted further, select these options:

  • TypeScript: Yes
  • ESLint: No
  • Tailwind CSS: No
  • Place code inside src/ directory: No
  • App Router: Yes
  • Turbopack for next dev: Yes
  • Customize the import alias (@/* by default): No

Then navigate to your project folder.

cd scanbot-next-doc-tut

Open app/page.tsx and replace the file’s contents with the following:

export default function Home() {
  return <div></div>;
}

Step 2: Initialize the SDK

In your project folder, install the Scanbot Web SDK package with the following command:

npm i scanbot-web-sdk

The SDK contains WebAssembly binaries that should be hosted on your server. We ship these binaries with the npm package. Since Node.js doesn’t copy the binaries to the target automatically, you need to manually copy them to the desired destination (we recommend the public asset directory).

You can quickly copy them from node_modules to a folder called wasm in the public asset directory using the following command:

mkdir -p public/wasm && cp -r node_modules/scanbot-web-sdk/bundle/bin/complete/* public/wasm

💡 To ensure these files are always copied to the same directory for all users and updated when the SDK itself is updated, you can add the command as a post-installation script to your package.json:

"postinstall": "mkdir -p public/wasm && cp -r node_modules/scanbot-web-sdk/bundle/bin/complete/* public/wasm"

Next, we want to specify "use client" at the top of our page.tsx so that Next.js knows this is a Client Component that runs in the browser. We’ll also import the ScanbotSDK type for the Ready-To-Use UI and the DocumentScannerUIResult type for the scan result functionality.

Your page.tsx should look like this:

"use client";

import { useEffect } from "react";
import type ScanbotSDK from "scanbot-web-sdk/ui";
import type { DocumentScannerUIResult } from "scanbot-web-sdk/@types";

export default function Home() {
  return <div></div>;
}

Loading the Scanbot SDK

Now, we’ll create a function within the Home component to load the Scanbot SDK. We’ll also set up the variables we’ll need to store the SDK and its scanning result.

We use a useEffect hook to initialize the SDK when the component mounts. This hook will also apply a new state variable isSdkReady to indicate whether the SDK has been initialized successfully. We’ll use this to conditionally render the scanner buttons.

Additionally, the scanbotSdkRef is used to hold a reference to the initialized SDK, which we can then access in other parts of the app.

  const [scanResult, setScanResult] = useState<DocumentScannerUIResult | null>(null);
  const scanbotSdkRef = useRef<typeof ScanbotSDK | null>(null);
  const [isSdkReady, setIsSdkReady] = useState(false);

  useEffect(() => {
    const initializeSDK = async () => {
      try {
        const sdk = (await import('scanbot-web-sdk/ui')).default;

        if (sdk) {
          await sdk.initialize({
            licenseKey: "", // Leave empty for 60-second trial mode
            enginePath: "/wasm/",
          });
          setIsSdkReady(true);
          scanbotSdkRef.current = sdk;
        }
      } catch (error) {
        console.error("Failed to initialize Scanbot SDK:", error);
        setIsSdkReady(false);
      }
    };

    initializeSDK();
  }, []);

Step 3: Configure the document scanner

Now that the SDK is initialized, we can create a function to run the document scanner. This function will create a new instance of the DocumentScanningFlow configuration and pass it to the createDocumentScanner method of the Scanbot SDK. The result of the scan will be stored in the state variable scanResult, which we’ll use later to export to PDF.

const runDocumentScanner = async () => {
  const scanbotSDK = scanbotSdkRef.current;

  if (!scanbotSDK) {
    console.warn("Scanbot SDK not ready yet.");
    return;
  }

  try {
    const config = new scanbotSDK.UI.Config.DocumentScanningFlow();
    const result = await scanbotSDK.UI.createDocumentScanner(config);

    if (result) {
        setScanResult(result);
    }
  } catch (error) {
    console.error("Error during document scanning:", error);
  }
};

Adding a button to trigger the document scanner

Now, we need to add a button to the UI to trigger the document scanner. We’ll add this inside the return statement of the Home component. We’ll then assign the runDocumentScanner function to the onClick event of the button, which only shows when the SDK is ready.

return (
    <div>
      {isSdkReady ? (
        <>
          <button onClick={runDocumentScanner}>Run Scanner</button>
        </>
      ) : (
        <p>Loading Scanbot SDK...</p>
      )}
    </div>
  );
}

Your page.tsx should now look like this:

"use client";

import { useEffect, useState, useRef } from "react";
import type ScanbotSDK from "scanbot-web-sdk/ui";
import type { DocumentScannerUIResult } from "scanbot-web-sdk/@types";

export default function Home() {

const [scanResult, setScanResult] = useState<DocumentScannerUIResult | null>(null);
const scanbotSdkRef = useRef<typeof ScanbotSDK | null>(null);
const [isSdkReady, setIsSdkReady] = useState(false);

useEffect(() => {
    const initializeSDK = async () => {
      try {
        const sdk = (await import('scanbot-web-sdk/ui')).default;

        if (sdk) {
          await sdk.initialize({
            licenseKey: "", // Empty for 60-second trial mode
            enginePath: "/wasm/",
          });
          setIsSdkReady(true); // Set SDK to ready after successful initialization
          scanbotSdkRef.current = sdk; // Set ref to the initialized SDK instance
        }
      } catch (error) {
        console.error("Failed to initialize Scanbot SDK:", error);
        setIsSdkReady(false);
      }
    };

    initializeSDK();
  }, []);

// Function to launch the document scanner UI
const runDocumentScanner = async () => {
  const scanbotSDK = scanbotSdkRef.current;

  if (!scanbotSDK) {
    console.warn("Scanbot SDK not ready yet.");
    return;
  }

  try {
    const config = new scanbotSDK.UI.Config.DocumentScanningFlow();
    const result = await scanbotSDK.UI.createDocumentScanner(config);

    if (result) {
        setScanResult(result);
    }
  } catch (error) {
    console.error("Error during document scanning:", error);
  }
};

return (
    <div>
      {isSdkReady ? (
        <>
          <button onClick={runDocumentScanner}>Run Scanner</button>
        </>
      ) : (
        <p>Loading Scanbot SDK...</p>
      )}
    </div>
  );
}

To test the app locally, use the command:

npm run dev

And navigate to http://localhost:3000 to ensure the document scanner works as expected.

Currently, we can’t do anything with the scanned pages, so let’s change that!

Step 4: Implement the PDF export feature

The PDF export functionality is implemented within the handleDocumentExport function. This function takes the scanResult from the previous step and uses it to generate a PDF document with specified options. The generated PDF document is then downloaded to the user’s computer. The exportPdf function handles the actual PDF file download process.

First, import the PdfConfiguration type:

import type { DocumentScannerUIResult, PdfConfiguration } from "scanbot-web-sdk/@types";

Then create the PDF export functions:

  const handleDocumentExport = async () => {
    if (!scanResult) return;

    try {
      const options: Partial<PdfConfiguration> & { runOcr: boolean } = {
        pageSize: "A4",
        pageDirection: "PORTRAIT",
        pageFit: "FIT_IN",
        dpi: 72,
        jpegQuality: 80,
        runOcr: false
      };

      const document = await scanResult.document.createPdf(options);
      await exportPdf(document);
    } catch (error) {
      console.error("Error exporting PDF:", error);
    }
  };

  const exportPdf = async (documentData: ArrayBuffer) => {
    const a = document.createElement("a");
    document.body.appendChild(a);
    a.style.display = "none";
    const blob = new Blob([documentData], { type: 'application/pdf' });
    const url = window.URL.createObjectURL(blob);
    a.href = url;
    a.download = 'scanned-document.pdf';
    a.click();
    window.URL.revokeObjectURL(url);
    document.body.removeChild(a);
  };

Finally, we can add another button to export the PDF when the scan result is available.

  return (
    <div >
      {isSdkReady ? (
        <>
          <button onClick={runDocumentScanner}>Run Scanner</button>
          {scanResult && <button onClick={handleDocumentExport}>Export PDF</button>}
        </>
      ) : (
        <p>Loading Scanbot SDK...</p>
      )}
    </div>
  );
}

Your final page.tsx will look like this:

"use client";

import { useEffect, useState, useRef } from "react";
import type ScanbotSDK from "scanbot-web-sdk/ui";
import type { DocumentScannerUIResult, PdfConfiguration } from "scanbot-web-sdk/@types";

export default function Home() {
  const [scanResult, setScanResult] = useState<DocumentScannerUIResult | null>(null);
  const scanbotSdkRef = useRef<typeof ScanbotSDK | null>(null);
  const [isSdkReady, setIsSdkReady] = useState(false);  // New state for loading

  useEffect(() => {
    const initializeSDK = async () => {
      try {
        const sdk = (await import('scanbot-web-sdk/ui')).default;

        if (sdk) {
          await sdk.initialize({
            licenseKey: "", // Empty for 60-second trial mode
            enginePath: "/wasm/",
          });
          setIsSdkReady(true); // Set SDK to ready after successful initialization
          scanbotSdkRef.current = sdk; // Set ref to the initialized SDK instance
        }
      } catch (error) {
        console.error("Failed to initialize Scanbot SDK:", error);
        setIsSdkReady(false);
      }
    };

    initializeSDK();
  }, []);

  // Function to launch the document scanner UI
  const runDocumentScanner = async () => {
    const scanbotSDK = scanbotSdkRef.current;

    if (!scanbotSDK) {
      console.warn("Scanbot SDK not ready yet.");
      return;
    }

    try {
      const config = new scanbotSDK.UI.Config.DocumentScanningFlow();
      const result = await scanbotSDK.UI.createDocumentScanner(config);

      if (result) {
        setScanResult(result);
      }
    } catch (error) {
      console.error("Error during document scanning:", error);
    }
  };

  // Function to handle the PDF export process
  const handleDocumentExport = async () => {
    if (!scanResult) return;

    try {
      const options: Partial<PdfConfiguration> & { runOcr: boolean } = {
        pageSize: "A4",
        pageDirection: "PORTRAIT",
        pageFit: "FIT_IN",
        dpi: 72,
        jpegQuality: 80,
        runOcr: false
      };

      const document = await scanResult.document.createPdf(options);
      await exportPdf(document);
    } catch (error) {
      console.error("Error exporting PDF:", error);
    }
  };

  const exportPdf = async (documentData: ArrayBuffer) => {
    const a = document.createElement("a");
    document.body.appendChild(a);
    a.style.display = "none";
    const blob = new Blob([documentData], { type: 'application/pdf' });
    const url = window.URL.createObjectURL(blob);
    a.href = url;
    a.download = 'scanned-document.pdf';
    a.click();
    window.URL.revokeObjectURL(url);
    document.body.removeChild(a);
  };

  return (
    <div>
      {isSdkReady ? (
        <>
          <button onClick={runDocumentScanner}>Run Scanner</button>
          {scanResult && <button onClick={handleDocumentExport}>Export PDF</button>}
        </>
      ) : (
        <p>Loading Scanbot SDK...</p>
      )}
    </div>
  );
}

Now you can run your Next.js app using:

npm run dev

And navigate to http://localhost:3000 to see your document scanner web app in action.

Scanning a document and exporting it as a PDF with our Next.js Document Scanner web app

💡 To test your web app on your phone, you have a few options. One option is to use a service like ngrok, which creates a tunnel to one of their SSL-certified domains. Their Quick Start guide will help you get up and running quickly.

Conclusion

🎉 Congratulations! You can now scan documents from your browser and export them as PDFs!

If this tutorial has piqued your interest in integrating document scanning functionalities into your web app or website, make sure to take a look at our SDK’s other neat features in the documentation – or run our example project for a more hands-on experience.

Should you have questions about this tutorial or run into any issues, we’re happy to help! Just shoot us an email via tutorial-support@scanbot.io.

Happy scanning! 🤳

Related blog posts