> **New in 2026 — Managed VPS hosting**: DeployHQ now offers [Managed VPS Hosting](https://www.deployhq.com/hosting/managed-vps) — fully-managed Linux servers with built-in CI/CD for Remix SSR. The walkthrough below covers BYO-server deployment with PM2, which remains fully supported.

**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

```bash
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)

```typescript
// 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)

```typescript
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

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

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

### PM2 Configuration

```javascript
// 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

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

### Nginx Reverse Proxy

```nginx
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

```bash
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](https://www.deployhq.com/signup) — free for one project.

Questions? Contact [support@deployhq.com](mailto:support@deployhq.com) or on [Twitter/X](https://x.com/deployhq).
