Skip to main content
Technical Guides Shopify Remix

How to change the localhost port for the new Shopify Remix App template

A comprehensive guide on changing the default localhost port for Shopify Remix apps, including WebSocket configuration for hot module replacement.

honeybound Team
5 min read
How to change the localhost port for the new Shopify Remix App template

After scanning through the docs, there is a simple parameter we can pass to set the port for Shopify Remix apps. However, getting hot module replacement (HMR) to work properly requires a few additional steps. This guide will walk you through the complete process.

The Challenge

When developing multiple Shopify apps locally or when port 3000 is already in use, you need to change the default port. While changing the port itself is straightforward, ensuring that HMR (Hot Module Replacement) continues to work requires additional configuration.

Step-by-Step Solution

Step 1: Update shopify.web.toml

First, modify your shopify.web.toml file to specify the custom port:

name = "remix"
roles = ["frontend", "backend"]
webhooks_path = "/webhooks"
port = 8002  # Add this line - change to your desired port

[commands]
dev = "npm exec remix dev"

[hmr_server]
http_paths = ["/ping"]

The port parameter tells Shopify CLI which port to use for your app.

Step 2: Create a Custom LiveReload Component

The default LiveReload component doesn’t handle custom ports well. Create a new file at app/components/LiveReload.tsx:

import { useEffect } from "react";

/**
 * Custom LiveReload component that supports custom ports
 * This component handles WebSocket connections for HMR
 */
export const LiveReload =
  process.env.NODE_ENV !== "development"
    ? () => null
    : function LiveReload({
        port = Number(process.env.REMIX_DEV_SERVER_WS_PORT || 8002),
        nonce = undefined,
      }: {
        port?: number;
        nonce?: string;
      }) {
        useEffect(() => {
          const protocol = location.protocol === "https:" ? "wss:" : "ws:";
          const host = location.hostname;
          const socketPath = `${protocol}//${host}:${port}/socket`;

          const ws = new WebSocket(socketPath);
          
          ws.onmessage = (message) => {
            const event = JSON.parse(message.data);
            if (event.type === "LOG") {
              console.log(event.message);
            }
            if (event.type === "RELOAD") {
              console.log("💿 Reloading window ...");
              window.location.reload();
            }
          };
          
          ws.onerror = (error) => {
            console.log("Remix dev asset server web socket error:");
            console.error(error);
          };
          
          return () => {
            ws.close();
          };
        }, [port]);

        return null;
      };

Step 3: Update Your Root Component

Replace the default LiveReload import in your app/root.tsx (or app/root.jsx):

// Remove the old import
// import { LiveReload } from "@remix-run/react";

// Add your custom import
import { LiveReload } from "./components/LiveReload";

export default function App() {
  return (
    <html>
      <head>
        <meta charSet="utf-8" />
        <meta name="viewport" content="width=device-width,initial-scale=1" />
        <Meta />
        <Links />
      </head>
      <body>
        <Outlet />
        <ScrollRestoration />
        <Scripts />
        {/* Pass your custom port to LiveReload */}
        <LiveReload port={8002} />
      </body>
    </html>
  );
}

Step 4: Create a Ping Route

HMR needs a ping endpoint to check if the server is alive. Create a new file at app/routes/ping.tsx:

import { json } from "@remix-run/node";
import type { ActionFunction } from "@remix-run/node";

// This route handles HMR ping requests
export const action: ActionFunction = async ({ request }) => {
  return json({ success: true }, 200);
};

// Also handle GET requests for debugging
export const loader = async () => {
  return json({ success: true }, 200);
};

Step 5: Update package.json Scripts

Modify your development script in package.json to include the tunnel URL:

{
  "scripts": {
    "dev": "shopify app dev --tunnel-url=https://your-tunnel-domain.com:80",
    "dev:local": "shopify app dev",
    "build": "remix build",
    "start": "remix-serve build"
  }
}

If you’re using a custom tunnel service (like Cloudflare Tunnel), make sure to specify port 80 as shown.

Step 6: Environment Variables (Optional)

For more flexibility, you can use environment variables. Create or update your .env file:

# Development port configuration
PORT=8002
REMIX_DEV_SERVER_WS_PORT=8002

