ShipLocal
← Back to blog

ShipLocal build series · Part 1

Why Sharing localhost with Clients Is Still a Broken Developer Experience

The problem is not hosting. The problem is collaboration.

Article 1 in the ShipLocal build series — the problem before the architecture.

Every developer who ships work for someone else has lived this moment.

You finish a feature. You test it locally. It works. You message the client:

"The website is ready — can you check it?"

Then the workflow breaks.


The two bad options

Option A: Deploy just to show progress

You push to a staging branch. You wait for the build. You configure environment variables that only exist on your laptop. You fix a bug that only appears in production. You send the link.

Twenty minutes later — if you're lucky — the client opens the page.

This works. It is also absurd. You deployed infrastructure to answer a question that your local machine already knows the answer to: "Does this look right?"

Option B: Explain localhost to a non-technical client

You send instructions:

Open Terminal. Type this command. Go to localhost:3000.

The client opens their browser. Nothing loads. Because localhost on their laptop is not your laptop.

Or you reach for a tunneling tool. You paste a URL into WhatsApp. The client opens it. The page loads. Progress.

Then the feedback arrives:

"The button on the left should be bigger."

Which left? The one in the header? The sidebar? The footer on mobile? You stare at the message. You reply: "Can you send a screenshot?"

They send a screenshot. It is cropped. You guess. You deploy again.


The hidden workflow problem

Developers live here:

localhost:3000

Clients live here:

???

The internet was designed for production. DNS, HTTPS, CDNs, load balancers — the entire stack assumes your app lives on a server somewhere, reachable by a public URL.

But developers spend most of their time before that point. In dev servers. In hot reload. In half-finished branches on a machine that only exists on a desk in Lagos, Berlin, or Austin.

The gap is not technical ignorance. It is a workflow mismatch:

Developer thinks in…Client thinks in…
Components, routes, CSS"That thing on the left"
Git branches, env vars"Can you make it pop more?"
localhost ports"I clicked the link, nothing happened"
Deploy pipelines"Why do I have to wait until tomorrow?"

Hosting tools solve reachability: "Can they open the page?"

They do not solve feedback: "Can they tell me what to change in a way I can act on?"


What existing tools actually give you

It helps to separate the categories instead of comparing product logos.

Tunneling tools

ngrok, Cloudflare Tunnel, localtunnel, and others answer one question well:

"How do I put a public URL in front of my local port?"

They expose your machine. They do not know who your client is, what they commented on, or whether the feedback maps to a DOM element. When the session ends, the context often ends with it.

For a backend developer testing a webhook, that is enough. For an agency developer waiting on client approval, it is half the job.

Temporary deployments

Vercel previews, Netlify deploy previews, Railway, Render — spin up a URL from a branch or commit. Excellent for teams that already live in Git.

The cost is latency and ceremony. Every "can you check this?" becomes a mini-release: push, build, env sync, link, wait. For small visual tweaks — padding, copy, color — the deploy tax is disproportionate.

Staging servers

A persistent environment that mirrors production. The right long-term home for QA.

Wrong tool for "I changed the hero text ten minutes ago, does this feel right?" Staging is slow to update, often shared across projects, and still disconnected from how clients actually give feedback (email threads, WhatsApp voice notes, annotated PDFs).

The common gap

All of these solve access.

None of them solve collaboration:

  • Where on the page did the client mean?
  • What did the page look like when they said it?
  • How does feedback get back to the developer without a second translation step?
  • How do you iterate three times in an afternoon without three deploys?

They expose the machine, but they don't solve the collaboration workflow.


What "fixed" would look like

Imagine a workflow that matches how agencies actually work:

  1. Developer runs one command while the app is on localhost.
  2. Client gets a normal HTTPS link — shareable on WhatsApp, email, or Slack.
  3. Client opens the real running page, not a screenshot, not a PDF mockup.
  4. Client points at what they mean — on the element, on the page.
  5. Developer sees structured feedback tied to URL, selector, and viewport — not a voice note to decode.

The bar is not "replace production hosting." The bar is stop deploying just to ask a question.


What we built toward that workflow

I have been building ShipLocal around this loop — not as an ngrok replacement, but as a local-first collaboration layer for developers who hand work to clients.

Three pieces, deliberately simple:

1. The tunnel (entry point)

shiplocal 3000

Your local dev server gets a public preview URL. The client opens it in their browser. No deploy. No VPN instructions. No "install this first."

The tunnel is infrastructure — necessary, but not the product.

2. The feedback overlay (the actual product)

On proxied pages, clients see a 💬 control. They click an element, leave a comment, and the tool captures context: page path, coordinates, CSS selector, screenshot.

The client does not need to learn Figma, Jira, or your component library. They point and type in human language.

3. The developer dashboard (closing the loop)

Feedback lands in one place: which project, which preview session, which page, which element. The developer acts without playing telephone with a WhatsApp thread.


Why this matters beyond convenience

This is not only about saving deploy time. It changes when client input enters the process.

Today, many agencies treat client review as a gate before production — a formal phase with staging URLs and sign-off meetings.

If review can happen on localhost-quality iteration speed, feedback moves left: earlier, cheaper, less emotionally charged. The client sees work in progress. The developer fixes while context is fresh. The staging deploy becomes confirmation, not discovery.

That shift — from "show finished work" to "collaborate on in-progress work" — is what broken localhost sharing prevents today.


What is still hard (honest limits)

No tool fully fixes this yet. Some real constraints:

Full-stack apps. A frontend on :3000 calling an API on :4000 breaks naive tunnels — the client's browser cannot reach your backend via localhost. Multi-service routing is the next engineering layer, not a polish item.

Feedback quality. Clients can still be vague. Tools reduce ambiguity; they do not replace conversation.

Trust and access. A public preview URL is a temporary window into unfinished work. Password protection, expiry, and access control matter for agency use.

Production parity. Localhost is not production. Layout, performance, and env-specific bugs still require real deploys. The goal is fewer deploys for review, not zero deploys forever.


The question worth asking

Next time you finish a feature locally, ask:

Am I deploying because the work needs production infrastructure — or because I have no better way to show a human what I built?

If it is the second reason, the developer experience is still broken.

Tunneling fixed reachability years ago. Collaboration on localhost — structured feedback, client-readable previews, developer-actionable context — is still wide open.

That is the problem ShipLocal is built on. If you are an agency developer, freelancer, or team that lives in this loop, try it and tell us what breaks. Early testers are exactly who we need right now.


ShipLocal is open source. Go from localhost to client-ready in seconds. Collect visual client feedback on the live page. GitHub · Dashboard