1import{r as o}from"./vendor-query-BVEPqhnh.js";import{d as t,f as n}from"./index-Cy7bmE2h.js";import{t as r}from"./BlogPost-BO62Vwf-.js";var e=o();function l(){return(0,e.jsxs)(r,{slug:"/blog/test-localhost-on-mobile",tag:"Dev",title:n,lede:"Your dev server is happily humming at http://localhost:3000. Your phone has no idea what that is. Here are five real ways to bridge that gap, roughly ordered from easiest to most bulletproof.",image:t,imageAlt:"Web developer's desk at night: laptop open to a code editor with a smartphone propped beside it displaying a localhost preview over USB â a practical setup for testing local dev servers on a real mobile device",date:"Jun 17, 2026",read:"10 min read",children:[(0,e.jsxs)("p",{children:["Every web developer hits this wall eventually. You've built something beautiful, it's running on ",(0,e.jsx)("code",{children:"localhost:3000"}),`, and you want to see it on your phone. Except your phone doesn't know what "localhost" means â localhost is your laptop, not the phone. So how do you get one to see the other?`]}),(0,e.jsx)("p",{children:'Here are five ways, from "works in 30 seconds" to "works even behind a hostile corporate firewall in a coffee shop bathroom".'}),(0,e.jsx)("h2",{children:"Method 1: Use your LAN IP address (30 seconds, most of the time)"}),(0,e.jsxs)("p",{children:["Both your laptop and your phone are on the same Wi-Fi, right? Then your laptop has an IP address that your phone can actually reach. On macOS or Linux, run"," ",(0,e.jsx)("code",{children:"ifconfig"})," (or ",(0,e.jsx)("code",{children:"ip a"}),"). On Windows, run ",(0,e.jsx)("code",{children:"ipconfig"}),". Look for something like ",(0,e.jsx)("code",{children:"192.168.1.42"}),"."]}),(0,e.jsxs)("p",{children:["Now, on your phone, open the browser and go to"," ",(0,e.jsx)("code",{children:"http://192.168.1.42:3000"}),". If your dev server is listening on all interfaces, you'll see your site. Done."]}),(0,e.jsxs)("p",{children:[(0,e.jsx)("strong",{children:"Gotcha:"})," Vite, Next.js dev, and Create React App all default to listening on ",(0,e.jsx)("code",{children:"localhost"})," only, which means only your laptop can reach them. Start them with ",(0,e.jsx)("code",{children:"--host"})," (Vite) or ",(0,e.jsx)("code",{children:"-H 0.0.0.0"}),"(Next.js) to bind to all interfaces. Otherwise your phone will just spin."]}),(0,e.jsx)("h2",{children:"Method 2: A tunnel (ngrok, cloudflared, tailscale funnel)"}),(0,e.jsx)("p",{children:"When you're not on the same network â coffee shop Wi-Fi that blocks device-to-device traffic, a client's guest network, testing from a phone on cellular â LAN tricks fall apart. Enter tunnels."}),(0,e.jsxs)("p",{children:["Install ",(0,e.jsx)("a",{href:"https://ngrok.com",target:"_blank",rel:"noopener noreferrer",children:"ngrok"}),", run ",(0,e.jsx)("code",{children:"ngrok http 3000"}),", and it hands you back a public HTTPS URL like"," ",(0,e.jsx)("code",{children:"https://abc123.ngrok.io"}),". That URL works from anywhere in the world, including your phone on cellular. Cloudflare Tunnel and Tailscale Funnel do the same thing with slightly different tradeoffs."]}),(0,e.jsxs)("p",{children:[(0,e.jsx)("strong",{children:"Bonus:"})," HTTPS out of the box, which means you can test service workers, PWAs, and any feature that requires a secure context â something you can't do with a raw LAN IP."]}),(0,e.jsx)("h2",{children:"Method 3: Chrome DevTools remote debugging (Android)"}),(0,e.jsxs)("p",{children:["This one is under-used and genuinely magical. Plug an Android phone into your laptop via USB, enable USB debugging on the phone, open"," ",(0,e.jsx)("code",{children:"chrome://inspect"}),' on your laptop, and enable "Port forwarding" with port 3000 â localhost:3000.']}),(0,e.jsxs)("p",{children:["Now on the phone, browsing to ",(0,e.jsx)("code",{children:"http://localhost:3000"})," hits your laptop's dev server through the USB cable. You also get a full remote DevTools instance so you can inspect elements, debug JavaScript, and watch network requests happening on the phone â from your laptop."]}),(0,e.jsx)("h2",{children:"Method 4: Safari remote debugging (iPhone)"}),(0,e.jsx)("p",{children:"Same idea, iOS version. Plug the iPhone into a Mac via USB, enable Web Inspector in iOS Safari's advanced settings, open Safari on the Mac, and the iPhone's tabs show up under Develop â [Your iPhone]."}),(0,e.jsx)("p",{children:"You still need to point the iPhone at your laptop somehow â usually via method 1 (LAN IP) or method 2 (tunnel). But once it's loaded, you can inspect DOM, run console commands, and debug the exact page on the exact phone, remotely from the Mac."}),(0,e.jsx)("h2",{children:`Method 5: A mobile simulator (the "I don't want to leave my laptop" option)`}),(0,e.jsxs)("p",{children:["Half the time, you don't really need to touch the phone. You just want to see if the layout works at iPhone widths and doesn't fall apart at 375px."," ",(0,e.jsx)("a",{href:"/download",children:"MobileReady"})," and other mobile simulators run inside your browser, so they can preview ",(0,e.jsx)("em",{children:"anything the browser can reach"})," â including localhost. Point the simulator at ",(0,e.jsx)("code",{children:"http://localhost:3000"}),", pick a device from the dropdown, and you're looking at your dev site inside an iPhone or Pixel frame in about two seconds."]}),(0,e.jsx)("p",{children:`You should still test on physical hardware eventually. But for daily "does this layout work" checks, a simulator is faster than any tunnel, LAN trick, or USB cable. It's the difference between checking your work every 30 seconds and checking it every 30 minutes.`}),(0,e.jsx)("h2",{children:"Which one should you use?"}),(0,e.jsxs)("ul",{children:[(0,e.jsxs)("li",{children:[(0,e.jsx)("strong",{children:"Qu
1ickest layout check:"})," Mobile simulator extension."]}),(0,e.jsxs)("li",{children:[(0,e.jsx)("strong",{children:"Same Wi-Fi, real phone:"})," LAN IP + ",(0,e.jsx)("code",{children:"--host"}),"."]}),(0,e.jsxs)("li",{children:[(0,e.jsx)("strong",{children:"Different network / cellular / demoing to a client:"})," ngrok or Cloudflare Tunnel."]}),(0,e.jsxs)("li",{children:[(0,e.jsx)("strong",{children:"Deep debugging on Android:"})," chrome://inspect + port forwarding."]}),(0,e.jsxs)("li",{children:[(0,e.jsx)("strong",{children:"Deep debugging on iOS:"})," Safari Web Inspector via USB."]})]}),(0,e.jsx)("h2",{children:"Bonus: HTTPS on localhost"}),(0,e.jsxs)("p",{children:["Some features (service workers, camera access, geolocation) refuse to work over plain ",(0,e.jsx)("code",{children:"http://"}),". If you're testing those on mobile, either use a tunnel (which provides HTTPS automatically) or set up"," ",(0,e.jsx)("a",{href:"https://github.com/FiloSottile/mkcert",target:"_blank",rel:"noopener noreferrer",children:"mkcert"})," ",`to give your local dev server a trusted certificate. It's a one-time setup that saves years of "why doesn't this work on my phone" frustration.`]}),(0,e.jsx)("p",{children:"Testing on mobile shouldn't be an afterthought at the end of the sprint. Set up one of these methods once, bookmark it, and start checking your work on a real screen size the same day you build it. The bugs you catch on Tuesday are much cheaper than the ones a client catches on Friday."})]})}
1export{l as component};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.