Last updated on 7th March 2026

Deploy Remix Apps with DeployHQ

Prerequisites:

  • Node.js 18 or later
  • SSH access to your server
  • A DeployHQ account

What is Remix?

Remix is a React framework focused on web standards and progressive enhancement. It uses server-side rendering, nested routing with data loading, and form handling that works without JavaScript — enhancing progressively when JS is available.

As of 2025, Remix has merged with React Router v7 and uses Vite as its build tool.

Step 1: Create a Remix Project

npx create-remix@latest my-app
cd my-app
npm run dev

The app runs at http://localhost:5173.

Step 2: Project Structure

my-app/
├── app/
│   ├── root.tsx            # Root layout
│   ├── entry.client.tsx    # Client entry
│   ├── entry.server.tsx    # Server entry
│   └── routes/             # File-based routing
│       ├── _index.tsx      # → /
│       ├── about.tsx       # → /about
│       └── blog.$slug.tsx  # → /blog/:slug
├── public/                 # Static assets
├── build/                  # Build output
│   ├── server/             # Server bundle
│   └── client/             # Client assets
├── vite.config.ts
├── package.json
└── .env

Step 3: Core Features

Loaders (Server-Side Data Fetching)

// app/routes/blog.$slug.tsx
import type { LoaderFunctionArgs } from '@remix-run/node'
import { json } from '@remix-run/node'
import { useLoaderData } from '@remix-run/react'

export async function loader({ params }: LoaderFunctionArgs) {
  const post = await db.posts.findUnique({ where: { slug: params.slug } })
  if (!post) throw new Response('Not Found', { status: 404 })
  return json({ post })
}

export default function BlogPost() {
  const { post } = useLoaderData<typeof loader>()
  return <article><h1>{post.title}</h1><div>{post.content}</div></article>
}

Actions (Form Handling)

export async function action({ request }: ActionFunctionArgs) {
  const formData = await request.formData()
  const email = formData.get('email') as string
  await subscribe(email)
  return json({ success: true })
}

Nested Routes and Layouts

Routes can be nested — child routes render inside parent layouts via <Outlet />:

routes/
├── dashboard.tsx          # Layout for /dashboard/*
├── dashboard._index.tsx   # → /dashboard
├── dashboard.settings.tsx # → /dashboard/settings
└── dashboard.profile.tsx  # → /dashboard/profile

Step 4: Deploy with DeployHQ

Build Command

cd %path% && npm ci && npm run build

Build output: - build/server/ — Node.js server bundle - build/client/ — Static client assets

PM2 Configuration

// ecosystem.config.cjs
module.exports = {
  apps: [{
    name: 'remix-app',
    script: 'node_modules/.bin/remix-serve',
    args: './build/server/index.js',
    instances: 'max',
    exec_mode: 'cluster',
    env_production: {
      NODE_ENV: 'production',
      PORT: 3000,
    },
    max_memory_restart: '512M',
  }]
}

Post-Deployment SSH Command

cd /var/www/remix-app && npm ci --omit=dev && pm2 reload ecosystem.config.cjs --env production

Nginx Reverse Proxy

server {
    listen 443 ssl http2;
    server_name example.com;

    # Serve static client assets directly
    location /build/ {
        alias /var/www/remix-app/build/client/;
        expires 1y;
        add_header Cache-Control "public, immutable";
    }

    # Proxy all other requests to Remix server
    location / {
        proxy_pass http://localhost:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Environment Variables

Set on the server in .env: bash DATABASE_URL=postgresql://user:pass@localhost:5432/mydb SESSION_SECRET=a-long-random-string

Step 5: Troubleshooting

"Cannot find module" After Deployment

Ensure npm ci --omit=dev runs in the deployment directory. Remix's server bundle may reference packages that need to be installed on the server.

Hydration Mismatches

Ensure server and client render the same content. Common cause: using Date.now() or browser-only APIs in components without checking typeof window.

Port Conflicts

ss -tulnp | grep :3000
pm2 delete remix-app && pm2 start ecosystem.config.cjs --env production

Conclusion

Remix's web-standards approach and progressive enhancement make it a robust framework for server-rendered React applications. DeployHQ handles the build pipeline and PM2 process management for zero-downtime deployments.

Sign up for DeployHQ — free for one project.

Questions? Contact support@deployhq.com or on Twitter/X.