Configuring Fixed Port for Shopify App Dev Tunnel (Vite Modification)
Watch Video Tutorial
Configuring Fixed Port for Shopify App Dev Tunnel (Vite Modification)
Newcomer-friendly guide to pin the dev server to a stable port and remove common tunnel/HMR pitfalls on Windows.
Why fix the port?
- The Shopify App dev server (React Router + Vite) may choose a random port when the default is occupied, which complicates tunneling and reverse proxies.
- Fixing the port (e.g., 3001) makes FRP/Cloudflare Tunnel configuration straightforward and reduces daily friction.
Before vs After (Summary)
- Before:
react-router dev
may bind to an unpredictable port; HMR might attempt to bind to your public tunnel hostname, causingEADDRNOTAVAIL
on Windows. - After: Vite dev server uses port 3001 with
strictPort: true
; HMR listens locally without binding to the public hostname, while clients connect via the tunnel domain.
Step-by-step Changes
1) Update Vite server port and strict mode
File: vite.config.ts
Before:
server: {
port: Number(process.env.PORT || 3000),
host: "0.0.0.0",
hmr: /* dynamic host based on SHOPIFY_APP_URL */
}
After:
server: {
port: 3001,
host: "0.0.0.0",
strictPort: true,
hmr: hmrConfig, // see next section
}
2) Fix HMR to avoid binding to public tunnel IP
In vite.config.ts
, the HMR config is derived from SHOPIFY_APP_URL
. When using a public tunnel URL, binding HMR host to that domain may resolve to Cloudflare IPs (e.g., 104.21.x.x
) and fail to listen on Windows.
Before:
const
host =
new
URL(process.env.SHOPIFY_APP_URL || "http://localhost").hostname;
let
hmrConfig;
if
(host === "localhost") {
hmrConfig = { protocol: "ws", host: "localhost", port: 64999, clientPort: 64999 };
} else
{
hmrConfig = { protocol: "wss", host, port: parseInt(process.env.FRONTEND_PORT!) || 8002, clientPort: 443 };
}
After:
const
host =
new
URL(process.env.SHOPIFY_APP_URL || "http://localhost").hostname;
let
hmrConfig;
if
(host === "localhost") {
hmrConfig = { protocol: "ws", host: "localhost", port: 64999, clientPort: 64999 };
} else
{
hmrConfig = {
protocol: "wss",
// Do not bind HMR to the public hostname to avoid EADDRNOTAVAIL on Windows
port: parseInt(process.env.FRONTEND_PORT!) || 8002,
clientPort: 443,
};
}
This lets the HMR server listen locally, while the client still connects via the public tunnel domain.
3) Align FRP config (optional, only if you use FRP)
- Set
local_ip = 127.0.0.1
andlocal_port = 3001
in yourfrpc.ini
service entry. - If
netstat
shows the dev server listening on::1:3001
(IPv6), uselocal_ip = ::1
instead.
4) Start the dev server with your tunnel
Use:
shopify app dev --tunnel-url https://your-tunnel.example.com:443
Common Pitfalls and Lessons Learned
- Adding
--port
flags toreact-router dev
can sometimes cause "React Router Vite plugin not found" errors; prefer setting the port invite.config.ts
. - Binding HMR to the public hostname can cause
EADDRNOTAVAIL
on Windows. Keep HMR server local and let clients connect via your tunnel. - If you run behind a global proxy, clear proxy env vars for the dev shell or define
NO_PROXY=localhost,127.0.0.1,::1
to avoidETIMEDOUT
during local handshakes.
References
- Vite server config: https://vitejs.dev/config/server-options
- React Router dev docs: https://reactrouter.com/en/main/routers/picking-a-router
- Shopify CLI dev options: https://shopify.dev/docs/apps/tools/cli