Manage and Deploy Next.js Using Github Actions and VPS
Table of Contents
Next.js has become not only the leading framework for building React-powered web apps but also a simple way to develop and deploy JavaScript applications with less manual configuration. The developer ergonomics are great. The CLI installer sets up the whole repository with Typescript, Lint, watch mode, configuration files and other basics. There are many deployment options from fully managed to self-hosting. A fully managed deployment solution can get pricey (long running functions can easily ram up the bill) and have technical limitations.
Technical limitations include the lack of fine-grained routing control and no static IP (it is pricey). Fine-grained routing control is useful when routing parts of a domain like /blog/* to an external blog engine like Hugo. Deploying on VPS is a viable and cheap alternative. I use to run expensive API functions while the main web app is deployed on a fast and fully managed solution with global CDN (which still runs on free tier).
#
VPS to the rescue
Deploying on VPS is, even if you aren’t particular well-versed in Linux server management, not too complicated. There exists multiple tutorials and build scripts that will setup SSL encryption (Let’s Encrypt), NGinx and Node.
In this example I use the node process manager, PM2. It handles process restarts, CPU cluster mode, logging and environmental variables. Add a ecosystem.config.js
to the root of the Next.js project:
module.exports = {
apps: [
{
name: 'my-app',
exec_mode: 'cluster',
instances: 'max',
script: 'node_modules/next/dist/bin/next',
watch: false,
},
],
};
And then a command to (re)start the application to the package.json
:
"scripts": {
"restart": "pm2 startOrRestart ecosystem.config.js --env production --update-env"
}
The --update-env
ensures that environmental variables passed from Github during deployment are updated.
#
Continuous deployment using Github Actions
The last step automatizing deploying. This is done by adding a deployment script for Github Actions:
name: Deploy
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Use Node.js 18
uses: actions/setup-node@v3
with:
node-version: 18.x
- name: Run install
run: yarn install --frozen-lockfile
- name: Run build
run: yarn build
- name: Deploy using ssh
uses: appleboy/ssh-action@master
env:
YOUR_SECRET: ${{ secrets.YOUR_SECRET }}
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
key: ${{ secrets.PRIVATE_KEY }}
port: 22
script: |
cd /home/user/web-app
git pull origin main
rm -rf .next
yarn install --frozen-lockfile
yarn build
YOUR_SECRET=${{ env.YOUR_SECRET }} yarn restart
The deployment script runs whenever the main
branch is updated. It builds the application to detect builds errors before trying to deploy. It adds the secret environmental variable YOUR_SECRET
managed by Github on the repository settings page.
An alternative deployment method would be to sync the build files from Github Actions using SCP to avoid re-building it on the deployed VPS. This would also make it more efficient and fast. This can easily be used to deploy to multiple servers depending on setup.