How to Build a Barcode and QR Code Scanner in Next.js with SSR

Next.js is a flexible React framework that gives you building blocks to create fast web applications. It can pre-render your React app so that it is SEO-friendly. It supports several types of pre-rendering including server-side rendering or static generation, and updating or creating content at runtime with incremental static regeneration.

In this article, we are going to build a barcode and QR code scanner using Next.js. Dynamsoft Camera Enhancer is used to access the camera in browsers and Dynamsoft Barcode Reader is used to read barcodes from the camera video frames.

Getting started with Dynamsoft Barcode Reader

Build a Barcode and QR Code Scanner with Next.js in Steps

Let’s do this in steps.

New Project

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

npx create-next-app@latest barcode-scanner

Then, we can run the following to test it:

cd barcode-scanner
npm run dev

Install Dependencies

Install Dynamsoft Barcode Reader and Dynamsoft Camera Enhancer.

npm install dynamsoft-javascript-barcode dynamsoft-camera-enhancer

Create a Barcode Scanner React Component

Next, let’s create a barcode scanner component under src/components/BarcodeScanner.tsx that can open the camera and scan barcodes from the camera video frames.

  1. Write the basic content of the component:

    import { CameraEnhancer } from "dynamsoft-camera-enhancer";
    import { PlayCallbackInfo } from "dynamsoft-camera-enhancer/dist/types/interface/playcallbackinfo";
    import { TextResult,BarcodeReader } from "dynamsoft-javascript-barcode";
    import React from "react";
    import { ReactNode } from "react";
    
    export interface ScannerProps{
      isActive?: boolean;
      children?: ReactNode;
      interval?: number;
      license?: string;
      onInitialized?: (enhancer:CameraEnhancer,reader:BarcodeReader) => void;
      onScanned?: (results:TextResult[]) => void;
      onPlayed?: (playCallbackInfo: PlayCallbackInfo) => void;
      onClosed?: () => void;
    }
    
    const BarcodeScanner = (props:ScannerProps): React.ReactElement => {
      return (
      )
    }
    
    export default BarcodeScanner;
    

    The component has several props to control the scanner and get the barcode results.

  2. Define the container for the scanner with a container for the camera using a fixed class name used by the Dynamsoft Camera Enhancer. Here, we use the relative position so that we can customize its position in the parent component.

    const container = React.useRef(null);
    return (
      <div ref={container} style={{ position:"relative", width:"100%", height:"100%" }}>
        <div className="dce-video-container"></div>
      </div>
    )
    
  3. Initialize Barcode Reader and Camera Enhancer when the component is mounted. Several events are registered.

    React.useEffect(()=>{
      const init = async () => {
        if (BarcodeReader.isWasmLoaded() === false) {
          if (props.license) {
            BarcodeReader.license = props.license;
          }else{
            BarcodeReader.license = "DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAxLTE2NDk4Mjk3OTI2MzUiLCJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSIsInNlc3Npb25QYXNzd29yZCI6IndTcGR6Vm05WDJrcEQ5YUoifQ=="; //one-day trial license
          }
          BarcodeReader.engineResourcePath = "https://cdn.jsdelivr.net/npm/dynamsoft-javascript-barcode@9.6.11/dist/";
        }
        reader.current = await BarcodeReader.createInstance();
        enhancer.current = await CameraEnhancer.createInstance();
        await enhancer.current.setUIElement(container.current!);
        enhancer.current.on("played", (playCallbackInfo: PlayCallbackInfo) => {
          if (props.onPlayed) {
            props.onPlayed(playCallbackInfo);
          }
        });
        enhancer.current.on("cameraClose", () => {
          if (props.onClosed) {
            props.onClosed();
          }
        });
        enhancer.current.setVideoFit("cover");
        if (props.onInitialized) {
          props.onInitialized(enhancer.current,reader.current);
        }
      }
      if (mounted.current === false) {
        init();
      }
      mounted.current = true;
    },[])
    
  4. Monitor the isActive props. Start the camera if it is set to true. Otherwise, stop the camera.

    const toggleCamera = () => {
      if (mounted.current === true) {
        if (props.isActive === true) {
          enhancer.current?.open(true);
        }else{
          stopScanning();
          enhancer.current?.close();
        }
      }
    }
    
    React.useEffect(()=>{
      toggleCamera();
    },[props.isActive])
    
  5. Define functions related to scanning the barcodes. We start an interval to get frames from the camera and read barcodes.

    const interval = React.useRef<any>(null);
    const decoding = React.useRef(false);
    const startScanning = () => {
      const decode = async () => {
        if (decoding.current === false && reader.current && enhancer.current) {
          decoding.current = true;
          const results = await reader.current.decode(enhancer.current.getFrame());
          if (props.onScanned) {
            props.onScanned(results);
          }
          decoding.current = false;
        }
      }
      if (props.interval) {
        interval.current = setInterval(decode,props.interval);
      }else{
        interval.current = setInterval(decode,40);
      }
    }
    
    const stopScanning = () => {
      clearInterval(interval.current);
    }
    
  6. Trigger scanning if the camera is opened.

    enhancer.current.on("played", (playCallbackInfo: PlayCallbackInfo) => {
      if (props.onPlayed) {
        props.onPlayed(playCallbackInfo);
      }
      startScanning();
    });
    
  7. Try to toggle the camera after initialization as well.

    const init = async () => {
      toggleCamera();
    }
    

