qr-code-styling icon indicating copy to clipboard operation
qr-code-styling copied to clipboard

self is not defined error with nextjs when used in backend

Open sc0rp10n-py opened this issue 1 year ago • 10 comments

this is my code

import cloudinary from "cloudinary";
import QRCodeStyling from "qr-code-styling";

cloudinary.v2.config({
    cloud_name: process.env.CLOUDINARY_CLOUD_NAME,
    api_key: process.env.CLOUDINARY_API_KEY,
    api_secret: process.env.CLOUDINARY_API_SECRET,
});

const handler = async (req, res) => {
    if (req.method === "POST") {
        const { id } = req.body;
        try {
            const qrCode = new QRCodeStyling({
                width: 300,
                height: 300,
                type: "png",
                data: process.env.NEXT_PUBLIC_AUTH_URL+"view/" + id,
                image: "/logo.png",
                margin: 10,
                qrOptions: {
                    typeNumber: 0,
                    mode: "Byte",
                    errorCorrectionLevel: "Q",
                },
                imageOptions: {
                    hideBackgroundDots: true,
                    imageSize: 0.4,
                    margin: 0,
                    crossOrigin: "anonymous",
                },
                dotsOptions: {
                    color: "#1f2791",
                    type: "rounded",
                },
                backgroundOptions: {
                    color: "#ffffff",
                },
                cornersSquareOptions: {
                    color: "#f22d4e",
                    type: "extra-rounded",
                },
                cornersDotOptions: {
                    color: "#f22d4e",
                    type: "dot",
                },
            });
            const png = qrCode.png();
            const { secure_url } = await cloudinary.v2.uploader.upload(png, {
                folder: "qr-codes",
                public_id: id,
                overwrite: true,
                invalidate: true,
            });
            console.log(secure_url);
            res.status(200).json({ success: true, data: secure_url });
        } catch (error) {
            console.log(error);
            res.status(400).json({
                success: false,
                message: "Something went wrong!",
            });
        }
    } else {
        res.status(400).json({ success: false, message: "Invalid request" });
    }
};

export default handler;

i get this error

