How to Integrate a Client-Side Document Scanner in a Nuxt.js App

Nuxt is a free and open-source framework with an intuitive and extendable way to create type-safe, performant and production-grade full-stack web applications and websites with Vue.js.

In this article, we are going to build a document scanner using Nuxt.js and Dynamic Web TWAIN. It can scan documents via scanners using protocols like TWAIN, WIA, ESCL, SANE, and ICA.

What you’ll build: A full-stack Nuxt.js web application that lets users acquire scanned pages from a physical document scanner (TWAIN/WIA/ESCL/SANE/ICA) and save them as a PDF — with the Dynamsoft SDK safely loaded client-side to avoid SSR errors.

Key Takeaways

  • Dynamic Web TWAIN integrates with Nuxt.js by wrapping the scanner component in <ClientOnly> to prevent server-side rendering errors.
  • The SDK supports TWAIN, WIA, ESCL, SANE, and ICA scanner protocols through a unified JavaScript API.
  • Scanned pages can be exported as a multi-page PDF directly from the browser with a single API call.
  • This pattern applies to any Nuxt.js app that needs to embed a hardware-dependent, DOM-dependent client-side library.

Common Developer Questions

  • How do I integrate a client-side document scanner SDK in a Nuxt.js app without SSR errors?
  • How do I use Dynamic Web TWAIN with Vue 3 and the Composition API?
  • How do I save scanned documents as a PDF in a Nuxt.js app?

Demo screenshot:

screenshot

Prerequisites & Nuxt SSR Setup Note

Important for Nuxt.js / SSR: Dynamic Web TWAIN accesses hardware and modifies the DOM directly, so it must run entirely on the client. Wrap the scanner component in <ClientOnly> (shown in the steps below) to prevent server-side rendering errors. Do not import the SDK in server-only code paths.

Get your trial key.

Step 1: Create a New Nuxt.js Project

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

npx nuxi@latest init document-scanner

Step 2: Install Dependencies

  1. Install Dynamic Web TWAIN.

    npm install dwt
    
  2. Copy the resources of Dynamic Web TWAIN to the public folder.

    1. Install ncp.

      npm install ncp --save-dev
      
    2. Modify package.json to add the following scripts:

      "build": "ncp node_modules/dwt/dist public/assets/dwt-resources && nuxt build",
      "dev": "ncp node_modules/dwt/dist public/assets/dwt-resources && nuxt dev",
      

      Remember to create the assets/dwt-resources folder beforehand.

Step 3: Build the Document Scanner Vue Component

Next, let’s create a document scanner component under components/DocumentScanner.vue.

In the template, add a div as the container of the viewer of Dynamic Web TWAIN.

<template>
  <div ref='viewer' id='dwtcontrolContainer'></div>
</template>

When the component is mounted, load Dynamic Web TWAIN and register the OnWebTwainReady event. When Web TWAIN is ready, a document viewer will appear in the container. The Web TWAIN object for further actions is emitted using the onWebTWAINReady event.

<script setup lang='ts'>
import { onMounted, ref, watch } from 'vue';
import Dynamsoft from 'dwt';
import { WebTwain } from 'dwt/dist/types/WebTwain';

const emit = defineEmits(['onWebTWAINReady']);
const viewer = ref(null);
const ContainerId = 'dwtcontrolContainer';
let DWObject:WebTwain|undefined;

const initDWT = () => {
  Dynamsoft.DWT.RegisterEvent('OnWebTwainReady', () => {
    DWObject = Dynamsoft.DWT.GetWebTwain(ContainerId);
    emit('onWebTWAINReady',DWObject);
  });

  Dynamsoft.DWT.ResourcesPath = 'assets/dwt-resources';
  Dynamsoft.DWT.Containers = [{
      WebTwainId: 'dwtObject',
      ContainerId: ContainerId
  }];
  Dynamsoft.DWT.Load();
}

onMounted(async () => {
  initDWT();
});
</script>

In addition, we can make the viewer occupy the entire container with the following steps:

  1. Set the style for the container.

    <style lang="css" scoped>
    #dwtcontrolContainer {
      width: 100%;
      height: 100%;
    }
    </style>
    
  2. Modify the viewer’s size.

    DWObject.Viewer.width = "100%";
    DWObject.Viewer.height = "100%";
    

Step 4: Use the Scanner Component in Your Nuxt.js App

Switch to app.vue. Let’s use the scanner component in the app.

  1. Import the scanner component. Since the SDK has to modify the DOM, we need to wrap the scanner with ClientOnly.

    <template>
      <div>
        <h2>Document Scanner</h2>
        <div id="viewer">
          <ClientOnly>
            <DocumentScanner @onWebTWAINReady="onWebTWAINReady"></DocumentScanner>
          </ClientOnly>
        </div>
      </div>
    </template>
    <script setup lang="ts">
    import { WebTwain } from 'dwt/dist/types/WebTwain';
    let DWTObject:WebTwain|undefined;
    const onWebTWAINReady = (dwt:WebTwain) => {
      DWTObject = dwt;
    };
    </script>
    <style lang="css" scoped>
    #viewer {
      width: 320px;
      height: 320px;
    }
    </style>
    
  2. Add a button to trigger scanning.

    <template>
      <button @click="scan">Scan</button>
    </template>
    <script setup lang="ts">
    const scan = ()=> {
      if (DWTObject) {
        DWTObject.SelectSourceAsync()
          .then(function () {
            return DWTObject!.AcquireImageAsync({
              IfCloseSourceAfterAcquire: true,
            });
          })
          .catch(function (exp) {
            alert(exp.message);
          });
      }
    }
    </script>
    
  3. Add a button to save the scanned document pages as a PDF file.

    <template>
      <button @click="save">Save</button>
    </template>
    <script setup lang="ts">
    const save = ()=> {
      if (DWTObject) {
        DWTObject.SaveAllAsPDF("Scanned.pdf",
        function(){
          console.log("success")
        },
        function(errorCode:number,errorString:string){
          console.log(errorString)
        });
      }
    }
    </script>
    

All right, we’ve now finished the document scanner with Nuxt.js. You can check out the online demo to have a try.

Common Issues & Edge Cases

  • SDK fails to load in SSR mode: If you see errors like document is not defined or window is not defined at build time, you have not wrapped the component in <ClientOnly>. All Dynamic Web TWAIN initialization must happen inside onMounted or within a <ClientOnly> boundary.
  • Resources path 404: The ResourcesPath must point to the public folder where you copied the DWT dist files (e.g., assets/dwt-resources). If the path is wrong, the viewer will render blank. Verify the ncp copy step ran before nuxt dev.
  • Scanner does not appear in source list: On Windows, TWAIN and WIA drivers must be installed for the physical scanner. On macOS, ICA is used automatically. The SelectSourceAsync() method lists all available drivers — if the list is empty, the driver is missing or the Dynamsoft Service is not running.

Source Code

The complete project is available on GitHub: https://github.com/tony-xlh/NuxtJS-Document-Scanner