Usage

Wrap your config in next.config.ts with withQRCode:

TypeScript
import type { NextConfig } from "next";
import { withQRCode } from "next-plugin-qrcode";

const nextConfig: NextConfig = {
  // your Next.js config
};

export default withQRCode(nextConfig);

Start your dev server:

Terminal
npm run dev

Your terminal shows a QR code encoding your dev server's address on the local network. Scan it with your phone to open the app. Both devices must be on the same network.

Try it live in the demo, or tune the URL with options.

Why a config wrapper and not a bundler plugin

Turbopack is written in Rust, and Next.js is explicit about what that costs: plugins have to be described by name so Rust can load them, because JavaScript functions can't be passed to Rust. That is why remark and rehype plugins are configured as strings under Turbopack:

TypeScript
const withMDX = createMDX({
  options: {
    remarkPlugins: ["remark-gfm"],
    rehypePlugins: [["rehype-katex", { strict: true }]],
  },
});

A webpack plugin is the opposite of that. It is a JavaScript class with an apply() method, handed to the bundler as a live object. There is no way to pass one to Turbopack, so a QR code printed from inside the bundler would work under next dev --webpack and silently do nothing under Turbopack.

withQRCode sidesteps the boundary instead of trying to cross it. Your next.config.ts is evaluated by Node before either bundler starts, so the plugin prints the QR code there and returns your config untouched. Nothing is registered with webpack, nothing has to be serialized for Turbopack, and the same code path runs for both.

For the full story on the Rust boundary, see Using plugins with Turbopack in the Next.js documentation.