Setting up Nuxt.js and Strapi applications and deploying them to the server

Nuxt.js is a popular framework that enhances the capabilities of Vue.js for building websites. It provides developers with guidelines and restrictions to simplify the website creation process. It is particularly well-suited for creating content-rich websites such as company websites or blogs. By leveraging the features of Vue.js, Nuxt.js enables developers to build dynamic and engaging web experiences with ease.

Strapi is an open-source headless CMS built on Node.js, designed for efficient content modelling and management. It offers a user-friendly interface, allowing developers to define and structure content types easily. With its headless architecture, Strapi separates the content management system from the presentation layer, enabling flexible API consumption. It is a customizable CMS focused on content types, making it ideal for content-driven applications and websites.

PM2 is an open-source process manager for Node.js applications that simplifies management and deployment. It offers process monitoring, automatic restarts, load balancing, and log management. With PM2, developers can easily manage their Node.js applications in production.

Setting up the project

Firstly, we have to set up both Nuxt.js and Strapi, which will be referred to as frontend and backend.

# setting up Nuxt.js

mkdir nuxt-strapi
npx nuxi init frontend
cd frontend
yarn install

# setting up Strapi
yarn create strapi-app backend

After this, we will configure PM2 to handle the management of both Nuxt.js and Strapi. This setup will provide a centralized solution for managing the environment variables as well.

ecosystem.config.js:

const env = process.argv[process.argv.indexOf('--env') + 1];
const isDev = env === 'dev';

module.exports = {
  apps: [
    // Frontend - Nuxt 3
    {
      name: 'frontend',
      cwd: './frontend', // Adjust the path to your frontend project directory
      append_env_to_name: true,
      script: 'yarn',
      args: isDev ? 'dev' : 'start',
      interpreter: 'none',
      env_dev: {
        NODE_ENV: 'development',
        HOST: '0.0.0.0',
        STRAPI_URL: 'http://127.0.0.1:1337',
        PORT: 3000, // Choose the port you want to use for your frontend
      },
      watch: false,
      max_memory_restart: '1G',
      instances: 1, // Only one instance
      hooks: {
        pre_start: {
          command: 'yarn',
          args: 'build', // Build the frontend before starting
        },
      },
    },
    // Backend - Strapi
    {
      append_env_to_name: true,
      name: 'backend',
      cwd: './backend', 
      script: 'yarn',
      args: isDev ? 'develop' : 'start',
      interpreter: 'none',
      env_dev: {
        NODE_ENV: 'development',
        HOST: '0.0.0.0',
        PORT: '1337',
        URL: 'http://127.0.0.1:1337',
        APP_KEYS: "toBeModified1,toBeModified2",
        API_TOKEN_SALT: 'tobemodified',
        ADMIN_JWT_SECRET: 'tobemodified',
        JWT_SECRET: 'tobemodified',
        DATABASE_CLIENT: 'sqlite',
        DATABASE_FILENAME: './tmp/data.db'
      },
      watch: false,
      max_memory_restart: '1G',
      instances: 1, // Only one instance
      hooks: {
        pre_start: {
          command: 'yarn',
          args: 'build', // Build the backend before starting
        },
      },
    },
  ],
};

Then, we can use the following command to start both the frontend and the backend.

pm2 start ecosystem.config.js --env dev
PM2 - Ecosystem File
Advanced process manager for production Node.js applications. Load balancer, logs facility, startup script, micro service management, at a glance.

To let the Nuxt.js interact with Strapi, we can add @nuxtjs/strapi dependency.

yarn add --dev @nuxtjs/strapi
Setup · Nuxt Strapi
Learn how to setup strapi module in your Nuxt 3 application.

Loading the .env file into the PM2 configuration

Although PM2 provides a nice unified entrypoint of managing the env variables and the applications, it is currently not possible to load environment variables directly from the .env file. Therefore I wrote an npm package to resolve this problem.

pm2-dotenv
A package to load environment variables from .env files for PM2 ecosystem.config.js. Latest version: 0.3.0, last published: 2 days ago. Start using pm2-dotenv in your project by running `npm i pm2-dotenv`. There are no other projects in the npm registry using pm2-dotenv.

Modify the ecosystem.config.js:

const { injectEnvs } = require('pm2-dotenv')

