Deploying a Vue and Node Application to AWS Elastic Beanstalk
Deploying a web app with Vue.js in the frontend and Node.js in the backend for the first time can be a daunting task. It required quite a bit of reading and understanding AWS specifics to resolve some of the issues I faced along the way. I took one approach of bundling the VueJS and NodeJS together and uploading it to AWS Elastic Beanstalk directly, however it turned out to be quite limiting when I started integrating AWS Cloudfront. I then took another approach which seems to serve quite well in production. I will outline both approaches below.
Let’s imagine we have a project directory called testapp. The testapp directory has two subdirectories
- client — Vue.js frontend code
- server — Nodejs backend code
First Approach
The first approach to deploy will be just packaging the Vuejs bundle along with the Nodejs and uploading it to AWS Elastic Beanstalk. The steps here will be:
- Build Vue.js project in the client directory
npm run build. // this will generate a dist directory in the client
2. Paste the dist directory inside the public directory in the server folder.
3. Make sure your nodejs server is configured to serve the static files from the public directory. Adding these lines there should do it:
4. Start the node server and check localhost:3000. You should be able to run the entire website on localhost:3000.
node app.js
5. Deploy the website on AWS Elastic Beanstalk:
- Sign up on AWS for a developer account if you don’t have one already.
- This is a great resource to understand how to deploy a Node.js application to Elastic Beanstalk.
- Once this is done, you should have an elastic beanstalk link like testapp.us-east-1.elasticbeanstalk.co) where you should be able to see the website deployed.
- Please note that the eb commands (eb init, eb deploy) need to be executed in the server folder.
6. Migrate DNS Services from your domain name provider to Route53
I had a domain that I purchased from GoDaddy and wanted my website to appear on that domain instead of the elastic beanstalk URL. For this, I decided to move my DNS services from GoDaddy to AWS Route53. I will recommend this blog that walks over this process step by step. There was one caveat here. Importing the exported Zone file didn’t just work in Route53. To resolve this:
- Find the IP address of the elastic beanstalk URL using a website like https://www.ipvoid.com/find-website-ip/
- Replace PARKED in the Zone file with the IP
- Import the Zone file and it should import successfully.
- You can change the A record to an Alias type and point it to your elastic beanstalk URL.
Another thing that didn’t work for me seamlessly was when I tried pasting the AWS nameservers in Godaddy. Apparently, Godaddy shows an unexpected error if you end the nameserver with a period. So ns-1122.awsdns-11.org. didn’t work, but pasting ns-1122.awsdns-11.org worked.
7. Configure your app for SSL
This involves getting a certificate for your domain name from AWS Certificate Manager and then enabling secure listener port 443 on Elastic Beanstalk. This article describes the entire process quite well.
The above approach works pretty well, except that I was facing issues adding AWS Cloudfront (CDN) in front of my website to lower the loading times. (If any of you figure this out, I would love to know!). Without adding CDN, the website loading speed was quite slow. This was when I figured out the second approach which also is more modular and cleaner.
Second Approach
The second approach involves deploying the client separately as a static website in AWS S3 and the server separately in Elastic Beanstalk. The sequential steps will be:
- Build Vue.js project in the client directory
npm run build // this will generate a dist directory in the client
2. Migrate DNS Services from your DNS provider to Route53 as described in the first approach. (This is needed only if you want to host at your own custom domain)
3. We will deploy the dist directory to the AWS S3 bucket as a static website. To deploy to S3, this is a great blog to follow. You can also add Cloudfront and redirect HTTP to HTTPS as mentioned in the blog. I created a deploy script called deploy.sh with the following content:
# deploy.sh
#!/bin/bash// Build Vue website
npm run build// Copy Files to S3
aws --region <aws_region> --profile <profile-name> s3 sync ./dist s3://<bucket_name> --delete// Invalidate Cloudfront
aws configure set preview.cloudfront true && aws cloudfront create-invalidation --distribution-id <distribution_id> --paths '/*'
I then add an entry in package.json to run this deploy script
// Execute deploy.sh using npm run deploy"scripts": { "deploy": "./deploy.sh",
"dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
"e2e": "node test/e2e/runner.js",
"start": "npm run dev",
"test": "npm run e2e"
},
Once all the steps are done, you should be able to see your website running at https://yourdomain.com — none of the server-side APIs will work but the static pages should all load.
3. We will then deploy the nodejs server code to Elastic Beanstalk. The approach will be similar to the steps outlined in First Approach with the following differences:
- We won’t be copying the dist folder to public folder inside server directory. We are deploying only the server code.
- Once you deploy the nodejs code to Elastic Beanstalk, you will get the elastic beanstalk environment url using which you can hit the APIs.
- We will then want to move the elastic beanstalk service to a custom domain name like api.yourdomain.com. To do this, add a CNAME record in Route53 with the following details:
Name: api.yourdomain.com
Type: CNAME
Alias: No
Value: nodeapp.us-east-2.elasticbeanstalk.com // Elastic Beanstalk Url
- Once the above is done, the client will be hosted at https://yourdomain.com and server will be hosted at http:// api.yourdomain.com. You can also configure your server for SSL by following step 7 in First Approach. Then you will be able to access your server at https://api.yourdomain.com.
4. Instruct your Vue frontend app to talk to https://api.yourdomain.com in production. To do this, I added an environment variable in prod.env.js vue config and used it in the axios base configuration
// prod.env.js'use strict'
module.exports = {
NODE_ENV: '"production"',
VUE_APP_API_URL: '"https://api.yourdomain.com"'
}// api.js
const BASE_SERVER_URL = process.env.VUE_APP_API_URL
5. Deploy the server using eb deploy in server directory and the client using npm run deploy in client directory.
Adding Cache-Control headers
On analyzing my website on Google Pagespeed Insights, I discovered that one of the issues was that Cache-Control Headers were not present in on the resources being returned from S3 which resulted in browsers not caching them and hence fetching the resources again and again. This causes higher loading times for users. To fix this, I added the following lines to my deploy script:
# deploy.sh
#!/bin/bash
npm run build aws --region <aws_region> --profile <profile_name> s3 sync ./dist s3://<bucket_name> --deleteaws s3 cp s3://<bucket_name>/ s3://<bucket_name>/ --recursive --metadata-directive REPLACE --exclude '*' --include '*.css' --cache-control max-age=86400 --content-type text/cssaws s3 cp s3://<bucket_name>/ s3://<bucket_name>/ --recursive --metadata-directive REPLACE --exclude '*' --include '*.js' --cache-control max-age=86400aws s3 cp s3://<bucket_name>/ s3://<bucket_name>/ --recursive --metadata-directive REPLACE --exclude '*' --include "*.jpg" --include "*.svg" --include "*.png" --cache-control max-age=86400aws configure set preview.cloudfront true && aws cloudfront create-invalidation --distribution-id <distribution_id> --paths '/*'
Phew! This finally completes an end-to-end working deployment of a web-app built on Vue.js and Node.js on AWS. If you find this useful or know of something that can make this process even better, please let me know in the comments.