Before I start, I want to acknowledge my teammates at FYC Labs, Zahnen and Adolfo, as well as a post written here in June 2020 from Michael Weibel. This saved me some headaches (although I still had a few in working on this).
While working on deploying a recent NextJS converted web app, I ran into a few issues, and after additional eyes from my teammates and the article, I was able to put together a current (and I write current since this will evolve eventually) Github Action that can build a NextJS project and get it deployed to AWS Elastic Beanstalk (EB) with minimal fuss.
Let's review the steps in this example specific to a deploy to a QA/staging environment.
We want to Gzip the latest NextJS build after all approved pull requests are merged into QA.
We want to increment the version for our latest deployment.
We want the app to work.
For brevity's sake I'll note why:
AWS Beanstalk
EB is a tool that makes it easy to deploy and scale applications. Here is the trick, there's not a lot of customization. You choose the platform, zip up your code and press a button.
While this is effective in allowing AWS to handle:
- App health monitoring (complete with downloadable logs)
- Capacity Provisioning
- Load Balancing
- Security Updates
This isn't good if you have customization or need to live debug the deployment.
Github Actions
What we want to do, since we are depending on another resource to manage scale and capacity, we should go ahead and employ CI/CD to take care of the deployments for us.
When running through this, you should of course have already setup your EB Instance and be able to click into the interface to monitor the health of the app (you will be able to in Github Actions too, but using the EB interface does offer some live health updates that Github Actions may not express.
At the core of this post is the Github Action, this example assumes you have a QA branch.
name: QA Deploy
on:
push:
branches:
- "qa"
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: "14"
- name: Generate build number
id: buildnumber
uses: einaregilsson/build-number@v3
with:
token: ${{secrets.github_token}}
- name: Generate deployment package
run: npm run next-install && npm run generate-zip
- name: Deploy to EB
uses: einaregilsson/beanstalk-deploy@v18
with:
aws_access_key: ${{ secrets.AWS_ACCESS_KEY }}
aws_secret_key: ${{ secrets.AWS_SECRET }}
application_name: name-of-your-project
environment_name: name-of-your-project-staging
version_label: name-of-your-project-${{ steps.buildnumber.outputs.build_number }}
region: us-east-1
deployment_package: deploy.zip
This yaml file includes two nice Github action packages from Einar Egilsson: one is a simple build number provider and the other is his beanstalk-deploy, which is great for this exercise. If you'd rather use semver, I plan to update this article in the future with a way to do this. But for this, let's name our deployments with simple English. The version will read like this in the EB application detail view: name-of-your-project-1 (then 2, 3, etc.)
Scripts to Build and GZIP the Build
You'll notice two scripts in the generate deployment package, one installs and builds the project, the other zips the final build. I recommend that you create a scripts
folder with these sh
scripts in them:
scripts
- generate-zip.sh
- next-install.sh
In our next-install
script:
rm -rf .next && npm install && npm run build
We want to be sure that we are generating a fresh next build, removing the build directory before the new build should overwrite errors that may have appeared in previous builds.
Placing these commands in script files cleans up the deploy script and ensures sound organization and steps to the process.
Here is the generate-zip
script:
zip deploy.zip -r .next package.json next.config.js public .npmrc
The reason we are including the public folder is so that the build has access to the assets that you have placed inside of it.
The .npmrc
file is in here in case you have any private or paid packages that you need to provide the auth for. If you don't the EB won't install those packages and your deployment will break.
If you have additional configs from other plugins that you are using, include those in the script so they can be compressed as well.
Next Steps and Notes
For troubleshooting, try building locally and running next start
. Then try a manual deployment by clicking upload in the EB application detail interface.
Make sure that your package.json
provides a node version:
"engines": {
"node": "14"
},
Also, in your package.json
make sure that you have the scripts you need in the action setup, and set the port to 8080
for next start
"build": "next build",
"start": "next start -p 8080",
"lint": "next lint",
"generate-zip": "sh ./scripts/generate-zip.sh",
"next-install": "sh ./scripts/next-install.sh"
This was brief, sorry!
This works for a sound CI/CD process. In this example we used qa
, but if you need this for a production deployment on a main
branch, simply copy the yaml file and paste it into a productionDeploy.yml
or however you name your production deployments. You would only need to change the environment name and the branch you are pushing to.
Again, this was meant as a brief intro to this, but one that hopefully prevents and/or won't lead to headaches when working in this unique stack.
Shoutouts (I don't know you Einar, but thanks for the GH Action Packages):
- Einar Egilsson
- Michael Weibel
- My teammates Zahnen and Adolfo!
Top comments (1)
Hi Phillip. Great write-up.
If you are pushing (deploy.zip) with .next folder. I'm trying to understand the need to re-build it, and does it build? You can simply run the app after unzip.
Rupesh