🟢
Hosting

Deploying a Nuxt App: SSR, Static Generation, PM2 and Nginx

06.11.2026
← All articles

Nuxt is a Vue-based framework that can produce either a server-rendered dynamic site or a fully static one from the same code. Decide before deploying, because the choice determines the server you need. Fresh data on every request, user logins or a server API call for a VPS with Node.js. Mostly unchanging pages can be generated as static HTML and placed on ordinary hosting.

The build output: .output

Nuxt 3 and later use the Nitro server engine. npm run build creates a self-contained .output folder with a server part and a public part. The server does not need the source code or the full node_modules, because Nitro bundles what it needs.

npm ci
npm run build
node .output/server/index.mjs

It listens on port 3000 by default; change it with PORT/HOST or NITRO_PORT/NITRO_HOST. Behind Nginx, bind to 127.0.0.1.

SSR on a VPS with PM2

Install an LTS Node.js, clone with Git and build. If a small server runs out of memory during the build, add swap or build locally on a machine with the same OS and architecture and upload only .output. Then run it under PM2, following the ecosystem example from the Nuxt docs.

// ecosystem.config.cjs
module.exports = {
  apps: [{
    name: "misol-nuxt",
    port: "3000",
    exec_mode: "cluster",
    instances: "max",
    script: "./.output/server/index.mjs",
    env: {
      HOST: "127.0.0.1",
      NUXT_PUBLIC_API_BASE: "https://api.example.uz"
    }
  }]
};
npm install -g pm2
pm2 start ecosystem.config.cjs
pm2 save
pm2 startup

In cluster mode never keep sessions or caches in process memory; set instances to 1 if your app is not ready for that.

Runtime config without rebuilding

Values in runtimeConfig can be overridden at runtime by environment variables prefixed with NUXT_, or NUXT_PUBLIC_ for browser-visible values. Changing an API URL or secret only needs a PM2 restart, not a rebuild. Never put secrets under public, because everything there ends up in the browser.

// nuxt.config.ts
export default defineNuxtConfig({
  runtimeConfig: {
    apiSecret: "",
    public: { apiBase: "" }
  }
});

Nginx in front

server {
    listen 80;
    server_name example.uz;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}
sudo nginx -t && sudo systemctl reload nginx
sudo certbot --nginx -d example.uz

Nginx terminates SSL with a free Let's Encrypt certificate from Certbot and forwards traffic to Nuxt.

Static generation: hosting without Node.js

nuxi generate pre-renders every page into HTML under .output/public, which you can upload to any web server, including hosting with an ISPmanager panel. No Node.js process is needed, so it uses few resources and loads fast. Dynamic routes the build cannot discover are not generated and may return 404 when opened directly; list them in nitro.prerender.routes.

npx nuxi generate
ls .output/public

Example: a hotel in Bukhara

A small hotel in Bukhara had misol.uz built in Nuxt. Room descriptions, a gallery and a contact page rarely change, so the first version was generated with nuxi generate and uploaded to ordinary hosting. When online booking and live room availability were added later, the project moved to a VPS in SSR mode with PM2 and Nginx as above. The code barely changed; only the build command and hosting did.

When unsure, start simple: sayt.uz hosting is enough for a static site, and you can move to a VPS once dynamic features appear.

Related articles

🐘 What's New in PHP 8.4: Property Hooks, Asymmetric Visibility and More 📧 How to Create and Set Up a Mailbox in ISPmanager 🚀 Zero-downtime deploys with release folders and a symlink switch 🔀 Managing Node.js Versions with nvm: Install, Switch and .nvmrc
🌐 Language
🇺🇿 O'zbek 🇺🇿 Ўзбек 🇷🇺 Русский 🇬🇧 English ✓