# Run Your Entire Web Dev Workflow From Your Phone (Claude Code + a Tunnel)

Claude Code's Remote Control puts your session on your phone, but not your localhost. Here's the tunnel setup, and the dev-server config that bites.

> Source: https://pluginizelab.com/blog/run-your-entire-web-dev-workflow-from-your-phone

> Published: 2026-08-17 by Md Aminur Islam

There's a specific kind of frustration that shows up the moment you start driving Claude Code from your phone.

The coding part works beautifully. You're on a bus, or in a queue, or lying on the couch, and you can read what Claude is doing, approve a tool call, tell it to refactor the header, ask it to fix the failing test. Your laptop is at your desk doing the work. Your phone is the steering wheel.

Then Claude says "done - check it out at http://localhost:3000" and the whole thing falls apart. Your phone has no idea what `localhost:3000` means. That address points at your phone, not your laptop. You are now a developer who can write code but cannot look at it.

The fix is two pieces stacked on top of each other: **Remote Control** for driving the session, and **a tunnel** for seeing the result. Here's how to wire both up.

---

## Part 1: Get Claude Code onto your phone

Claude Code has a Remote Control feature that connects a session running on your machine to the Claude mobile app. The session never leaves your laptop - your files, your dev server, your node_modules all stay put. The phone is just a window into that terminal.

Start it from your project folder:

```bash
claude --remote-control "My App"
```

You get a normal interactive session at your desk *and* the same session on your phone. If you're already mid-conversation, `/remote-control` (or `/rc`) does the same thing without losing your history. If you're walking away entirely, `claude remote-control` runs it in server mode with no local prompt.

Either way, Claude Code prints a session URL and a QR code. Scan the QR into the Claude app, or open the app, tap **Code**, and pick the session from the list. That's the whole setup.

Two things worth doing while you're at the desk:

- **Turn on push notifications.** Run `/config` and enable pushes. Now your phone buzzes when a build finishes or Claude needs a decision, instead of you checking every few minutes. You can also ask inline: *"notify me when the tests pass."*
- **Check you're eligible.** Remote Control needs a paid claude.ai plan and `claude auth login` - API keys won't work. If it refuses to start, `claude doctor` says why.

## Part 2: Why you still can't see your app

You've got Claude in your pocket. You tell it to change the hero section. It does. And you're stuck, because the dev server is bound to `127.0.0.1:5173` on a laptop that's behind your router, behind your ISP's NAT, with no public address.

Being on the same Wi-Fi *sometimes* helps - you can occasionally reach `http://192.168.1.x:5173` if your dev server binds to `0.0.0.0`. But that dies the moment you leave the house, and it's exactly when you leave the house that this whole setup becomes worth doing.

What you actually want is a **tunnel**: a small daemon on your laptop that opens an outbound connection to a public edge server, which then forwards inbound requests back down that pipe to your local port. No firewall changes. No port forwarding. No public IP.

---

## Part 3: Picking a tunnel

Three realistic options, and the free tiers diverge sharply.

### Cloudflare Tunnel - the best free option

Two flavours, and the distinction matters more than most tutorials admit.

**Quick tunnel** (no account, one command):

```bash
cloudflared tunnel --url http://localhost:5173
```

You get a random `trycloudflare.com` subdomain, printed to your terminal. Copy it to your phone and you're browsing your local app from anywhere. Zero setup.

The catch: quick tunnels are explicitly meant for development and testing. Expect a cap on in-flight requests, no Server-Sent Events support, no SLA, and a brand new URL every time you restart. For "look at my dev server for the next two hours," that's completely fine. For anything you want to leave running, it isn't.

**Named tunnel** (account + your domain on Cloudflare DNS):

More setup - you create the tunnel, route DNS to it, and usually end up managing a `config.yml` or a system service. In exchange you get a stable URL on your own domain that survives restarts, free SSL, no bandwidth caps, no session timeouts, and you can put Cloudflare Access in front of it so only you can load the page. The requirement is that your domain's authoritative DNS lives on Cloudflare.

If you're going to do this more than twice, spend the twenty minutes on a named tunnel. `dev.yourdomain.com` pointing at your laptop is a genuinely nice thing to own.

### ngrok - fastest to first URL, tightest free tier

```bash
ngrok http 5173
```

Still the quickest path from nothing to a public URL, and its request inspector is better than anything Cloudflare gives you - useful if you're debugging webhooks rather than just eyeballing a page. Every account gets a free static domain, so your URL doesn't rotate on restart.

But ngrok cut its free plan hard in early 2026: roughly 2-hour session limits, about 1 GB of monthly bandwidth, a handful of endpoints, and an interstitial warning page in front of every visit. The two-hour ceiling is the one that hurts here - you'll be restarting the tunnel and re-copying URLs in the middle of your afternoon. Removing the interstitial means paying.

### Expose - good if you're already in PHP land

```bash
expose share http://localhost:8000
```

Beyond Code's tool, self-hostable, and pleasant if you're doing Laravel work. The shared free tier has session limits similar in spirit to ngrok's. Its real selling point is that you can run your own Expose server on a cheap VPS and stop worrying about anyone's free tier.

### Quick comparison

