> **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 Nitro SSR mode. (For SSG `nuxt generate` builds, [DeployHQ Static Hosting](https://www.deployhq.com/hosting/static) on Cloudflare's edge is the simpler path.) The walkthroughs below cover BYO-server deployment, which remains fully supported.

**Prerequisites:**

- Node.js 18 or later (Node.js 20 LTS recommended)
- A server with SSH access (for SSR) or SFTP access (for static)
- A DeployHQ account

## What is Nuxt?

Nuxt is the official meta-framework for Vue.js. It provides server-side rendering (SSR), static site generation (SSG), and hybrid rendering out of the box, built on top of the Nitro server engine and Vite.

Nuxt 3 handles routing, data fetching, state management, and build configuration automatically — letting you focus on building features rather than configuring tooling. It supports auto-imported components, composables, and a powerful module ecosystem.

## Step 1: Create a Nuxt Project

```bash
npx nuxi init my-app
cd my-app
npm install
```

Start the development server:
```bash
npm run dev
```

The app runs at `http://localhost:3000` with hot module replacement.

## Step 2: Project Structure

```
my-app/
├── app.vue                 # Root component
├── pages/                  # File-based routing
│   ├── index.vue           # → /
│   ├── about.vue           # → /about
│   └── blog/
│       ├── index.vue       # → /blog
│       └── [slug].vue      # → /blog/:slug
├── components/             # Auto-imported components
│   ├── AppHeader.vue
│   └── AppFooter.vue
├── composables/            # Auto-imported composables
│   └── useAuth.ts
├── layouts/                # Layout components
│   └── default.vue
├── middleware/              # Route middleware
├── plugins/                # Vue plugins
├── server/                 # Server-side code (Nitro)
│   ├── api/                # API routes
│   │   └── posts.get.ts    # → GET /api/posts
│   └── middleware/
├── assets/                 # Processed by Vite (SCSS, images)
├── public/                 # Static files (favicon, robots.txt)
├── nuxt.config.ts          # Nuxt configuration
└── package.json
```

### File-Based Routing

Pages in the `pages/` directory automatically become routes:

| File | Route |
|------|-------|
| `pages/index.vue` | `/` |
| `pages/about.vue` | `/about` |
| `pages/blog/index.vue` | `/blog` |
| `pages/blog/[slug].vue` | `/blog/:slug` |
| `pages/users/[id]/posts.vue` | `/users/:id/posts` |

### Auto-Imports

Components in `components/`, composables in `composables/`, and utilities in `utils/` are auto-imported. No `import` statements needed:

```vue
<script setup>
// useAuth is auto-imported from composables/useAuth.ts
const { user, logout } = useAuth()

// useFetch is a built-in Nuxt composable
const { data: posts } = await useFetch('/api/posts')
</script>

<template>
  <!-- AppHeader is auto-imported from components/ -->
  <AppHeader />
  <main>
    <div v-for="post in posts" :key="post.id">
      {{ post.title }}
    </div>
  </main>
</template>
```

## Step 3: Configuration

```typescript
// nuxt.config.ts
export default defineNuxtConfig({
  devtools: { enabled: true },

  modules: [
    '@nuxt/content',     // Markdown-based CMS
    '@nuxt/image',       // Image optimisation
    '@pinia/nuxt',       // State management
  ],

  css: ['~/assets/css/main.css'],

  runtimeConfig: {
    // Server-only (not exposed to client)
    databaseUrl: process.env.DATABASE_URL,
    apiSecret: process.env.API_SECRET,

    // Public (exposed to client)
    public: {
      apiBase: process.env.NUXT_PUBLIC_API_BASE || '/api',
      siteUrl: process.env.NUXT_PUBLIC_SITE_URL || 'https://example.com',
    }
  },

  routeRules: {
    // Hybrid rendering: per-route configuration
    '/': { prerender: true },
    '/blog/**': { swr: 3600 },           // Stale-while-revalidate, 1 hour
    '/admin/**': { ssr: false },          // Client-side only
    '/api/**': { cors: true },
  },

  nitro: {
    preset: 'node-server'  // For VPS deployment
  }
})
```

## Step 4: Core Features

### Data Fetching

Nuxt provides `useFetch` and `useAsyncData` for SSR-safe data fetching:

```vue
<script setup>
const { data: post, error } = await useFetch(`/api/posts/${route.params.slug}`)

if (error.value) {
  throw createError({ statusCode: 404, message: 'Post not found' })
}
</script>
```

### Server Routes (Nitro)

Create API endpoints in `server/api/`:

```typescript
// server/api/posts.get.ts
export default defineEventHandler(async (event) => {
  const query = getQuery(event)
  const posts = await db.posts.findMany({
    take: Number(query.limit) || 10,
    orderBy: { createdAt: 'desc' }
  })
  return posts
})
```

```typescript
// server/api/posts.post.ts
export default defineEventHandler(async (event) => {
  const body = await readBody(event)
  const post = await db.posts.create({ data: body })
  return post
})
```

### Middleware

Route middleware runs before navigation:

```typescript
// middleware/auth.ts
export default defineNuxtRouteMiddleware((to, from) => {
  const { user } = useAuth()
  if (!user.value) {
    return navigateTo('/login')
  }
})
```

Apply to pages:
```vue
<script setup>
definePageMeta({
  middleware: 'auth'
})
</script>
```

### SEO

```vue
<script setup>
useHead({
  title: 'My Page Title',
  meta: [
    { name: 'description', content: 'Page description for SERP' }
  ]
})

useSeoMeta({
  ogTitle: 'My Page Title',
  ogDescription: 'Page description',
  ogImage: '/og-image.png'
})
</script>
```

## Step 5: Rendering Modes

### SSR (Server-Side Rendering) — Default

Every request renders on the server. Best for dynamic, personalised content.

```bash
npm run build
```

Output: `.output/` directory with Nitro server.

### SSG (Static Site Generation)

Pre-render all pages at build time:

```bash
npx nuxt generate
```

Output: `.output/public/` with static HTML files.

### Hybrid Rendering

Mix rendering strategies per route in `nuxt.config.ts`:

```typescript
routeRules: {
  '/': { prerender: true },              // Static at build time
  '/blog/**': { swr: 3600 },            // SSR with caching
  '/dashboard/**': { ssr: false },       // Client-side only
  '/api/**': { cache: { maxAge: 60 } },  // Cached API
}
```

## Step 6: Deploy with DeployHQ

### SSR Deployment (Node.js Server)

**Build Command:**
```bash
cd %path% && npm ci && npm run build
```

Output is `.output/` containing the Nitro server. The entry point is `.output/server/index.mjs`.

**Do NOT set a deployment subdirectory** — deploy the entire `.output/` directory.

**PM2 Ecosystem File:**

Create `ecosystem.config.cjs` in your project root:

```javascript
module.exports = {
  apps: [{
    name: 'nuxt-app',
    script: './.output/server/index.mjs',
    instances: 'max',
    exec_mode: 'cluster',
    env_production: {
      NODE_ENV: 'production',
      PORT: 3000,
      NITRO_PORT: 3000
    },
    max_memory_restart: '512M'
  }]
}
```

**Post-Deployment SSH Command:**
```bash
cd /var/www/nuxt-app && npm ci --omit=dev && pm2 reload ecosystem.config.cjs --env production
```

First deployment:
```bash
cd /var/www/nuxt-app && npm ci --omit=dev && pm2 start ecosystem.config.cjs --env production && pm2 save && pm2 startup
```

**Nginx Reverse Proxy:**
```nginx
server {
    listen 443 ssl http2;
    server_name example.com;

    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    # Static assets from Nuxt build
    location /_nuxt/ {
        alias /var/www/nuxt-app/.output/public/_nuxt/;
        expires 1y;
        add_header Cache-Control "public, immutable";
    }

    # All other requests → Nitro 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 in `.env` on the server (not committed to Git):
```bash
DATABASE_URL=postgresql://user:pass@localhost:5432/mydb
API_SECRET=your-secret
NUXT_PUBLIC_API_BASE=https://api.example.com
NUXT_PUBLIC_SITE_URL=https://example.com
```

### SSG Deployment (Static Files)

**Build Command:**
```bash
cd %path% && npm ci && npx nuxt generate
```

**Deployment Subdirectory:**
```
.output/public
```

**No PM2 needed** — just static files served by Nginx.

**Nginx Configuration:**
```nginx
server {
    listen 443 ssl http2;
    server_name example.com;

    root /var/www/nuxt-site;
    index index.html;

    location /_nuxt/ {
        expires 1y;
        add_header Cache-Control "public, immutable";
    }

    location / {
        try_files $uri $uri/ $uri/index.html /200.html;
    }

    error_page 404 /404.html;
}
```

## Step 7: Troubleshooting

### Hydration Mismatch Errors

**Symptom:** Console warnings about hydration mismatches after deployment.

**Fix:** Ensure server and client render the same content. Common causes:
- Browser extensions injecting HTML
- Date/time rendering that differs between server and client timezones
- Using `Math.random()` or `Date.now()` in components

Wrap client-only content:
```vue
<ClientOnly>
  <DynamicComponent />
</ClientOnly>
```

### Environment Variables Undefined in Production

**Symptom:** `runtimeConfig` values are `undefined` on the server.

**Fix:** Nuxt reads environment variables at build time for `public` config and at runtime for server config. Ensure:
- Public variables are prefixed with `NUXT_PUBLIC_`
- Server variables match the config key path (e.g., `NUXT_DATABASE_URL` for `runtimeConfig.databaseUrl`)
- The `.env` file exists on the server

### Build Fails with "Cannot find module"

**Fix:** Run `npm ci` (not `npm install`) to ensure exact lockfile versions. Delete `node_modules/` and `.nuxt/` if stale:
```bash
rm -rf node_modules .nuxt .output && npm ci && npm run build
```

### Port Already in Use

```bash
ss -tulnp | grep :3000
pm2 delete nuxt-app
pm2 start ecosystem.config.cjs --env production
```

## Conclusion

Nuxt 3 gives you SSR, SSG, and hybrid rendering with a single framework. DeployHQ handles the build and deployment pipeline — whether you're deploying a static marketing site or a full SSR application with API routes.

For more deployment guides, browse the [DeployHQ guides library](https://www.deployhq.com/guides).

[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).
