Build a Nuxt 3 Barcode and QR Code Scanner with Webcam Access
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 barcode and QR code scanner using Nuxt.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. Try the online demo to see the final result.
What you’ll build: A Nuxt.js web application that opens a webcam stream and scans barcodes and QR codes in real time using the Dynamsoft Barcode Reader SDK.
Key Takeaways
- Dynamsoft Barcode Reader can be integrated into a Nuxt.js app as a Vue component that reads 1D/2D barcodes from a live webcam feed.
- The
CaptureVisionRouterprocesses camera frames and returns decoded barcode results via an event-driven callback. - The SDK’s WebAssembly engine runs entirely client-side, so no server-side barcode processing is required.
- Wrapping the scanner component in
<ClientOnly>is necessary because Nuxt uses server-side rendering by default.
Common Developer Questions
- How do I scan QR codes from a webcam in a Nuxt.js app?
- How do I create a reusable barcode scanner Vue component for Nuxt 3?
- Why does my Dynamsoft Barcode Reader component fail during Nuxt server-side rendering?
Prerequisites
- Node.js 18 or later
- A Nuxt.js 3 project (created in Step 1 below)
- Get a 30-day free trial license for Dynamsoft Barcode Reader
Step 1: Create a New Nuxt.js Project
Create a new Nuxt.js project named barcode-scanner:
npx nuxi@latest init barcode-scanner
Step 2: Install the Barcode Reader SDK
Install the Dynamsoft Barcode Reader bundle.
npm install dynamsoft-barcode-reader-bundle
Step 3: Configure the SDK License and Resources
Create a file named dynamsoft.config.ts with the following content to initialize the license and resources.
import { CoreModule } from "dynamsoft-core";
import { LicenseManager } from "dynamsoft-license";
import "dynamsoft-barcode-reader";
let initialized = false;
export async function init(){
if (initialized) {
return;
}
// Configures the paths where the .wasm files and other necessary resources for modules are located.
CoreModule.engineResourcePaths.rootDirectory = "https://cdn.jsdelivr.net/npm/";
/** LICENSE ALERT - README
* To use the library, you need to first specify a license key using the API "initLicense()" as shown below.
*/
await LicenseManager.initLicense("LICENSE-KEY", {
executeNow: true,
});
/**
* You can visit https://www.dynamsoft.com/customer/license/trialLicense?utm_source=samples&product=dbr&package=js to get your own trial license good for 30 days.
* Note that if you downloaded this sample from Dynamsoft while logged in, the above license key may already be your own 30-day trial license.
* For more information, see https://www.dynamsoft.com/barcode-reader/docs/web/programming/javascript/user-guide/index.html?ver=10.4.2002&cVer=true#specify-the-license&utm_source=samples or contact support@dynamsoft.com.
* LICENSE ALERT - THE END
*/
// Optional. Preload "BarcodeReader" module for reading barcodes. It will save time on the initial decoding by skipping the module loading.
await CoreModule.loadWasm(["DBR"]);
initialized = true;
return;
}
Step 4: Create a Barcode Scanner Vue Component
Next, let’s create a barcode scanner component under src/components/BarcodeScanner.vue that can open the camera and scan barcodes from the camera video frames.
-
Write the basic content of the component:
<script setup lang="ts"> const cameraViewContainer: Ref<HTMLElement | null> = ref(null); </script> <template> <div ref="cameraViewContainer" id="cameraViewContainer"></div> </template> <style scoped> #cameraViewContainer { width: 100%; height: 100%; top: 0; left: 0; position: absolute; } </style>It has an element which serves as the container of the camera.
-
Run the init method defined in
dynamsoft.config.tsafter the component is mounted.onMounted(async () => { await init(); }); -
Initialize Camera Enhancer and bind it to the UI element after the component is mounted.
const cameraViewContainer: Ref<HTMLElement | null> = ref(null); let cameraEnhancer: CameraEnhancer; let cameraView:CameraView; onMounted(async () => { try { await init(); // Create a `CameraEnhancer` instance for camera control and a `CameraView` instance for UI control. cameraView = await CameraView.createInstance(); cameraEnhancer = await CameraEnhancer.createInstance(cameraView); // Get default UI and append it to DOM. cameraViewContainer.value!.append(cameraView.getUIElement()); } catch (ex: any) { let errMsg = ex.message || ex; console.error(errMsg); } }); -
Create a Capture Vision Router instance to process the camera frames to read barcodes.
let cvRouter: CaptureVisionRouter; onMounted(async () => { try { //... cvRouter = await CaptureVisionRouter.createInstance(); cvRouter.setInput(cameraEnhancer); } catch (ex: any) { let errMsg = ex.message || ex; console.error(errMsg); } }); -
Define two emit events. One is to emit the detected barcode results and the other is to emit the initialized event.
// define a 'scanned' event that the Scanner component emits when frames are scanned const emit = defineEmits<{ (e: 'scanned', results: BarcodeResultItem[]): void (e: 'initialized'): void }>();-
Add a result receiver to get the detected barcode results and emit them.
// Define a callback for results. cvRouter.addResultReceiver({ onDecodedBarcodesReceived: (result) => { emit("scanned",result.barcodeResultItems); } }); -
Emit
initializedafter the instances are created.onMounted(async () => { try { //... emit("initialized"); } catch (ex: any) { let errMsg = ex.message || ex; console.error(errMsg); } });
-
-
Expose two methods to control the scanning status of the component.
// expose start/stop to control pause/resume scanning const start = async () => await startScanning(); const stop = () => stopScanning(); defineExpose({ start, stop }); const stopScanning = () => { cameraView?.setScanLaserVisible(false); cvRouter?.stopCapturing(); cameraEnhancer.close(); } const startScanning = async () => { await cameraEnhancer.open(); cvRouter?.startCapturing("ReadSingleBarcode"); cameraView?.setScanLaserVisible(true); } -
Release resources before the component is unmounted.
// dispose cvRouter when it's no longer needed onBeforeUnmount(async () => { try { cvRouter?.dispose(); cameraEnhancer?.dispose(); } catch (_) { } });
Step 5: Integrate the Scanner Component into Your Nuxt App
Switch to app.vue. Let’s use the scanner component in the app.
-
Import the scanner component and add a button to control its scanning status. Since the SDK has to modify the DOM, we need to wrap the scanner with
ClientOnly.<template> <div id="app"> <h2>Barcode Scanner in Nuxt.js</h2> <button v-if="initialized" @click="toggleScanning"></button> <span v-if="!initialized">Initializing...</span> <div class="container" v-if="mounted"> <ClientOnly fallback-tag="span" fallback="Loading barcode scanner..."> <BarcodeScanner ref="scanner" @initialized="onInitialized" @scanned="onScanned"></BarcodeScanner> </ClientOnly> </div> </template> <script setup lang="ts"> import type { BarcodeResultItem } from 'dynamsoft-barcode-reader-bundle'; import BarcodeScanner from './components/BarcodeScanner.vue'; const scanner = ref(); const initialized = ref(false); const scanning = ref(false); const onInitialized = () => { initialized.value = true; } const toggleScanning = () => { if (scanner.value) { scanning.value = !scanning.value; if (scanning.value) { scanner.value.start(); }else{ scanner.value.stop(); } }else{ alert("Not mounted"); } } </script> <style lang="css" scoped> .container { position: relative; width: 360px; height: 360px; } </style> -
Display the scanned results.
<template> <div> <div> Results: </div> <ol> <li v-for="(barcode,index) in scannedBarcodes" :key="'barcode-'+index"></li> </ol> </div> </template> <script setup lang="ts"> const scannedBarcodes = ref<BarcodeResultItem[]>([]); const onScanned = (barcodes:BarcodeResultItem[]) => { if (barcodes.length>0) { scannedBarcodes.value = barcodes; } } </script>
All right, we’ve now finished the barcode and QR code scanner with Nuxt.js. You can check out the online demo to have a try.
Common Issues and Edge Cases
- Camera permission denied: If the user blocks camera access,
CameraEnhancer.open()will throw an error. Wrap the call in a try/catch and display a user-friendly message prompting them to allow camera permissions in their browser settings. - SSR hydration mismatch: Dynamsoft SDK accesses browser-only APIs (
navigator.mediaDevices,document). Always wrap<BarcodeScanner>in<ClientOnly>to prevent server-side rendering errors in Nuxt. - Slow initial load on first scan: The SDK loads WebAssembly modules on first use. Call
CoreModule.loadWasm(["DBR"])during initialization (as shown in Step 3) to preload the module and avoid a delay when the user starts scanning.
Frequently Asked Questions
How do I implement a QR code reader in Nuxt.js?
Install dynamsoft-barcode-reader-bundle, create a Vue component that initializes CameraEnhancer and CaptureVisionRouter, and wrap it in <ClientOnly> to avoid SSR issues. This tutorial walks through every step.
Does the Dynamsoft Barcode Reader work with Nuxt 3 server-side rendering?
The SDK itself requires browser APIs and cannot run on the server. Use Nuxt’s <ClientOnly> wrapper so the scanner component only renders on the client side.
What barcode formats can this Nuxt.js scanner read?
Dynamsoft Barcode Reader supports over 20 barcode formats including QR Code, Data Matrix, PDF417, Code 128, Code 39, EAN-13, UPC-A, and more. The ReadSingleBarcode preset used in this tutorial auto-detects the format.