Skip to content

Add Cloudflare Tunnel as alternative to ngrok for local dev tunneling#142

Merged
jfrench9 merged 3 commits into
mainfrom
feature/cloudflared-tunnel-option
May 19, 2026
Merged

Add Cloudflare Tunnel as alternative to ngrok for local dev tunneling#142
jfrench9 merged 3 commits into
mainfrom
feature/cloudflared-tunnel-option

Conversation

@jfrench9
Copy link
Copy Markdown
Member

@jfrench9 jfrench9 commented May 19, 2026

Summary

Adds Cloudflare Tunnel (cloudflared) as a sibling tunneling option to ngrok, and unifies both behind a single PUBLIC_TUNNEL_DOMAIN env var. Useful when an OAuth provider like QuickBooks needs a public HTTPS redirect URI but the rest of the app runs against localhost.

Either tunnel works for the OAuth handshake — pick based on what you already have set up:

Tunnel Setup Notes
ngrok ngrok account + reserved static domain on *.ngrok-free.dev (free tier) Lowest one-time setup if you don't own a domain. Free tier shows a browser interstitial on first visit per session.
cloudflared Cloudflare-managed domain + cloudflared tunnel login + cloudflared tunnel create + DNS route (one-time) More steps to set up, but no interstitial and you get your own branded hostname.

cloudflared is not a zero-setup path — it requires cloudflared tunnel login (which needs a Cloudflare account) plus a Cloudflare-managed DNS zone. The genuinely zero-config *.trycloudflare.com "quick tunnel" mode isn't used here because it produces a random URL each run, which is incompatible with Intuit's pre-registered redirect URI requirement.

Changes

New

  • bin/tunnel-cloudflared.sh — Runs cloudflared tunnel run $CLOUDFLARED_TUNNEL_NAME against the dev server. Validates cloudflared is installed, that CLOUDFLARED_TUNNEL_NAME and PUBLIC_TUNNEL_DOMAIN are set, and warns if the target port has no listener.
  • tunnel:cloudflared npm script + VS Code task mirroring the existing ngrok wiring.

