How Preview of a running app works
The tunnel to localhost, the allowed ports, named targets, and what to do when Preview shows an old version of the page.
Preview opens the app running on your computer on localhost, right on your phone. Not a copy, not a screenshot — that running app. The Agent changes something, you tap refresh and you see it.
How the data gets there
The browser in the app talks to the phone's own localhost and the Server on the other side opens a TCP connection to localhost:port at its end. In between is the same encrypted channel everything else goes through.
Nothing is rewritten in the tunnel — neither headers nor bodies. Thanks to that WebSockets work too, so a dev server with hot reload behaves as it does on the computer.
Nothing gets published anywhere. An app on localhost stays on localhost; the tunnel exists only while Preview is open.
Allowed ports
The Server lets through only ports from the allowed ranges. By default these are:
3000–3999, 4000–4999, 5173, 8000–8999
The usual dev servers fit in there and databases and SSH do not. It can be changed in the Server's configuration:
{
"allowed_ports": [{ "from": 3000, "to": 3999 }, { "from": 9000, "to": 9000 }]
}
An attempt at a port outside the range is written into the Server's log. It is the only trace that the phone tried to reach somewhere it should not.
Named targets — and why they are worth it
Whole applications can be named in the Server's configuration, that is, not one port but a group:
{
"preview_targets": [
{ "name": "jobino", "port": 3000, "extra_ports": [8000] }
]
}
In Preview they then show up as a button under “From the Server's configuration” and open all of their ports at once.
This is the most common fix for a problem that looks like a broken app: the page loads, but logging in or loading data fails. The frontend runs on 3000, the API on 8000 — and when only 3000 is open, calls to the API lead nowhere from the phone. A named target opens both.
A target with a port outside the allowed ranges is rejected by the Server at startup. Were it let through, Preview would open and only the call to the API would fail — and it would be hunted in the app, not in the file where it really is.
An old version of the page
The most common confusion: the Agent reports it is done, but Preview still shows the old thing. Usually one of these three is to blame — in this order:
- The browser is holding the loaded page
- Tap refresh in the Preview bar. It is not an ordinary reload — it loads a new window, so nothing the browser stashed away is used. That is exactly why it is built this way: you press it at the moment you changed the code, and you want to see the new one.
- The dev server on the computer did not reload
- When the Agent changed something that is not picked up at runtime — configuration, dependencies, the build — refreshing on the phone will not help, because the old process is still running on the computer. Have the Agent restart that server.
- The tunnel is stuck on an old connection
- Close Preview and open it again. A new connection to localhost is made — that is the step that helps when the dev server restarted in the meantime and the old connection stayed hanging.
When even that does not help, it is almost always on the computer, not on the phone. The quickest check is to open that address in a browser on the computer.
Other things that get mixed up
- Nothing is listening on the port
- The app on the computer is not running, or it runs on a different port. Preview shows the error as a notice with the option to try again, not as a browser error page.
- It listens, but only on IPv6
- By default Vite binds to
[::1]. The Server tries both address families, so this works — and when it does fail anyway, the notice shows which address refused. - A white screen with no error
- Usually a request to another port, which leads nowhere from the phone. Preview catches such requests and tells you about them — but the fix is a named target with all the ports, see above.
- The app registers a service worker
- The browser on the phone does not support service workers over HTTP. Preview slips in an inert stand-in instead, so the app does not crash — but offline mode does not work in Preview and is not meant to.
- A broken layout
- Switch the width to “Computer”. An app written for the desktop behaves differently at a phone's width and that is not Preview's fault.
Stuck somewhere other than what is here? Write to support@coden-app.com.