module.exports = {
    apps: [
        {
            name: 'frontend',
            
            // add this line
            ...injectEnvs('frontend')
        },
        {
            name: 'backend',
            ...injectEnvs('backend')
        }
    ]
}

Now you can place all the variables in a .env.production file and let pm2 loads them:

FRONTEND_NODE_ENV='production'
FRONTEND_HOST='0.0.0.0'
FRONTEND_STRAPI_URL='http://127.0.0.1:1337'
FRONTEND_PORT=3000

BACKEND_NODE_ENV='production'
BACKEND_HOST='0.0.0.0'
BACKEND_PORT='1337'
BACKEND_URL='http://127.0.0.1:1337'
BACKEND_APP_KEYS="toBeModified1toBeModified2"
BACKEND_API_TOKEN_SALT='tobemodified'
BACKEND_ADMIN_JWT_SECRET='tobemodified'
BACKEND_JWT_SECRET='tobemodified'
BACKEND_DATABASE_CLIENT='sqlite'
BACKEND_DATABASE_FILENAME='./tmp/data.db'

Note that you may change the database connection from SQLite to MySQL. However, you might have to modify the config/database.js file in the Strapi project.


const path = require('path');

module.exports = ({ env }) => {
  const client = env('DATABASE_CLIENT', 'sqlite');

  const connections = {
    mysql: {
      connection: {
        connectionString: env('DATABASE_URL'),
        host: env('DATABASE_HOST', 'localhost'),
        port: env.int('DATABASE_PORT', 3306),
        database: env('DATABASE_NAME', 'strapi'),
        user: env('DATABASE_USERNAME', 'strapi'),
        password: env('DATABASE_PASSWORD', 'strapi'),
        ssl: env.bool('DATABASE_SSL', false) && {
          key: env('DATABASE_SSL_KEY', undefined),
          cert: env('DATABASE_SSL_CERT', undefined),
          ca: env('DATABASE_SSL_CA', undefined),
          capath: env('DATABASE_SSL_CAPATH', undefined),
          cipher: env('DATABASE_SSL_CIPHER', undefined),
          rejectUnauthorized: env.bool(
            'DATABASE_SSL_REJECT_UNAUTHORIZED',
            true
          ),
        },
      },
      pool: { min: env.int('DATABASE_POOL_MIN', 2), max: env.int('DATABASE_POOL_MAX', 10) },
    },
    sqlite: {
      connection: {
        filename: path.join(
          __dirname,
          '..',
          env('DATABASE_FILENAME', './tmp/data.db')
        ),
      },
      useNullAsDefault: true,
    },
  };

  return {
    connection: {
      client,
      ...connections[client],
      acquireConnectionTimeout: env.int('DATABASE_CONNECTION_TIMEOUT', 60000),
    },
  };
};

Also, you might have to change the config/server.js:

module.exports = ({ env }) => ({
  host: env('HOST', '0.0.0.0'),
  port: env.int('PORT', 1337),
  url: env('URL', 'http://0.0.0.0:1337'),
  app: {
    keys: env.array('APP_KEYS'),
  },
  webhooks: {
    populateRelations: env.bool('WEBHOOKS_POPULATE_RELATIONS', false),
  },
});

Finally, start the processes:

pm2 start ecosystem.config.js --env production

Use Nginx for reverse proxy

When all is set, the final step is to use nginx to proxy the request. You may add the following configuration to your existing nginx config. Note that in this example we put the Strapi to the /api path.

upstream strapi {
    server localhost:1337;
}

upstream nuxt {
    server localhost:3000;
}

server {
    listen 80;

    gzip            on;
    gzip_types      text/plain application/xml text/css application/javascript;
    gzip_min_length 1000;

    # Static Root
    location / {
        expires $expires;
        proxy_redirect                      off;
        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;
        proxy_read_timeout          1m;
        proxy_connect_timeout       1m;
        proxy_pass                          http://nuxt;
    }

    # Strapi API and Admin
    location /api/ {
        rewrite ^/api/?(.*)$ /$1 break;
        proxy_pass http://strapi;
        proxy_http_version 1.1;
        proxy_set_header X-Forwarded-Host $host;
        proxy_set_header X-Forwarded-Server $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;
        proxy_set_header Host $http_host;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "Upgrade";
        proxy_pass_request_headers on;
    }
}