error - ReferenceError: self is not defined
    at Object.<anonymous> (/home/sc0rp10n/deeks/chain-trust/node_modules/qr-code-styling/lib/qr-code-styling.js:1:208)
    at Module._compile (node:internal/modules/cjs/loader:1099:14)
    at Object.Module._extensions..js (node:internal/modules/cjs/loader:1153:10)
    at Module.load (node:internal/modules/cjs/loader:975:32)
    at Function.Module._load (node:internal/modules/cjs/loader:822:12)
    at Module.require (node:internal/modules/cjs/loader:999:19)
    at require (node:internal/modules/cjs/helpers:102:18)
    at Object.qr-code-styling (/home/sc0rp10n/deeks/chain-trust/.next/server/pages/api/qr.js:32:18)
    at __webpack_require__ (/home/sc0rp10n/deeks/chain-trust/.next/server/webpack-api-runtime.js:33:42)
    at eval (webpack-internal:///(api)/./pages/api/qr.js:7:73)
    at Object.(api)/./pages/api/qr.js (/home/sc0rp10n/deeks/chain-trust/.next/server/pages/api/qr.js:42:1)
    at __webpack_require__ (/home/sc0rp10n/deeks/chain-trust/.next/server/webpack-api-runtime.js:33:42)
    at __webpack_exec__ (/home/sc0rp10n/deeks/chain-trust/.next/server/pages/api/qr.js:52:39)
    at /home/sc0rp10n/deeks/chain-trust/.next/server/pages/api/qr.js:53:28
    at Object.<anonymous> (/home/sc0rp10n/deeks/chain-trust/.next/server/pages/api/qr.js:56:3)
    at Module._compile (node:internal/modules/cjs/loader:1099:14)
    at Object.Module._extensions..js (node:internal/modules/cjs/loader:1153:10)
    at Module.load (node:internal/modules/cjs/loader:975:32)
    at Function.Module._load (node:internal/modules/cjs/loader:822:12)
    at Module.require (node:internal/modules/cjs/loader:999:19)
    at require (node:internal/modules/cjs/helpers:102:18)
    at DevServer.runApi (/home/sc0rp10n/deeks/chain-trust/node_modules/next/dist/server/next-server.js:507:34)
    at DevServer.handleApiRequest (/home/sc0rp10n/deeks/chain-trust/node_modules/next/dist/server/next-server.js:878:21)
    at Object.fn (/home/sc0rp10n/deeks/chain-trust/node_modules/next/dist/server/next-server.js:828:46)
    at async Router.execute (/home/sc0rp10n/deeks/chain-trust/node_modules/next/dist/server/router.js:243:32)
    at async DevServer.runImpl (/home/sc0rp10n/deeks/chain-trust/node_modules/next/dist/server/base-server.js:432:29)
    at async DevServer.run (/home/sc0rp10n/deeks/chain-trust/node_modules/next/dist/server/dev/next-dev-server.js:831:20)
    at async DevServer.handleRequestImpl (/home/sc0rp10n/deeks/chain-trust/node_modules/next/dist/server/base-server.js:375:20)
    at async /home/sc0rp10n/deeks/chain-trust/node_modules/next/dist/server/base-server.js:157:99
FetchError: invalid json response body at http://localhost:3000/api/qr reason: Unexpected token < in JSON at position 0    at /home/sc0rp10n/deeks/chain-trust/node_modules/next/dist/compiled/node-fetch/index.js:1:51220
    at runMicrotasks (<anonymous>)
    at processTicksAndRejections (node:internal/process/task_queues:96:5)
    at async handler (webpack-internal:///(api)/./pages/api/create-form.js:22:27)
    at async Object.apiResolver (/home/sc0rp10n/deeks/chain-trust/node_modules/next/dist/server/api-utils/node.js:372:9)
    at async DevServer.runApi (/home/sc0rp10n/deeks/chain-trust/node_modules/next/dist/server/next-server.js:514:9)
    at async Object.fn (/home/sc0rp10n/deeks/chain-trust/node_modules/next/dist/server/next-server.js:828:35)
    at async Router.execute (/home/sc0rp10n/deeks/chain-trust/node_modules/next/dist/server/router.js:243:32)
    at async DevServer.runImpl (/home/sc0rp10n/deeks/chain-trust/node_modules/next/dist/server/base-server.js:432:29)
    at async DevServer.run (/home/sc0rp10n/deeks/chain-trust/node_modules/next/dist/server/dev/next-dev-server.js:831:20)
    at async DevServer.handleRequestImpl (/home/sc0rp10n/deeks/chain-trust/node_modules/next/dist/server/base-server.js:375:20)
    at async /home/sc0rp10n/deeks/chain-trust/node_modules/next/dist/server/base-server.js:157:99 {
  type: 'invalid-json'
}

sc0rp10n-py avatar Apr 21 '23 01:04 sc0rp10n-py

I haven't taken a look at the source code so this might be wrong, but this probably happens because qr-code-styling requires the Window object (aka, self) to be defined, which you don't have on the backend. So you can't really use this library in a NodeJS environment.

This is also why issues such as #89 happen on the frontend as well when using NextJS, because the Window object is not defined during SSR.

josipslavic avatar Apr 21 '23 13:04 josipslavic

@josipslavic so is there any workaround for node js backend type environment?

sc0rp10n-py avatar Apr 24 '23 04:04 sc0rp10n-py

also interested in this!

ketz avatar Apr 25 '23 09:04 ketz

Maybe JSDom could help you guys here: https://github.com/jsdom/jsdom

Disclaimer, I haven't tried it with this project but have used it with success before when needing to use other DOM-releated tools inside of Node.js.

You can probably use it to polyfill window, etc. as you need.


If this project requires a true DOM canvas, however, your only options may be to:

  • Figure out how to run it on the client, anyway
  • Run it inside of Puppeteer
  • Find another way of emulating the canvas in Node

By far the easiest approach would be to run it on the client.

jzombie avatar Apr 25 '23 12:04 jzombie

Getting the same error on Nuxt after upgrading it to version 3.4.2. Before updates it worked fine

RomanSkrypnik avatar Apr 26 '23 16:04 RomanSkrypnik

I have done some playing around and came up with a band-aid solution for this problem (largely due to @jzombie and his jsdom proposition).

Here are a few things to note about this:

  • I haven't found a way to polyfill HTML's new Image(), and because of that, you won't be able to use the image property when creating your QR code (because it uses new Image() under the hood). If somebody could manage to find a way to polyfill that, we could drastically improve the performance of this code because we wouldn't need to use nodeHtmlToImage package (which when I was testing caused huge performance issues). We would also then be able to use qrCode.getRawData('png') (which we can't currently use because it also uses new Image() under the hood)
  • There might be a better and more up-to-date way to upload a buffer to cloudinary, I've just used the first answer that came up when Googling since it's not really the focus of this problem.
import cloudinary from 'cloudinary';
import streamifier from 'streamifier';
import QRCodeStyling from 'qr-code-styling';
import { JSDOM } from 'jsdom';
import nodeHtmlToImage from 'node-html-to-image';

cloudinary.v2.config({
  cloud_name: process.env.CLOUDINARY_CLOUD_NAME,
  api_key: process.env.CLOUDINARY_API_KEY,
  api_secret: process.env.CLOUDINARY_API_SECRET,
});

const dom = new JSDOM('<!DOCTYPE html><html><body></body></html>');
global.document = dom.window.document;
global.self = document.defaultView;
global.XMLSerializer = dom.window.XMLSerializer;

const handler = async (req, res) => {
  if (req.method === 'POST') {
    // Use dynamic imports so that qr-code-styling has access to our global polyfills
    import('qr-code-styling').then(async ({ default: QRCodeStyling }) => {
      const { id } = req.body;
      try {
        const qrCode = new QRCodeStyling({
          width: 300,
          height: 300,
          type: 'svg', // We have to create an svg that we could append to an HTML element
          data: process.env.NEXT_PUBLIC_AUTH_URL + 'view/' + id,
          // image: '/logo.png', Cannot set image due to Image being undefined (qr-code-styling uses new Image() under the hood)
          margin: 10,
          qrOptions: {
            typeNumber: 0,
            mode: 'Byte',
            errorCorrectionLevel: 'Q',
          },
          imageOptions: {
            hideBackgroundDots: true,
            imageSize: 0.4,
            margin: 0,
            crossOrigin: 'anonymous',
          },
          dotsOptions: {
            color: '#1f2791',
            type: 'rounded',
          },
          backgroundOptions: {
            color: '#ffffff',
          },
          cornersSquareOptions: {
            color: '#f22d4e',
            type: 'extra-rounded',
          },
          cornersDotOptions: {
            color: '#f22d4e',
            type: 'dot',
          },
        });

        // Append the QR code to some HTML element
        const htmlEl = dom.window.document.createElement('div');
        qrCode.append(htmlEl);

        // Convert that HTML to a Buffer
        const buffer = await nodeHtmlToImage({
          html: htmlEl.innerHTML,
        });

        // Upload buffer to cloudinary
        let cld_upload_stream = cloudinary.v2.uploader.upload_stream(
          { folder: 'some_folder' },
          function (error, result) {
            if (!error) {
              return res
                .status(200)
                .json({ success: true, url: result.secure_url });
            }
          }
        );
        streamifier.createReadStream(buffer).pipe(cld_upload_stream);
      } catch (error) {
        console.log(error);
        res.status(400).json({
          success: false,
          message: 'Something went wrong!',
        });
      }
    });
  } else {
    res.status(400).json({ success: false, message: 'Invalid request' });
  }
};

export default handler;

josipslavic avatar Apr 27 '23 10:04 josipslavic

Getting the same error on Nuxt after upgrading it to version 3.4.2. Before updates it worked fine

Solved using dynamic import on client-side

onMounted(() => {
  import('qr-code-styling').then(({ default: QRCodeStyling }) => {
    qrCode = new QRCodeStyling(options);
    qrCode.append(qr.value);
  });
});

RomanSkrypnik avatar Apr 27 '23 10:04 RomanSkrypnik

Well for Next.js at least, there's a way to bypass this using Dynamic imports :

const QrCode = dynamic(() => import('@/path/to/qrCode'), {
  ssr: false,
});

Zharkan avatar May 01 '23 01:05 Zharkan

@josipslavic I used this fork https://github.com/Loskir/styled-qr-code and it works fine, this is my code, i am using NextJS 13.4.4

My repo: https://github.com/cresenciof/nextjs-qr-code-generator

// https://nextjs.org/docs/app/building-your-application/routing/router-handlers

import { NextResponse } from "next/server";

import { QRCodeCanvas, Options } from "@loskir/styled-qr-code-node";

const options: Partial<Options> = {
  width: 400,
  height: 400,
  data: "https://www.facebook.com/",
  image:
    "https://upload.wikimedia.org/wikipedia/commons/thumb/0/05/Facebook_Logo_%282019%29.png/1200px-Facebook_Logo_%282019%29.png",
  dotsOptions: {
    color: "#4267b2",
    type: "rounded",
  },
  backgroundOptions: {
    color: "#e9ebee",
  },
  imageOptions: {
    crossOrigin: "anonymous",
    margin: 20,
  },
};

export async function GET() {
  try {
    const qrCode = new QRCodeCanvas(options);

    const file = await qrCode.toDataUrl("png");

    return NextResponse.json({
      pnfFile: file,
    });
  } catch (error) {
    return NextResponse.json(
      { error: "Failed to generate QR code" },
      { status: 500 }
    );
  }
}

This is the component where I am rendering the API response:

"use client";
import { useState, useEffect } from "react";

function ImageViewer() {
  const [imageSrc, setImageSrc] = useState<string | null>(null);

  useEffect(() => {
    fetch("/api/generate")
      .then((response) => response.json())
      .then((data) => {
        if (data.pngFile) {
          // the pngFile contains the png image as a base64 string
          setImageSrc(data.pngFile);
        }
      })
      .catch((error) => {
        console.error("Error fetching image:", error);
      });
  }, []);

  if (!imageSrc) {
    return <div>Loading...</div>;
  }

  return <>{imageSrc && <img src={imageSrc} alt="SVG Image" />}</>;
}

export default ImageViewer;

image

cresenciof avatar May 28 '23 03:05 cresenciof

@josipslavic I used this fork https://github.com/Loskir/styled-qr-code and it works fine, this is my code, i am using NextJS 13.4.4

My repo: https://github.com/cresenciof/nextjs-qr-code-generator

// https://nextjs.org/docs/app/building-your-application/routing/router-handlers

import { NextResponse } from "next/server";

import { QRCodeCanvas, Options } from "@loskir/styled-qr-code-node";

const options: Partial<Options> = {
  width: 400,
  height: 400,
  data: "https://www.facebook.com/",
  image:
    "https://upload.wikimedia.org/wikipedia/commons/thumb/0/05/Facebook_Logo_%282019%29.png/1200px-Facebook_Logo_%282019%29.png",
  dotsOptions: {
    color: "#4267b2",
    type: "rounded",
  },
  backgroundOptions: {
    color: "#e9ebee",
  },
  imageOptions: {
    crossOrigin: "anonymous",
    margin: 20,
  },
};

export async function GET() {
  try {
    const qrCode = new QRCodeCanvas(options);

    const file = await qrCode.toDataUrl("png");

    return NextResponse.json({
      pnfFile: file,
    });
  } catch (error) {
    return NextResponse.json(
      { error: "Failed to generate QR code" },
      { status: 500 }
    );
  }
}

This is the component where I am rendering the API response:

"use client";
import { useState, useEffect } from "react";

function ImageViewer() {
  const [imageSrc, setImageSrc] = useState<string | null>(null);

  useEffect(() => {
    fetch("/api/generate")
      .then((response) => response.json())
      .then((data) => {
        if (data.pngFile) {
          // the pngFile contains the png image as a base64 string
          setImageSrc(data.pngFile);
        }
      })
      .catch((error) => {
        console.error("Error fetching image:", error);
      });
  }, []);

  if (!imageSrc) {
    return <div>Loading...</div>;
  }

  return <>{imageSrc && <img src={imageSrc} alt="SVG Image" />}</>;
}

export default ImageViewer;

image

Thanks

natainditama avatar Jun 06 '23 12:06 natainditama