Setting the stage
It’s a warm summer evening, circa 600 B.C. 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. 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.
For deploy I would ssh to the instance (I did have an alias for ssh, so that’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 a restart, so you had to thread lightly and be very understanding and considerate. This approach has the allure of paying tribute to the 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 basis. 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!
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.
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 to take and the easiest thing to do.
The 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.
The downside of this is that you don’t have anything, but on the plus side, it’s really easy to count the recipes they provided. While quarantine is in effect, the supermarket looks like a street. I had the same chills down my spine, and I felt the same cold sweats as back in the day while exploring Google+ (remember that?).
To add insult to injury, the recipes are in pretty bad shape, so I had to tweak some of them to make it work. Also, the Chef team happily made versions 13 and 14 which are not even mentioned in the Amazon documentation, that’s always a good sign! I’ve spent two days in anguish and grief trying 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 victory 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 the “most popular frameworks” list, but that is how I think of it). In hindsight, maybe I should have paid attention to that survey they do.
Taking that into account and the 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’s expertise and skill this would take me a while and would seriously cut into my drinking time. I’ve decided to figure out if I can host this on my new sweetheart Amplify.I knew I could 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 a universal Nuxt.js app hosted on Amplify. Here is an image for all you doubting Thomas out there.
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.
- Install Amplify CLI
- 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"
]
}
- 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 into nuxt.config.js in 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
That is it. Congratulations!
For ENV variables, they are available on build (generate) time. If you need them in the 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, it showed 109 GB of bandwidth used. It turns out caching is set to be nonexisting by default.
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'
After redeployment, it will be cached as shown.
Don’t worry about the “x-cache: RefreshHit from cloudfront” and “x-cache: Miss from cloudfront” headers. It is an implementation detail, but the Amplify team 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 a hash added if you are using the responsive loader (and you should be).
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 that’s not my problem and it sounds like at least 5 minutes of work to figure it 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 happenedit 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!