Use the Barcode Scanner Component in the App

Switch to pages/index.tsx. Let’s use the scanner component in the app.

  1. Import the scanner component and bind an isActive state to it so that we can use a button to control its scanning status.

    export default function Home(props:any) {
      const [isActive,setIsActive] = React.useState(false);
      const toggleScanning = () => {
        setIsActive(!isActive);
      }
      return (
        <>
          <Head>
            <title>Next.js Barcode Reader</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 className={homeStyles.app}>
              <h2>Next.js Barcode Scanner</h2>
              <button onClick={toggleScanning}>{isActive ? "Stop Scanning" : "Start Scanning"}</button>
              <div className={homeStyles.barcodeScanner}>
                <BarcodeScanner
                  isActive={isActive}
                ></BarcodeScanner>
              </div>
            </div>
          </main>
        </>
      )
    }
    
  2. Bind the initialization event. Show an Initializing... text if the scanner has not been initialized. If the initialization has been done, display the start scanning button.

    JSX:

    {initialized ? (
      <button onClick={toggleScanning}>{isActive ? "Stop Scanning" : "Start Scanning"}</button>
    ) : (
      <div>Initializing...</div>
    )}
    <BarcodeScanner
      onInitialized={() => setInitialized(true)}
      isActive={isActive}
    ></BarcodeScanner>
    

    JavaScript:

    const [initialized,setInitialized] = React.useState(false);
    
  3. Bind the onScanned event. Stop scanning and display the barcode results if barcodes are found.

    JSX:

    <BarcodeScanner
      onInitialized={() => setInitialized(true)}
      isActive={isActive}
      onScanned={(results) => onScanned(results)}
    ></BarcodeScanner>
    

    JavaScript:

    const onScanned = (results:TextResult[]) => {
      if (results.length>0) {
        let text = "";
        for (let index = 0; index < results.length; index++) {
          const result = results[index];
          text = text + result.barcodeFormatString + ": " + result.barcodeText + "\n";
        }
        alert(text);
        setIsActive(false);
      }
    }
    

Read License from Environment Variables

We need to set the license for Dynamsoft Barcode Reader to use it. You can apply for a license here.

We can directly store the license in the code and use it:

BarcodeReader.license = "<license>";

But with Next.js’ server-side pre-rendering option, we can read the license from the system’s environment variable for better flexibility.

  1. Create a new getServerSideProps function in the index page:

    export async function getServerSideProps() {
      let license:string|undefined = process.env.DBRLicense;
      return { props: { license:license } };
    }
    

    The function tries to read the license from the environment variables and pass it as props to the page.

  2. In the page, we can pass the license props to the barcode scanner component to use it.

    <BarcodeScanner
      license={props.license}
      onInitialized={() => setInitialized(true)}
      isActive={isActive}
      onScanned={(results) => onScanned(results)}
    ></BarcodeScanner>
    

All right, we’ve now finished the barcode and QR code scanner with Next.js. You can check out the online demo to have a try.

Source Code

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