| | Cloudflare quick | Cloudflare named | ngrok free | Expose (shared) |
|---|---|---|---|---|
| Account needed | No | Yes | Yes | Yes |
| Setup time | ~1 min | ~20 min | ~2 min | ~2 min |
| Session limit | None | None | ~2 hours | Limited |
| Stable URL | No | Yes (your domain) | Yes (static domain) | No |
| Bandwidth cap | None stated | None | ~1 GB/mo | Limited |
| Interstitial page | No | No | Yes | No |
| Best for | A quick look | Leaving it running | Webhook debugging | PHP/Laravel |

**Recommendation:** quick tunnel today because you want to get moving, named tunnel this weekend because you'll want it permanently.

---

## Part 4: The dev-server config that will bite you

This is the step nobody warns you about, and it produces a blank page or a scary error banner that looks like the tunnel is broken. It isn't. Modern dev servers reject requests whose `Host` header doesn't match localhost, as a defence against DNS rebinding attacks. Your tunnel's hostname is, by definition, not localhost.

**Vite** - add the tunnel host to `vite.config.js`:

```js
export default {
  server: {
    host: true,              // bind 0.0.0.0
    allowedHosts: ['.trycloudflare.com', 'dev.yourdomain.com'],
    hmr: { clientPort: 443 } // HMR over the tunnel's HTTPS
  }
}
```

That `hmr.clientPort` line is what makes hot reload keep working through the tunnel. Without it the page loads but never updates, and you'll waste ten minutes wondering why Claude's changes aren't showing up.

**Next.js** - add `allowedDevOrigins` in `next.config.js`:

```js
module.exports = {
  allowedDevOrigins: ['dev.yourdomain.com', '*.trycloudflare.com']
}
```

**WordPress through Herd** has the same shape of problem with a different cause: WordPress stores the site URL in the database, so the tunneled page loads as bare HTML while every asset still points at `my-site.test`.

Herd installs Expose into your PATH and gives you a token field in settings, so from the site directory the tunnel is one command:

```bash
herd share
```

Herd detects the site's local URL and shares the right address. Now teach WordPress to answer on the public hostname, in `wp-config.php` above the "stop editing" line:

```php
$tunnel_host = $_SERVER['HTTP_X_FORWARDED_HOST'] ?? '';

if (str_contains($tunnel_host, 'sharedwithexpose.com')) {
    $_SERVER['HTTP_HOST'] = $tunnel_host;
    $_SERVER['HTTPS']     = 'on';
    define('WP_HOME',       'https://' . $tunnel_host);
    define('WP_SITEURL',    'https://' . $tunnel_host);
    define('COOKIE_DOMAIN', $tunnel_host);
}
```

Keep it conditional - hardcoding `WP_SITEURL` to the tunnel fixes the phone and breaks local `.test` access. Matching the suffix rather than an exact domain means it survives the free plan's rotating subdomains. The `HTTPS` line stops WordPress emitting `http://` URLs onto an `https://` page, since Expose terminates TLS before PHP sees the request.

Confirm the header name before trusting that snippet. Expose rewrites `Host` so Herd can pick the right site, which means the public hostname arrives in a forwarded header - load the tunnel on your phone, then read the actual request headers in Expose's dashboard at `http://127.0.0.1:4040` and swap the key if it differs.

URLs baked into post content still point at `.test`, and multisite is worse again - don't tunnel that one.

While you're here, tell Claude about it once and never think about it again. Drop this in your `CLAUDE.md`:

```markdown
## Mobile preview
This project is previewed through a Cloudflare tunnel at dev.yourdomain.com.
When adding a dev server config, keep `allowedHosts` and HMR clientPort 443 intact.
Never hardcode localhost URLs in client-side code - use relative paths.
```

That last line matters more than it looks. A hardcoded `http://localhost:8000/api` in your frontend works perfectly on your laptop and fails silently on your phone, because your phone's "localhost" is your phone.

---

## Part 5: Putting it together

The full startup sequence, run once at your desk before you go anywhere:

```bash
# Terminal 1 - your app
npm run dev

# Terminal 2 - the tunnel
cloudflared tunnel --url http://localhost:5173

# Terminal 3 - Claude, with Remote Control on
cd ~/projects/my-app
claude --remote-control "My App"
```

Then: scan the QR code into the Claude app, copy the tunnel URL into your phone's browser, and put both on your home screen or in split view. That's the whole rig.

The loop, once you're out of the house:

1. **Type into the Claude app.** "The mobile nav overlaps the logo below 400px. Fix it."
2. **Claude works on your laptop.** You watch the tool calls stream by. Approve anything it asks about.
3. **Switch to your browser tab, pull to refresh.** Or don't - with HMR configured, it's usually already updated.
4. **Screenshot the problem and send it back.** This is the part that feels like cheating. Attach a photo or screenshot in the Claude app; Claude sees the image directly as part of your message. "Still overlapping, see attached" is a complete bug report.
5. **Repeat.**

---

## Part 6: The things that will go wrong

**Your laptop must stay awake.** Both the session and the tunnel are local processes - a closed lid ends the party. Disable sleep on AC power. This is a preview mechanism, not hosting; if you want the app up permanently, deploy it.

**Your dev server is now public.** A `trycloudflare.com` URL is unguessable but has no auth in front of it. Never tunnel a production database, an admin panel, or a debug endpoint, use fake data locally, and stop the tunnel when you're done. On a named tunnel, put Cloudflare Access in front and the problem goes away.

**Dropped sessions come back.** Ctrl+C'd out of `claude remote-control`? Run it again in the same directory within a few hours and it picks up where it left off.

Set it up once. The next time you have an idea on a walk, you can just build it.