Author Image
by Kresimir Bojcic
Apr 21st 2020
Hosting Nuxt.js SSR on Amplify
Tags: Nuxt.js, Amplify, Server Side Rendering

Setting the stage

It's a warm summer evening, circa 600 BC. You've finished your shopping at the local market, or agora... and you look up at the night sky...

So anyways, last year we switched from our trusted Rails to Nuxt.js for company landing pages. Because I am very busy (mostly drinking) I had the most brilliant "budget" setup on our EC2 instance. I (single-handedly) installed Nginx, PM2, Git and Node. I also did Let's Encrypt bullshit dance that I feel too shaken to talk about. I'll pretend that it just worked and that they did not make arbitrary changes that would break update calls and then block me for a week for "throttling" reasons because I neglected to read their update info from that morning.

alt text

For deploy I would ssh to instance (I did have alias for ssh so it's cool) and then I would run this piece of code artistry:

cd www
cd kodiusweb
git pull 
npm install
npm run build
pm2 restart 0

On a reboot I would manually do:

pm2 start npm -- start

Don't judge! If it works it's not stupid 😓 It had a few hiccups, it did not work after restart so you had to thread lightly and be very understanding and considerate. This approach has allure of giving tribute to old school while making me a better person at the same time. What's not to like?

It was wonderful, a true bliss... but then my bastard team started harassing me on a daily bases. Like it was my fault that we have manual setup and that only I had access to that particular EC2 instance at the time. I mean, come on!

job harassment

You get the gist even if it's in Croatian and I am supposed to be the boss! This was very devastating to my ego and self confidence.

ruined day

Chef 12 to the rescue

This was too much, too fast! Enough is enough, so I pulled out my silk coding gloves and decided to automate our deploy pipeline. Our old setup was Amazon OpsWorks/Chef 11 recipes for Rails deploy. It was bulky but it did work and I had custom made recipes to allow for Let's Encrypt certs bullshit. Staying on OpsWorks and reusing Chef seemed like a logical step and easiest thing to do.

Only teeny-tiny issue with this was that Amazon killed OpsWorks while I was not looking. For new Ubuntu versions (I mean 2018 "new") you had to work with Chef 12. They provided exactly 0 (zero) recipes for this "new" Chef version.

Downside of this is that you don't have anything, but on a plus side it's really easy to count recipes they provided. Chef recipes supermarket looks like streets while quarantine is in effect. I had the same chills down my spine and I felt the same cold sweats like back in the day while exploring Google+ (remember that?).

nobody there

To add insult to injury, recipes are in pretty bad shape so I had to tweak some of them to make it work. Also Chef team happily made version 13 and 14 that are not even mentioned on Amazon documentation, that's always a good sign! I've spend two days in anguish and grief to get my custom chef recipe to:

  • install Node.js
  • install PM2 and Nuxt.js
  • install Nginx
  • pull from private Github repo
  • use OpsWorks lifecycle hooks and app integration
  • build Nuxt.js site there
  • host on port 3000

If someone is interested I can share a repo (Hello! Is anybody reading this?). It was a true win of will over matter. Chef is notoriously hard to debug and I know nothing about it so it was very, very time consuming. After two days I got the hang of it, but it got me thinking.

If Amazon silently killed OpsWorks, what are they pushing today? Where did everybody go? Why am I still here standing in the middle of nowhere? Is this high school all over again?

Then I found out (husband always knows the last) that Chef was winner of "most dead framework" in Stack Overflow 2019 survey (Technically it was second to last on "most popular frameworks" list, but that is how I think of it). In hindsight maybe I should have payed attention to that survey they do.

Taking that in account and fact that I was still missing:

  • Let's Encrypt setup (Yay!)
  • Github deploy on commit pipeline
  • Nginx awesome config script
  • Restarting scenarios
  • Zero downtime deployments
  • Email on successful deploy
  • Rollback on failed deploy + Email

I got a tad worried. Considering my Chef expertise and skill this would take me a while and would seriously cut into my drinking time. I've decided figure out if I can host this on my new sweetheart Amplify. I knew I can do it for statically generated and single page apps, but I was unsure about server side rendering.

It turns out you can't ("build" does not generate /dist folder needed), but if you run "generate" you get all that you need and everything works as intended from your SSR(universal) Nuxt.js app. Technically this is static site generation (SSG), but still I call this a win. Proof that it works is that you are reading this as this is universal Nuxt.js app hosted on Amplify. Here is an image for all you doubting Thomas out there.

universal app

Amplify and Nuxt.js SSR

Now that I figured that out all by myself, saved the day and am already regretting writing about it as it is turning out to be tedious, you still want me to show you how? Damnit. Just a reminder that you can always man up and hire us for top $ to do the heavy lifting. No? Ok then.

Good news is, it takes less then half an hour. Bad news is I am not going to get in details like in my Gridsome/Directus blog post debacle that is boring even for me to read.

If you are struggling to force yourself to do it, here is a list of pros and cons, because I value your time.

PROS CONS
No Let's Encrypt 🎉 "Managed" Hosting
No Nginx Installing Amplify CLI
Cheap AF
Zero downtime deploy
Github integration
Hosted on CDN, no need for handing assets separately
Zero installations(Node.js, PM2)
No Chef recipes
Notifications and failure rollback

Amplify also has great support for ENV variables, previews, password protecting selected branches... that we are not getting into, but basically it looks very well done. You are locked in a very opinionated setup that just works (it is very tedious for me to always do the same thing manually for the idea of freedom, not that I would do it differently anyways), so I am happy with my managed hosting prison.

Same reason I am on OSX (because it works) even if it means I need to buy a dongle here and there 😐

How to do it checklist

Only a few steps to get this to work.

  1. Install Amplify CLI
  2. Initialize Amplify in your project
    amplify init
    

Use defaults, Vue project. For source change to root "/".

You need to end up with:

//project-config.json
{
    "projectName": "kodiusweb",
    "version": "3.0",
    "frontend": "javascript",
    "javascript": {
        "framework": "vue",
        "config": {
            "SourceDir": "/",
            "DistributionDir": "dist",
            "BuildCommand": "npm run build",
            "StartCommand": "npm start"
        }
    },
    "providers": [
        "awscloudformation"
    ]
}
  1. Add Amplify plugin
//plugins/amplify.js
import Vue from 'vue'
import Amplify, * as AmplifyModules from 'aws-amplify'
import { AmplifyPlugin } from 'aws-amplify-vue'

Vue.use(AmplifyPlugin, AmplifyModules)

And plug it in nuxt.config.js with client mode

//nuxt.config.js
 plugins: [
    { src: '~/plugins/amplify.js', mode: 'client' }
  ]

You can push this to AWS with:

amplify push

After it finishes, login to https://aws.amazon.com/amplify/ and find app in your region. Open up app and go to build settings. You need to edit link 9 to be:

npm run generate

amplify yml

That is it. Congratulations!

For ENV variables, they are available on build(generate) time. If you need them in process add them in nuxt.config.js

 env: {
    nodeEnv: process.env.NODE_ENV,
    s3BucketName: process.env.S3_BUCKET_NAME
  }

Caching

After some time, I've noticed an unusually big bill for our Amplify service.
Checking it out, showed 109GB of bandwidth used. It turns out caching is set to be nonexisting by default.

amplify yml

It's easy enough to fix, add the snippet below to the amplify.yml and redeploy.

#amplify.yml
....
customHeaders:
   - pattern: '**/*'
    headers:
    - key: 'Cache-Control'
     value: 'public, max-age=31536000, immutable'

amplify yml

amplify yml

After redeploy it will be cached as shown.

amplify yml

Don't worry about "x-cache: RefreshHit from cloudfront" and "x-cache: Miss from cloudfront" headers. It is an implementation detail, but Amplify teams assures that CDN cache is working every time.

When using Nuxt.js, you can go reasonably aggressive with caching pattern matching, as each asset has hash added if you are using responsive loader (and you should).

Conclusion

Hosting Nuxt.js SSR on Amplify is a great choice especially if your traffic is not crazy (even if it's crazy I think it is very cost effective, but it's not my problem and it sounds like at least 5 minutes of work to figure out so...). On an unrelated note, sometimes I feel confident I could add clauses to give money away for the first reader that sends me a message pointing to that clause like what happened it this EULA 😁

To summarize for TL;DR

  • I hate Let's Encrypt dearly, I was already sold on just getting rid of them
  • OpsWorks is dead, unless you have to use it to host Elixir or something exotic. RIP
  • Amplify is almost free for me (let's face it, not that many people are visiting)
  • AWS did a good job with Amplify! 🍺
  • Nuxt.js SSR and not just SPA works on Amplify like a glove
  • Make sure to setup caching properly
  • You CAN have a hassle free SSL!

Friendly face

Contact Us

Name is required

Valid e-mail is required

Message is required

If you previously need to sign an NDA, email us at: info@kodius.com