npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

nuxt-cloudflared-tunnel

v0.1.1

Published

Nuxt module that opens a Cloudflare Quick Tunnel to the dev server on startup

Readme

nuxt-cloudflared-tunnel

npm version npm downloads License Nuxt codecov

Nuxt module that starts a Cloudflare Quick Tunnel to the dev server on startup, with the URL available at runtime.

Features

  • Starts a Cloudflare Quick Tunnel automatically on nuxt dev, no Cloudflare account or cloudflared login required.
  • Tunnel URL exposed at runtime via useRuntimeConfig() and a $tunnelUrl / $isTunnel plugin.
  • Automatically allows the tunnel host through Vite's dev server host check.
  • Tunnel additional local services (e.g. Storybook, an API) over their own Quick Tunnels via the storybook shorthand or a generic tunnels array.
  • No-op outside nuxt dev. Nothing added to production builds.
  • Configurable port, and can be disabled entirely (e.g. in CI).

Requirements

This module requires:

  • Nuxt ^4.0.0
  • A network path to Cloudflare's edge (Quick Tunnels are created over the open internet, so they won't work fully offline or behind an egress-restricted proxy)

Quick setup

Install the module to your Nuxt application with one command:

npx nuxt module add nuxt-cloudflared-tunnel

Or install manually:

# pnpm
pnpm add -D nuxt-cloudflared-tunnel

# yarn
yarn add -D nuxt-cloudflared-tunnel

# npm
npm install -D nuxt-cloudflared-tunnel

Then add it to the modules section of nuxt.config.ts:

export default defineNuxtConfig({
  modules: ['nuxt-cloudflared-tunnel'],
})

That's it. Run nuxt dev and the tunnel starts automatically once the dev server is listening:

🚇 Starting Cloudflare tunnel for Nuxt (:3000)...
🌐 Nuxt tunnel ready at: https://<random-words>.trycloudflare.com
🔓 Allowed tunnel host: <random-words>.trycloudflare.com

The tunnel URL is exposed to the app at runtime via useRuntimeConfig().public.cloudflaredTunnelUrl, and $tunnelUrl / $isTunnel from the runtime plugin (src/runtime/plugin.ts).

Introduction

Nuxt has decent support for tunneling via nuxi dev --tunnel (also backed by Cloudflare Quick Tunnels), but using it as a one-off CLI flag has the same limitations as running cloudflared in a second terminal by hand:

  • The tunnel isn't tied to the Nuxt config or committed to the repo, so every contributor has to know to pass the flag (or run cloudflared manually) themselves.
  • The URL isn't exposed anywhere the app can read it. Building callback URLs (OAuth redirects, Stripe/GitHub webhooks) for dynamic Quick Tunnel URLs means manually copying the printed URL around.
  • There's no way to disable it per-environment (e.g. CI) via config, only by remembering to drop the flag.

This module makes the tunnel a first-class part of the Nuxt config: modules: ['nuxt-cloudflared-tunnel'], committed once, and the URL is available at runtime via useRuntimeConfig() and $tunnelUrl/$isTunnel, for exactly the callback-URL use case above.

Configuration

Configure the module at the cloudflaredTunnel key in nuxt.config.ts:

export default defineNuxtConfig({
  cloudflaredTunnel: {
    enabled: true, // set false to disable the tunnel entirely
    port: undefined, // override the port; defaults to the dev server's detected port (falls back to 3000)
    log: true, // set false to silence the module's console output
    storybook: false, // set true to also tunnel a Storybook dev server on :6006
    tunnels: [], // arbitrary extra tunnel targets
  },
})

| Option | Type | Default | Description | | ----------- | ---------------- | ----------- | ----------------------------------------------------------------------------------------------- | | enabled | boolean | true | Disable to skip starting a tunnel (e.g. in CI or restricted networks). | | port | number | undefined | Force a specific local port instead of auto-detecting the dev server's. | | log | boolean | true | Log the tunnel URL and allowed host to the console. | | storybook | boolean | false | Shorthand to tunnel a Storybook dev server on port 6006 (5s startup delay). | | tunnels | TunnelTarget[] | [] | Extra services to tunnel. Each { port, label, delay? } opens a separate Quick Tunnel. |

Tunneling extra services

The storybook option is a convenience shorthand for the most common case. For anything else (API servers, Storybook on a non-default port, etc.), use the generic tunnels array:

export default defineNuxtConfig({
  cloudflaredTunnel: {
    tunnels: [
      { port: 6006, label: 'Storybook', delay: 5_000 },
      { port: 8080, label: 'API' },
    ],
  },
})

