How To Deploy A Nuxt.js SSR App To An AWS EC2 Instance

Deploy Nuxt.js on Amazon EC2

While Nuxt.js has lots of different features, one of its great features is Server Side Rendering (SSR), which eliminates a lot of issues with SEO.

But to use this feature, you need to also use the built-in Node.js server, which requires a different kind of production setup than that of a static site or a Single Page Application (SPA).

In this post, I will show how to deploy a Nuxt.js server-side rendered App on an AWS EC2 instance. Let's get started.

Step 1. Create the EC2 Instance

Follow these steps to create a new Ubuntu 20.04 server instance on AWS EC2.

  1. Sign in to the AWS Management Console.

    If you don't have an account yet click the "Create a Free Account" button and complete the registration process.

  2. Open the EC2 console page, and

  3. Click the "Launch Instance" button.

  4. On the Choose AMI page, Check the "Free tier only" checkbox on the left side of the page, enter "20.04" in the search box and hit enter, then click the Select button next to "Ubuntu Server 20.04 LTS (HVM), SSD Volume Type".

    AWS EC2 - Launch Instance - Choose AMI
  5. On the Choose Instance Type page, select the "t2.micro (Free tier eligible)" instance type and click the "Configure Security Group" in the top menu.

    AWS EC2 - Launch Instance - Choose Instance Type
  6. On the Configure Security Group page, click the Add Rule button twice and add two new rules to allow HTTP and HTTPS traffic, then click the "Review and Launch" button.

    AWS EC2 - Launch Instance - Configure Security Group
  7. On the Review page, click the Launch button at the bottom of the page.

  8. In the popup that appears, select "Create a new key pair", enter a name for the key pair (e.g. "aws-secret-server-key"), and click the "Download Key Pair" button to download the private key. You will need this key to connect to the server/instance via SSH.

    AWS EC2 - Launch Instance - Select an existing key pair or create a new key pair
  9. Click the "Launch Instances" button, then scroll to the bottom of the page and click "View Instances" to see details of the new Ubuntu EC2 instance that is launching.

  10. Make note of the new Instance ID, we will need it shortly.

Step 2. Allocate/Assign An Elastic IP Address

We need to assign an elastic IP to our instance, to avoid having to update our DNS record every time the instance is restarted.

  1. From the EC2 Instances page, scroll down (if necessary) on the left sidebar and select Elastic IPs

  2. On the Elastic IP addresses page, click the Allocate Elastic IP address button on the top right side of the page.

    AWS EC2 - Elastic IPs
  3. On the Allocate Elastic IP address page, simply click the Allocate button at the end of the page.

    AWS EC2 - Allocate Elastic IP address
  4. With the just allocated IP address selected on the next page, expand the Actions dropdown button and choose Associate Elastic IP address.

    AWS EC2 - Associate Elastic IP address
  5. On the next page choose the Instance ID we launched in step 1 in the Instance field and choose the instance's Private IP from the Private IP address field.

  6. Click the Associate button at the end of the page.

    AWS EC2 - Associate Elastic IP address

Step 3. Update Your DNS Record

Log in to your domain's DNS manager, and add an A record with your elastic IP address. The steps for adding a DNS record vary depending on the DNS provider. The image below shows how to add an A record on Cloudflare. It should give you an idea of how to add it to your own DNS provider.

Cloudflare add DNS A record

Step 4. Connect to the EC2 instance

Follow these steps to connect to your EC2 instance via SSH:

  1. From the EC2 Instances page, select the instance we just launched - by selecting the checkbox beside it.

  2. Expand the Actions dropdown menu at the top right corner of the page, and choose the Connect option.

    AWS EC2 - Instances
  3. Select the SSH Client tab and follow the instructions shown on the page.

    AWS EC2 - Connect to the instance
  4. Open up your terminal and paste the example ssh command given. Be sure to update the private key file path to match where the private key you downloaded in step 1 is located.

    For example, if your private key is located in your desktop folder;

    You first need to run;

    chmod 400 ~/Desktop/aws-secret-server-key.pem

    Then connect to the instance via ssh;

     ssh -i ~/Desktop/aws-secret-server-key.pem [email protected]

    Be sure to replace xxx-xx-xxx-xx with your Elastic IP address created in step 2.

    When connecting for the first time, you will be asked to confirm that you want to continue connecting. Type yes and you are good to go.

Step 5. Set Up a Basic Firewall

