How to Build a Document Scanner in Next.js

Next.js is the recommended framework to create React applications with extra features like server-side rendering and an intuitive router.

In this article, we are going to create a document scanning app with Next.js and Dynamic Web TWAIN.

Getting Started With Dynamic Web TWAIN

Build a Document Scanning Web App with Next.js

Let’s do this in steps.

New Project

Create a new Next.js project named document-scanner:

npx create-next-app@latest document-scanner

Then, we can run the following to test it:

cd document-scanner
npm run dev

Install Dependencies

Install Dynamic Web TWAIN from npm:

npm install dwt

Create a Component for Dynamic Web TWAIN

Dynamic Web TWAIN provides a document viewer control and a bunch of APIs to scan and manage documents. We are going to wrap the viewer as a React component and expose the object of Dynamic Web TWAIN to call different APIs.

Here is the basic content of the component:

interface props {
  onWebTWAINReady?: (dwt:WebTwain) => void;
}

const DWT: React.FC<props> = (props: props)  => {
  const containerID = "dwtcontrolContainer";
  const container = useRef<HTMLDivElement>(null);
  useEffect(()=>{
    Dynamsoft.DWT.RegisterEvent('OnWebTwainReady', () => {
      const DWObject = Dynamsoft.DWT.GetWebTwain(containerID);
      if (props.onWebTWAINReady) {
        props.onWebTWAINReady(DWObject); // expose the object of Dynamic Web TWAIN
      }
    });
    Dynamsoft.DWT.ResourcesPath = "https://unpkg.com/dwt@18.1.1/dist"; //use the resources from a CDN
    Dynamsoft.DWT.Containers = [{
        WebTwainId: 'dwtObject',
        ContainerId: containerID
    }];
    Dynamsoft.DWT.Load();
  },[]);

  return (
    <div ref={container} id={containerID}></div>
  );
}

export default DWT;

There are some additional props we can add to the component:

  1. A license to activate Dynamic Web TWAIN. We can apply for a trial license here.

    interface props {
      license?:string;
    }
    useEffect(()=>{
      if (props.license) {
        Dynamsoft.DWT.ProductKey = props.license;
      }
    },[]);
    
  2. Width and height for the viewer.

    interface props {
      width?: string;
      height?: string;
    }
    useEffect(()=>{
      Dynamsoft.DWT.RegisterEvent('OnWebTwainReady', () => {
        const DWObject = Dynamsoft.DWT.GetWebTwain(containerID);
        DWObject.Viewer.width = "100%";
        DWObject.Viewer.height = "100%";
        if (props.width) {
          if (container.current) {
            container.current.style.width = props.width;
          }
        }
        if (props.height) {
          if (container.current) {
            container.current.style.height = props.height;
          }
        }
      });
    },[]);
    
  3. View mode for the viewer:

    interface props {
      viewMode?: {cols:number,rows:number};
    }
    useEffect(()=>{
      Dynamsoft.DWT.RegisterEvent('OnWebTwainReady', () => {
        const DWObject = Dynamsoft.DWT.GetWebTwain(containerID);
        if (props.viewMode) {
          DWObject.Viewer.setViewMode(props.viewMode.cols,props.viewMode.rows);
        }
      });
    },[]);
    

Use the Component in the App

Next, we are going to use the component in the app.

  1. Because Dynamic Web TWAIN uses navigator which is only available in the browser, we need to disable server-side rendering for the component. We can import the component with the following code to do this:

    const DWT = dynamic(() => import("../components/DWT"), {
      ssr: false,
      loading: () => <p>Initializing Document Scanner</p>,
    });
    
  2. Update index.tsx to use the component.

    import { WebTwain } from 'dwt/dist/types/WebTwain';
    import dynamic from 'next/dynamic';
    import Head from 'next/head'
    import styles from '../styles/Home.module.css'
    import React from 'react';
    
    const DWT = dynamic(() => import("../components/DWT"), {
      ssr: false,
      loading: () => <p>Initializing Document Scanner</p>,
    });
    
    export default function Home() {
      const DWObject = React.useRef<WebTwain>();
      const onWebTWAINReady = (dwtObject:WebTwain) => {
        DWObject.current = dwtObject;
      }
      return (
        <>
          <Head>
            <title>Next.js Document Scanner</title>
            <meta name="description" content="Generated by create next app" />
            <meta name="viewport" content="width=device-width, initial-scale=1" />
            <link rel="icon" href="/favicon.ico" />
          </Head>
          <main>
            <div>
              <h2>Document Scanner</h2>
              <div className={styles.container}>
                <DWT
                  width='100%'
                  height='100%'
                  viewMode={{cols:2,rows:2}}
                  onWebTWAINReady={onWebTWAINReady}
                ></DWT>
              </div>
            </div>
          </main>
        </>
      )
    }
    
  3. Add a scan button to scan documents.

    const scan = () => {
      if (DWObject.current) {
        DWObject.current.SelectSource(function () {
          DWObject.current!.OpenSource();
          DWObject.current!.AcquireImage();
        },
          function () {
            console.log("SelectSource failed!");
          }
        );
      }
    }
    return (
      <>
        <main>
          <div>
            <h2>Document Scanner</h2>
            <button onClick={scan}>Scan</button>
          </div>
        </main>
      </>
    )
    
  4. Add a save button to save the scanned documents into a PDF file.

    const save = () => {
      if (DWObject.current) {
        DWObject.current.SaveAllAsPDF("Scanned");
      }
    }
    return (
      <>
        <main>
          <div>
            <h2>Document Scanner</h2>
            <button onClick={save}>Save</button>
          </div>
        </main>
      </>
    )
    

All right, we’ve now finished the document scanner. You can use the online demo to have a try.

Source Code

https://github.com/tony-xlh/NextJS-Document-Scanner