I’ve been working on something new called Relay.
This isn’t a launch post. It’s more of a starting point.
I want to write about Relay while I’m still building it: what problem it solves, why certain technical decisions exist, what works, what breaks, and the bits where something that looked like a 20-minute task quietly steals half the day.
Relay started from a frustration I’ve had for a long time.
Local development is still weirdly isolated.
You can have a website, API, service or database running perfectly on your own machine, but the moment somebody else needs to interact with it, things often become more complicated than they have any right to be.
A designer wants to try the latest version. QA wants to reproduce something on a real device. A senior developer wants to quickly review somebody else’s work. A junior developer is stuck and could really do with someone seeing the actual behaviour and logs instead of receiving the timeless classic:
“It just doesn’t work.”
A technician might need to understand how a physical device is interacting with a service. Or a colleague might simply need temporary access to an API or database you’re already running.
None of those are unusual situations.
And yet the normal answer is often some combination of waiting for CI, deploying unfinished work, setting up staging, opening ports, creating temporary tunnels, changing config, sending screenshots around, copying logs into chat, and asking someone to refresh one more time because this time it definitely should work.
All because the thing they wanted to use was already running.
That always felt slightly backwards.
Local doesn’t have to mean isolated
The principle behind Relay is simple: if something is already running locally, other people and devices should be able to interact with it without turning that into a deployment exercise.
Relay does not require an SDK inside the application, framework-specific plugins, or changes to the project just to make the connection work.
You start your application exactly as you normally do, start Relay alongside it, and carry on working.
Relay handles the connection between that local environment and whatever needs access to it.
That could be a real phone being used to test a web application. It could be a browser on somebody else’s machine so a designer or QA engineer can try the latest version. It could be another developer connecting through the CLI. The same idea also applies beyond websites to APIs and other local development services.
The destination isn’t really the interesting bit.
The source remains your development environment.
You don’t have to turn unfinished work into a deployment just because somebody else wants to poke it.
Relay extends the environment you already have rather than forcing you to rebuild your workflow around it.
Sharing it is only half the problem
Simply getting an application from one machine to another isn’t especially interesting on its own. There are already plenty of ways to expose a port or generate a temporary URL.
The more interesting part is what happens once somebody connects.
Say QA reproduces an issue on a physical phone. Great. Now the developer gets to ask the traditional sequence of questions:
“Any errors?”
“What does the console say?”
“Can you screenshot that?”
“Can you try it again?”
“Hang on, I’ve changed something.”
“Try it again.”
That whole loop is unnecessarily clumsy.
Relay keeps the useful information flowing both ways.
The application and its normal traffic travel towards the connected client, while diagnostics can come back: console output, JavaScript errors, failed requests, route information, screenshots and device state.
A designer can try an interaction while the developer is still changing it. A senior developer can help somebody debug without recreating the entire setup first. QA can reproduce a problem on the real device while the developer sees what the application is actually reporting.
And when the code changes, the connected client updates through HMR or Fast Refresh rather than waiting for another build, deployment, or ceremonial refresh button press.
That is a much more interesting problem than simply sharing a URL.
Relay stays outside the application
One of Relay’s core architectural decisions is that it does not become part of the application itself.
It would obviously make certain problems easier if every project installed a Relay package, added a plugin, and let Relay sprinkle a bit of magic through the app.
It would also mean Relay becomes yet another thing living in the codebase, which rather defeats the point.
Your application does not need to know Relay exists.
That means Relay handles the awkward stuff externally.
HTTP needs to behave correctly. WebSockets need to survive the journey. Cookies still need to belong to the application. Framework development servers need to keep doing whatever slightly cursed thing they normally do to make live updates work.
Relay’s own authentication, device controls and diagnostics are kept separate from the application traffic.
That separation makes the engineering harder, but it keeps the application clean and keeps Relay from becoming another framework-specific dependency.
The best developer tools tend to fit around an existing workflow.
The less good ones tend to introduce themselves with a 14-step migration guide.
Relay is aiming firmly for the first category.
Where it is now
This isn’t all theoretical. Relay Local is already far enough along that the core loop works.
Relay can run against a local development server, create a gateway, pair with a physical Android device, proxy HTTP and WebSocket traffic, preserve live updates and send useful diagnostics back from the device.
The Android client can report console messages, JavaScript exceptions and failed network requests back to the CLI while the application is being used.
A lot of the work recently has been around compatibility.
Development servers all solve roughly the same problem, which of course means they’ve all found slightly different ways of making your life interesting.
Vite-based projects have been a good starting point, and I’ve also spent time making Next.js and Turbopack behave correctly through Relay. I’m working through a much broader compatibility matrix rather than designing Relay around one ecosystem and hoping everybody else behaves themselves.
There’s still plenty being built: route synchronisation, more device controls, screenshots, broader diagnostics, automated compatibility testing and the remote side of Relay are all at different stages.
That’s one of the reasons I’m writing about it now instead of waiting until everything looks suspiciously polished and the development history has been conveniently forgotten.
The idea got bigger fairly quickly
The first thing I built was the local real-device workflow.
Then I started thinking about the underlying problem and realised it applies to quite a lot more than phones.
Relay currently starts with the local side: connecting real devices, browsers and other clients to things already running on your machine.
The next obvious step is a cloud-connected version that removes the same-network limitation.
That means the person testing your work doesn’t need to be sat next to you, or even on the same Wi-Fi. A designer, tester, colleague or customer could connect securely over the internet while the thing they’re using is still running on your development machine.
The same idea also applies beyond web pages. APIs and other local development services have exactly the same “it works here, but somebody else needs access to it” problem.
Some of that is working, some of it is being built, and some of it is still in the category of “this should be straightforward”, which is a phrase software development has repeatedly taught me not to trust.
But the direction is pretty clear: make the boundary around local development much less restrictive.
Why I’m writing about it now
I could wait until Relay is finished, build a polished landing page, write the documentation, remove all evidence that anything was ever difficult, and then announce it as though the whole thing emerged fully formed one Tuesday afternoon.
That would make for a cleaner story.
It would also skip most of the interesting part.
There are already technical decisions I’ve changed my mind about. There have been tasks I expected to take an hour that ate most of a day, and things I assumed would be horrible that worked first time and immediately made me suspicious.
There will be plenty more of both.
So I’m going to write about those parts as they happen: framework compatibility, HMR, WebSockets, real-device diagnostics, Android, tunnelling, security, remote connectivity, and whatever else decides to become the problem of the evening.
The first Android testing group will come later this month, but I’m not treating this series as a countdown to that.
It’s really just a record of building the thing while the interesting decisions are still fresh.
Relay started with one fairly simple observation:
Something being local shouldn’t mean everybody else has to wait until you deploy it.
Now I get to find out how far that idea goes.





Top comments (0)