Ubuntu 20.04 servers can use the UFW firewall to make sure only connections to certain services are allowed. We can set up a basic firewall using this application.

Applications can register their profiles with UFW upon installation. These profiles allow UFW to manage these applications by name. OpenSSH, the service allowing us to connect to our server now, has a profile registered with UFW.

You can see this by typing:

sudo ufw app list
sudo ufw app list

We need to make sure that the firewall allows SSH connections so that we can log back in next time. We can allow these connections by typing:

sudo ufw allow OpenSSH
sudo ufw allow OpenSSH

Then, we can enable the firewall by typing:

sudo ufw enable

Type y and press ENTER to proceed.

sudo ufw enable

You can verify that SSH connections are still allowed by typing:

sudo ufw status
sudo ufw status

As the firewall is currently blocking all connections except for SSH, when we install and configure additional services like Nginx, we will need to adjust the firewall settings to allow traffic in, as you will see below.

Step 6. Install Node.js.

From your home directory on the EC2 instance, use curl to retrieve the installation script for your preferred version of Node.js, making sure to replace 14.x with your preferred version string (if you want to install a different version).

curl -sL https://deb.nodesource.com/setup_14.x | sudo -E bash -

Then install Node.js using apt-get;

sudo apt-get install -y nodejs

Step 7. Create the server Root Directory

Let's create a new directory where we will serve our app from in our server.

Again, from your home directory, run the following command. You should of course to change nuxt-ssr to a name that makes sense for your situation.

mkdir nuxt-ssr

Now exit out of the remote ssh server for now by typing;

exit

Step 8. Build And Upload The Nuxt.js Bundle.

For demonstration, I've created a very simple Nuxt.js SSR app that uses the JSONPlaceholder API to display dummy posts;

Demo App

You can access the source code of this app here.

Now let's build the app for production on our local machine by running;

npm install

npm run build

You can certainly build the app in the production server but it's not recommended because it consumes lots of memory and causes up to a minute of downtime.

Once the build process is complete, copy package.jsonnuxt.config.js, and the .nuxt directory to the EC2 server instance. Now there are a couple of ways you can do this (like using an SFTP client, etc). But for simplicity, I prefer to use the scp command below on the terminal. Be sure to replace ~/Desktop/aws-secret-server-key.pem with the path where the private key you downloaded in step 1 is located, replace xxx-xx-xxx-xxx with the Elastic IP address you created in step 2, and replace nuxt-ssr with the name of the folder you created in step 7;

scp -i ~/Desktop/aws-secret-server-key.pem -r .nuxt package.json nuxt.config.js [email protected]:nuxt-ssr

Once the upload is complete, connect to the EC2 instance again via ssh;

ssh -i ~/Desktop/aws-secret-server-key.pem [email protected]

Change directory to the folder you created in step 7 and install production dependencies:

cd nuxt-ssr

npm i -—production

This will install the dependencies you need to run the Nuxt.js server.

Step 9. Install And Configure PM2

PM2 is a process manager for node.js, that will help us manage, restart and keep our application online.

To install PM2, run the following command;

sudo npm install pm2 -g

Create the pm2.config.js file

Notice the pm2.config.js file at the root of my demo project above. This is a special file that will tell pm2 how to start and run the app. Create this file at the root of your project folder. The content of the file for the demo app above looks like this;

module.exports = {
  apps : [{
    name   : "Nuxt SSR",
    script : "npm",
    args: "start",
    env_production: {
      PORT: 3000,
      NODE_ENV: "production"
    }
  }]
}

Finally, for pm2 setup, run the command;

pm2 save && pm2 startup

This will allow your processes to recover whenever the server restarts.

Step 10. Install And Configure Nginx

To install Nginx, run the following command:

sudo apt-get update

sudo apt-get install nginx

Adjust the Firewall To Allow Nginx HTTP

Before we can use Nginx, we need to reconfigure our firewall software to allow access to the service. Nginx registers itself as a service with ufw, our firewall, upon installation. This makes it easy to allow Nginx access on the Firewall.

We can list the applications configurations that ufw knows about by typing:

sudo ufw app list

You should get a listing like below:

sudo ufw app list

As you can see, there are three profiles available for Nginx:

  • Nginx Full: This profile opens both port 80 (normal, unencrypted web traffic) and port 443 (TLS/SSL encrypted traffic)

  • Nginx HTTP: This profile opens only port 80 (normal, unencrypted web traffic)

  • Nginx HTTPS: This profile opens only port 443 (TLS/SSL encrypted traffic)