Each target opens a separate Quick Tunnel with its own *.trycloudflare.com URL. The delay field (in ms) defers the tunnel start - useful for secondary services that take longer to boot.

Note on tunneling Storybook

In Nuxt 3/4, Storybook runs as a completely separate process (via @storybook-vue/nuxt), not inside Nuxt. The module simply opens a tunnel to whatever is listening on the configured port - it doesn't manage Storybook's lifecycle.

Cloudflare Quick Tunnels use random *.trycloudflare.com hostnames. Vite (which powers Storybook's dev server) blocks requests from unrecognized hosts by default, so you must configure Storybook's Vite to allow all hosts:

// .storybook/main.ts
viteFinal: (config) => {
  config.server ??= {}
  config.server.allowedHosts = true
  return config
},

The module handles the Nuxt side automatically (vite.server.allowedHosts = true), but Storybook's Vite config is separate.

The playground ships a working Storybook setup (config under .storybook/, sample components and stories in playground/components/) wired up with cloudflaredTunnel: { storybook: true } - run pnpm dev:all to see both services tunneled.

How it works

On the Nuxt listen hook (fired once the dev server is accepting connections), the module:

  1. Resolves the port to tunnel: options.port if set, otherwise the dev server's listening port, falling back to 3000 if the address can't be read (e.g. a Unix socket).
  2. Calls startTunnel() from untun, accepting Cloudflare's terms non-interactively.
  3. Awaits tunnel.getURL() for the public https://*.trycloudflare.com URL.
  4. Sets vite.server.allowedHosts = true so Vite's dev server accepts requests with the tunnel's Host header (Vite blocks unrecognized hosts by default).
  5. Exposes the URL via runtimeConfig.public.cloudflaredTunnelUrl.

For each extra tunnel target (including storybook: true), steps 2-3 repeat with the target's port. Targets with a delay are started via setTimeout so the primary tunnel isn't blocked.

If untun fails to start a tunnel (no tunnel returned, or the call rejects, e.g. no network access to Cloudflare's edge), the error is logged with console.error and dev server startup continues unaffected.

When the dev server shuts down, all tracked tunnels are closed via tunnel.close() on the Nuxt close hook. SIGTERM and SIGHUP handlers are also registered (in addition to untun's built-in SIGINT handling) so that non-interactive teardowns (process supervisors, terminal close) don't orphan the cloudflared child process.

FAQ

Q: Does this work in production?

A: No, by design. The module only hooks the listen event, which only fires in nuxt dev. There is no code path that starts a tunnel in a built/production app.

Q: Why does the tunnel URL change every time I restart the dev server?

A: It's a Cloudflare Quick Tunnel, which is ephemeral by design: free, anonymous, and reassigned on every connection. See Roadmap for plans around stable, named tunnels.

Q: Can I use this with nuxi dev --tunnel at the same time?

A: No, both would try to tunnel the same port. Use one or the other; this module exists so the tunnel doesn't depend on remembering a CLI flag.

Roadmap

Today this module only wraps Cloudflare Quick Tunnels: ephemeral, free, no Cloudflare account config required, but the URL changes every time the dev server restarts. That's fine for ad-hoc sharing (phone testing, a one-off webhook test) but not for anything needing a stable URL across restarts (OAuth app settings, third-party webhook configs that don't support easy URL updates).

Possible future scope, not yet implemented:

  • Named Tunnel support: a fixed hostname across restarts, via Cloudflare's authenticated tunnels (requires cloudflared login plus tunnel/DNS setup the module would need to manage or document).
  • Custom hostname config (cloudflaredTunnel: { hostname: 'dev.example.com' }) on top of named tunnel support.
  • DevTools panel integration, QR code output for mobile testing.
  • Tunnel health monitoring / automatic reconnection.

None of this is built. The module is intentionally a small, focused wrapper around untun's Quick Tunnel support today.

Testing

Unit tests live in test/module.test.ts and run with pnpm test. @nuxt/kit and untun are mocked so the tests exercise the module's setup() logic directly: port resolution, the listen hook, runtime config/Vite mutation, storybook shorthand, tunnels array handling, and tunnel-start failure handling, without booting a real Nuxt instance or network tunnel.

Contribution

# Install dependencies
pnpm install

# Generate type stubs
pnpm dev:prepare

# Develop with the playground
pnpm dev

# Build the playground
pnpm dev:build

# Run Storybook (on :6006) - tunneled via the module's `storybook` option
pnpm storybook

# Build Storybook as a static site
pnpm build-storybook

# Run ESLint
pnpm lint

# Run Vitest
pnpm test
pnpm test:watch

Bug reports and feature requests are welcome via GitHub Issues.

License

MIT

Maintainers

Stuart Clark (@Decipher)