Deploy a Next.JS App With Serverless to AWS Lambda

Why Not Vercel

Vercel is amazing and with a single command can deploy your entire site. However due to changes there pretty severe limits on the amount of serverless functions you can deploy. That limit on the free is 12. That seems fine until you realize that pages with getInitialProps will be counted.

Enter Serverless

Serverless is not just the name of a movement towards a serverless architecture but Serverless is also a company https://www.serverless.com. They provide a way to deploy to any of the cloud providers without having to know all the ins-and-outs of each the cloud provider.

They created a Next.JS Serverless component which you can check out here https://github.com/serverless-nextjs/serverless-next.js . This will deploy your Next.js application to AWS Lambda@Edge.

Meaning with Lambda@Edge your site, content, and APIs will all be served as close to the requesting user as possible. All powered using CloudFront and Lambdas.

They do have full support yet but they support 95% of Next.js Feature set.

The Config

Before we jump into config lets focus on what should be capable. Typically when deploying you may need two or more environments. Staging, production, or maybe one off feature branches. In order to accomplish this we will need to do a little setup.

First in your Next.js App install yarn add @sls-next/serverless-component@1.17.0 or whatever the latest version is. Then in the root create a serverless.yml and pop in the config below.

name: MyApplication

  component: "@sls-next/serverless-component@1.17.0"
      - ${env.SUB_DOMAIN}
      - ${env.DOMAIN}
    memory: 1024

Make sure you update the component to match the version installed. The component takes various inputs, however if you just want to deploy and don't want to worry too much about config all we need to do is specify our SUB_DOMAIN and DOMAIN as environment variables when we deploy.

You will get a CloudFront URL back if you do not specify a domain but we're focusing on production usage. Also I upped the memory as the cost to time execution is worth it.

Ensure that any domain you use is configured in Route 53, otherwise AWS won't be able to generate certificates and set things up correctly.

The Multi-Domain Caveat

If all you are doing is deploying a single domain then you could check in the .serverless folder and everything would be great.

The caveat is that the you do need to do a little bit of manual work to support multiple domains. Serverless generates a .serverless folder that stores all the generated resources. Like the CloudFront instance it needs to update, etc.

So if the .serverless exists it would deploy and update the resources, however production and staging would be 2 separate resources.

The Solution

To solve this issue the Serverless team recommends not checking in .serverless directory to git and syncing the .serverless folder to S3 based upon the environment.

So for example on first deploy you would run something like

aws s3 sync s3://my-bucket/my-repo-name/env-name/.serverless .serverless --delete

This reaches out and grabs the serverless folder from a specific bucket and puts it where ever you ran the command from. Generally this should be at the root of the repo next your .serverless.yml file.

After you deploy the .serverless folder needs to be reuploaded to S3. To accomplish this we run the reverse command. We put the .serverless folder first, and the S3 bucket destination second.

aws s3 sync .serverless s3://my-bucket/my-repo-name/env-name/.serverless --delete

This will sync back and replace the .serverless folder on the remote S3 bucket.

Deploying with Github Actions

The solution is outlined on their README . They link to a github actions workflow you can utilize. You will need to add your AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY into the Settings => Secret section of your repository.

Additionally you will need to setup a deploy command in your package.json . The deploy command would just be "deploy": "serverless" and you would need to ensure that serverless is installed as a devDependency. yarn add serverless --dev will accomplish that if you didn't already have it installed.

# [https://gist.github.com/hadynz/b4e190e0ce10e5811cb462920a9c678f](https://gist.github.com/hadynz/b4e190e0ce10e5811cb462920a9c678f)
name: Continuous deploy

    branches: [master]

    runs-on: ubuntu-latest

			DOMAIN: ${{secrets.PROD_DOMAIN}}

      - uses: actions/checkout@v1
      - uses: actions/setup-node@v1
          node-version: 12

      - name: Install dependencies
        run: npm ci

      - name: Run tests
        run: npm test

      - name: Download `.serverless` state from S3
        run: aws s3 sync s3://my-bucket/my-repo-name/env-name/.serverless .serverless --delete

      - name: Deploy to AWS
        run: npm run deploy

      - name: Upload `.serverless` state to S3
        run: aws s3 sync .serverless s3://my-bucket/my-repo-name/env-name/.serverless --delete

This is a slightly tweaked You will need to replace my-bucket/my-repo-name/env-name/ with the path to the folder.

This however will need to be different for both staging/prod because it needs to know to pull and replace the correct .serverless folder on S3.

There are two options

  1. Copy-paste your config, change the branch at the top from master to your staging branch name and have 2 separate configs. The configs would have hardcoded buckets, as well as hard coded domains.
  2. Setup an S3_SLS_PROD_BUCKET and S3_SLS_STAGING_BUCKET and still have 2 configs but they aren't hard coded. Same with the domains you'd need to swap out PROD_DOMAIN for a separate STAGING_DOMAIN .

One other option for the buckets is to use the BRANCH_NAME as the storage path.

Github CI isn't as feature packaged as some other CIs but the concept still holds. You need to pull the .serverless bucket stored in S3, deploy, then update the .serverless folder after deploy for each specific environment.