Modified

  • next.config.js — Reads PUBLIC_TUNNEL_DOMAIN (tunnel-agnostic) instead of NGROK_DOMAIN. Mutates process.env.NEXT_PUBLIC_ROBOSYSTEMS_API_URL directly so the override applies even when .env already defines that var (the env: {} field in next.config.js doesn't override .env values that are loaded first).
  • bin/tunnel-ngrok.sh — Reads PUBLIC_TUNNEL_DOMAIN instead of NGROK_DOMAIN.
  • .env.example — Restructured into "Option A — ngrok" / "Option B — cloudflared" sections under a single PUBLIC_TUNNEL_DOMAIN placeholder.

Breaking Changes

NGROK_DOMAIN is renamed to PUBLIC_TUNNEL_DOMAIN. Anyone with an existing .env using NGROK_DOMAIN will need a one-line rename. No runtime/API breakage.

Testing Notes

  • End-to-end verified locally with cloudflared running against https://qb.robofinsystems.comlocalhost:3001localhost:8000. Browser session, login, and /v1/* API proxying all confirmed working through the tunnel in Chrome and Firefox.
  • ngrok path still works unchanged — only the env var name shifted.
  • next.config.js is inert when neither var is set (no rewrites, no env override, no allowedDevOrigins entry).

Known Limitations

  • HMR over the tunnel — Next.js dev server rejects WebSocket upgrades from the cloudflared origin with a "malformed Unauthorized" error visible in the cloudflared logs. HMR works fine when loading the app from localhost:3001 directly; through the tunnel you'll need to manually reload. This is acceptable because the tunnel is only needed during the OAuth handshake, not for day-to-day dev.

🤖 Generated with Claude Code

Branch Info:

  • Source: feature/cloudflared-tunnel-option
  • Target: main
  • Type: feature

Co-Authored-By: Claude noreply@anthropic.com

@jfrench9
Copy link
Copy Markdown
Member Author

@claude please review this PR

@claude
Copy link
Copy Markdown

claude Bot commented May 19, 2026

Claude finished @jfrench9's task in 2m 44s —— View job


PR Review: Add Cloudflare Tunnel as alternative to ngrok

  • Gather context and understand request
  • Read changed files
  • Review bin/tunnel-cloudflared.sh (new script)
  • Review bin/tunnel-ngrok.sh (refactored)
  • Review next.config.js changes
  • Review .env.example changes
  • Review package.json and .vscode/tasks.json
  • Post comprehensive review feedback

Overall: Solid local-dev infrastructure improvement with good shell scripting practices. A few issues to address before merging, ranging from a likely CLI syntax bug to a misleading PR description.


Issues

1. bin/tunnel-cloudflared.sh — Named tunnel --url syntax is likely wrong (High)

Line 66:

exec cloudflared tunnel --url "http://localhost:${PORT}" run "${CLOUDFLARED_TUNNEL_NAME}"

The --url flag on cloudflared tunnel is the quick-tunnel trigger — it starts a temporary tunnel with a random *.trycloudflare.com URL and doesn't take a name. Appending run <name> after a --url flag is not standard documented behavior and is likely parsed incorrectly or ignored.

For a named tunnel, the correct command is:

cloudflared tunnel run "${CLOUDFLARED_TUNNEL_NAME}"

The local service URL (port) is configured in the tunnel's ingress YAML config (~/.cloudflared/<tunnel-id>.yml), not via a CLI flag at runtime. If you want to override the port at invocation time, the config file approach is the right path.

For a quick tunnel (no account, random URL), it's simply:

cloudflared tunnel --url "http://localhost:${PORT}"

These two modes are mutually exclusive. The current script attempts to combine them, which won't behave as intended. Fix this →

2. next.config.js — Redundant API URL assignment (Medium)

Lines 23–25 and 42–44 both set NEXT_PUBLIC_ROBOSYSTEMS_API_URL:

// Line 23-25 (new)
if (tunnelDomain) {
  process.env.NEXT_PUBLIC_ROBOSYSTEMS_API_URL = `https://${tunnelDomain}`
}

// Line 42-44 (existing, now redundant)
env: tunnelDomain
  ? { NEXT_PUBLIC_ROBOSYSTEMS_API_URL: `https://${tunnelDomain}` }
  : {},

The comment on lines 19–21 correctly explains why the process.env mutation was needed (.env wins over the env field). But that means the env: tunnelDomain ? { ... } : {} block is now dead code — the mutation already handles it, and the env field no longer takes effect for this var. The env field can be removed to avoid confusion. Fix this →

3. PR description doesn't match the implementation (Medium)

The summary says:

"Cloudflare Tunnel (free, no account required for quick tunnels)"

But the implementation requires:

  1. cloudflared tunnel login (account required)
  2. A Cloudflare-managed DNS zone (domain ownership required)
  3. cloudflared tunnel create + cloudflared tunnel route dns (one-time setup)

This is actually more setup than ngrok, just without a paid plan. The genuinely zero-config path — cloudflared tunnel --url http://localhost:3001, which generates a *.trycloudflare.com URL with no login — isn't offered at all. Consider either:

  • Adding a second quick-tunnel script/mode that runs the zero-config path, or
  • Correcting the PR description and .env.example to accurately describe the named-tunnel requirement

Minor / Nits

bin/tunnel-cloudflared.sh — Scheme strip missing (Low)

tunnel-ngrok.sh defensively strips https:///http:// from PUBLIC_TUNNEL_DOMAIN (lines 48–49). The cloudflared script doesn't, but next.config.js does strip it (line 14–17), so no breakage — just a consistency gap between the two scripts.

bin/tunnel-ngrok.sh — Error message specificity (Low)

Line 41: "Set it in $REPO_ROOT/.env to your reserved ngrok static domain." — now that PUBLIC_TUNNEL_DOMAIN is shared between both tunnel providers, a developer running tunnel:ngrok who has their cloudflare domain set would never see this error, which is fine. But a developer who hasn't set any domain yet and is trying ngrok gets a ngrok-specific hint, which is still correct. No action needed — just noting the shared-variable tradeoff is acceptable here.


What's Working Well

  • Shell scripting quality: set -euo pipefail, exec handoff, non-fatal port listener check, and set -a / set +a for .env sourcing are all solid patterns — consistent with the existing ngrok script.
  • Provider-agnostic abstraction: Unifying on PUBLIC_TUNNEL_DOMAIN in next.config.js is clean — adding a third tunnel provider later only touches the shell script, not the Next config.
  • Documentation: The .env.example expansion and inline script comments are clear and thorough.
  • VS Code integration: The new task correctly reuses the existing portRL input (defined in the inputs section at line 167).
  • Breaking change is acknowledged: The NGROK_DOMAINPUBLIC_TUNNEL_DOMAIN rename is documented in the PR description.

Summary: Fix the cloudflared tunnel CLI syntax (#1) before merging — it's the only change that would cause the script to silently misbehave. The redundant env field (#2) is low-risk cleanup. The description accuracy (#3) is worth a quick editorial pass.
|

@jfrench9 jfrench9 merged commit 580d439 into main May 19, 2026
3 checks passed
@jfrench9 jfrench9 deleted the feature/cloudflared-tunnel-option branch May 19, 2026 02:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant