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.

To create our app, we’ll be following these steps:
- Setting up the project
- Initializing the SDK
- Configuring the document scanner
- 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.

💡 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! 🤳