# Shopify app configuration
SHOPIFY_APP_URL=https://your-tunnel-domain.com
SCOPES=write_products,write_customers,write_draft_orders

Then update your shopify.web.toml to use the environment variable:

name = "remix"
roles = ["frontend", "backend"]
webhooks_path = "/webhooks"
# Port will be read from PORT env variable, fallback to 8002
port = 8002

[env]
PORT = { default = "8002" }

Advanced Configuration

Multiple App Development

If you’re developing multiple Shopify apps simultaneously, you can create a port configuration system:

// config/ports.ts
export const APP_PORTS = {
  mainApp: 3000,
  inventoryApp: 8001,
  analyticsApp: 8002,
  marketingApp: 8003,
} as const;

export function getAppPort(appName: keyof typeof APP_PORTS): number {
  return APP_PORTS[appName] || 3000;
}

Docker Configuration

If you’re using Docker for development, update your docker-compose.yml:

version: '3.8'

services:
  shopify-app:
    build: .
    ports:
      - "8002:8002"  # Map custom port
      - "8002:8002/udp"  # For WebSocket
    environment:
      - PORT=8002
      - REMIX_DEV_SERVER_WS_PORT=8002
      - NODE_ENV=development
    volumes:
      - .:/app
      - /app/node_modules
    command: npm run dev

Nginx Proxy Configuration

If you’re using Nginx as a reverse proxy:

server {
    listen 80;
    server_name your-app.local;

    # Main app proxy
    location / {
        proxy_pass http://localhost:8002;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
    }

    # WebSocket proxy for HMR
    location /socket {
        proxy_pass http://localhost:8002;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "Upgrade";
        proxy_set_header Host $host;
    }
}

Troubleshooting

HMR Not Working

If hot module replacement isn’t working:

  1. Check WebSocket connection:

    // In browser console
    const ws = new WebSocket('ws://localhost:8002/socket');
    ws.onopen = () => console.log('WebSocket connected');
    ws.onerror = (e) => console.error('WebSocket error:', e);
    
  2. Verify ping route:

    curl http://localhost:8002/ping
    # Should return: {"success":true}
    
  3. Check for port conflicts:

    lsof -i :8002  # macOS/Linux
    netstat -ano | findstr :8002  # Windows
    

Port Already in Use

If you get a “port already in use” error:

# Find and kill the process using the port
# macOS/Linux
lsof -ti:8002 | xargs kill -9

# Windows
netstat -ano | findstr :8002
taskkill /PID <PID> /F

Environment Variable Issues

Make sure your environment variables are loaded:

// app/utils/config.ts
export function getPort(): number {
  const port = process.env.PORT || process.env.REMIX_DEV_SERVER_WS_PORT || '8002';
  return parseInt(port, 10);
}

// Use in your components
import { getPort } from '~/utils/config';
const port = getPort();

Best Practices

  1. Use consistent ports: Keep the same port for both the app and WebSocket server
  2. Document your ports: Maintain a README with port assignments for team members
  3. Use environment variables: Makes configuration more flexible
  4. Test WebSocket connections: Ensure HMR works after configuration changes
  5. Handle errors gracefully: Add error handling to your LiveReload component

Port Configuration Reference

Here’s a quick reference for common port configurations:

ServiceDefault PortCommon Alternatives
Shopify App30008000, 8001, 8002
Remix Dev Server30018001, 8003
Database5432 (Postgres)5433, 5434
Redis63796380, 6381
Webhook TunnelRandomFixed with tunnel service

Conclusion

Changing the localhost port for Shopify Remix apps requires more than just updating a single configuration value. By following this guide, you’ll have a properly configured development environment with working hot module replacement on your custom port.

Remember to:

  • Update all related configurations (shopify.web.toml, LiveReload component, package.json)
  • Test WebSocket connections for HMR
  • Document your port choices for team collaboration
  • Use environment variables for flexibility

With these configurations in place, you can run multiple Shopify apps simultaneously or work around port conflicts without sacrificing developer experience.

Share this article

View More Articles

Stay Ahead of the Curve

Get weekly insights on ecommerce trends, Shopify tips, and growth strategies delivered straight to your inbox.

Join 5,000+ ecommerce professionals. Unsubscribe anytime.