It is recommended that you enable the most restrictive profile that will still allow the traffic you’ve configured. Since we haven’t configured SSL for our server yet, let's only allow traffic on port 80.

You can do this by running the following command:

sudo ufw allow 'Nginx HTTP'

You can verify the change by typing:

sudo ufw status

You should see HTTP traffic allowed in the displayed output:

sudo ufw status

To confirm that the Nginx server is running, type the following command and hit ENTER;

systemctl status nginx
systemctl status nginx

Setup Nginx Reverse Proxy

Let's create a new Nginx virtual host file for our domain using the nano text editor. You should replace nuxt-ssr-example.com with your actual domain.

sudo nano /etc/nginx/sites-available/nuxt-ssr-example.com

The example below should give you an idea of how the configuration could look. Again, you should replace nuxt-ssr-example.com with your actual domain name, and /home/ubuntu/nuxt-ssr with the path where your project is located.

server {
  listen 80;

  # Your domain
  server_name nuxt-ssr-example.com;

  # Proxy to nuxt renderer.
  location / {
	 proxy_pass http://localhost:3000;
  }

  # Serve nuxt bundle with max cache life.
  location ~^\/_nuxt(.*)$ {
    alias /home/ubuntu/nuxt-ssr/.nuxt/dist/client/$1;
    gzip on;
    gzip_comp_level 6;
    gzip_vary on;
    gzip_types text/css application/json application/javascript text/javascript application/x-font-ttf font/opentype;
    expires max;
  }

  # Serve static content
  location ~* \.(js|jpg|jpeg|txt|png|css|pdf|ico|map)$ {
    gzip_static on;
    expires 30d;
  }

  # Redirect from /path/ to /path
  rewrite ^/(.*)/$ /$1 permanent;
}

# Redirect domain aliases
server {
  server_name www.nuxt-ssr-example.com;
  return 301 https://$host$request_uri;
}

To activate the new virtual host configuration file, create a symbolic link to it in sites-enabled:

sudo ln -s /etc/nginx/sites-available/nuxt-ssr-example.com /etc/nginx/sites-enabled/

To confirm that the configuration doesn’t contain any syntax errors, you can run:

sudo nginx -t

You should see an output like this:

sudo nginx -t

Now to apply the changes, reload Nginx with:

sudo systemctl reload nginx

You will need to run this command every time you modify this file or any other Nginx configuration file.

Step 11. Start The Nuxt.js Server

Change directory to your project root folder and start up the Nuxt.js server by running;

pm2 start pm2.config.js –-env production

And your site should now be up at your domain; http://nuxt-ssr-example.com.

Step 12. Install An SSL Certificate

Now let's install an SSL certificate using Let’s Encrypt's Certbot software.

Install certbot and its Nginx plugin using apt:

sudo apt install certbot python3-certbot-nginx

Certbot is now ready for use, but in order for it to configure SSL for Nginx, we need to allow HTTPS through the firewall.

Allowing HTTPS Through the Firewall

To let in HTTPS traffic through our firewall, we can allow the Nginx Full profile and then delete the Nginx HTTP profile allowance:

Run the following command to do that;

sudo ufw allow 'Nginx Full'
sudo ufw delete allow 'Nginx HTTP'

Your status should now look like this:

sudo ufw status
sudo ufw status

We’re now ready to run Certbot and install our certificates.

Obtaining an SSL Certificate

To obtain an SSL certificate for the domains nuxt-ssr-example.com and www.nuxt-ssr-example.com, run;

sudo certbot --nginx -d nuxt-ssr-example.com -d www.nuxt-ssr-example.com

If this is your first time running certbot, you will be prompted to enter an email address and agree to the terms of service. After that, certbot will run a challenge to verify that you control the domain you’re requesting a certificate for.

If successful, certbot will ask how you’d like to configure your HTTPS settings.

Certbot redirect choices

Make a choice by entering either 1 or 2 and hitting ENTER. The configuration will be updated, and Nginx will reload to pick up the new configuration. certbot will wrap up with a message informing you that the process was successful and where your certificates are stored:

Certbot Congratulations

Your SSL certificates are now ready. We can test it out by prefixing our domain with https:// (e.g https://nuxt-ssr-example.com) and now our browser’s security indicator should show that our site is properly secured, usually with a green lock icon.

And that's it. If you have any questions feel free to drop them in the comment section below.

Also, consider following me on Twitter @nzesalem. ✌🏽