Manato
Manato
Jul 26 · 7 min read

TL:DR: Deploy serverless Next.js app using serverless framework, serverless-nextjs-plugin and AWS Lambda function. Repo: https://github.com/manakuro/nextjs-with-aws-lambda-sample

Next.js

Next.js announced that it supports for serverless deployment out of the box in v8 release. By splitting into lambdas functions it improves reliability and scalability. It provides the benefits of scale as you go and “Pay for what you use” model. To simply test it out, Next.js gives us an awesome tutorial with Zeit Now deployment which is really simple and easy. But in this article I will introduce a deployment to AWS Lambda.

Follow the steps to deploy:


Set up Next.js

To quickly install Next.js and set it up, you can use create-next-app .

Install create-next-app:

$ npm install -g create-next-app

Install Next.js app:

$ create-next-app my-app

Make sure that you use v8 in package.json :

{
"name": "my-app",
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start"
},
"dependencies": {
"next": "^8.1.0",
"react": "^16.8.6",
"react-dom": "^16.8.6"
},
}

And start dev server:

$ cd my-app
$ yarn dev
Welcome page

Turn serverless mode on in next.config.js:

/* next.config.js */module.exports = {
target: 'serverless', // <- add here
webpack: config => {
// Fixes npm packages that depend on `fs` module
config.node = {
fs: 'empty'
}
return config
}
}

Build app and you can see artifacts of serverless in .next like this:

$ yarn build
Artifacts of build

These are the functions that route to your app and deploy as Lambda functions in AWS.

Install Serverless framework

Serverless framework can deploy to all major cloud providers such as AWS, GCP and Azure and provides a bunch of APIs to deploy. A single configuration file called serverless.yml allows you to list your functions and defines the endpoints. You can see a docs for AWS here.

Install serverless:

$ npm install serverless -g

To deploy AWS Lambda you need to configure AWS account:

$ serverless config credentials --provider aws --key {your access key id} --secret {your secret access key}

If you do not have a AWS account, follow the instruction to sign up here.

serverless — Sign up for an AWS account

To deploy through serverless framework, add serverless.yml to root:

# serverless.ymlservice: ${self:custom.name}provider:
name: aws
runtime: nodejs10.x
stage: ${opt:stage, 'dev'}
region: ap-northeast-1 // wherever you want
custom:
name: my-app

Install serverless-nextjs-plugin

Nextjs 8 serverless mode doesn’t work out of the box with AWS Lambda. You need to add handlers manually to each serverless pages. But there has been already a plugin around called serverless-nextjs-plugin which allows you to handle Next App pages in Lambda without requiring any configuration.

Install serverless-nextjs-plugin :

yarn add -D serverless-nextjs-plugin

Edit the serverless.yml and add:

service: ${self:custom.name}provider:
name: aws
runtime: nodejs10.x
stage: ${opt:stage, 'dev'}
region: ap-northeast-1
plugins: # <- add here
- serverless-nextjs-plugin
custom:
name: my-app
serverless-nextjs: # <- add here
assetsBucketName: 'my-app-assets-${self:provider.stage}'
package: # <- add here
# exclude everything
# page handlers are automatically included by the plugin
exclude:
- ./**

assetBucketName is a S3 bucket with the name provided. All plugin configuration options can been seen here.

serverless-nextjs-plugin options

Deploy

Now that you’re ready to deploy your app.

Deploy:

$ serverless deploy -v

And you can see the output:

Serverless Nextjs: Started building next app ...
Creating an optimized production build ...
Compiled successfully.Page Size Files Packages
┌ ⚡ / 21.1 kB 2 3
├ /_app 2.04 kB 0 3
├ /_document
└ /_error 2.11 kB 0 3
Serverless Nextjs: Copying next pages to tmp build folder
Serverless Nextjs: Found 3 next page(s)
Serverless Nextjs: Creating compat handler for page: 404.js
Serverless Nextjs: Creating compat handler for page: _error.js
Serverless Nextjs: Creating compat handler for page: index.js
Serverless: Packaging service...
Serverless: Excluding development dependencies...
Serverless Nextjs: Cleaning up tmp build folder ...
Serverless Nextjs: Found bucket "my-app-assets-dev" from serverless.yml
Serverless Nextjs: Proxying NextJS assets -> https://s3-ap-northeast-1.amazonaws.com/my-app-assets-dev/_next/{proxy}
Serverless Nextjs: Proxying static files -> https://s3-ap-northeast-1.amazonaws.com/my-app-assets-dev/static/{proxy}
Serverless: Creating Stack...
Serverless: Checking Stack create progress...
CloudFormation - CREATE_IN_PROGRESS - AWS::CloudFormation::Stack - my-app-dev
CloudFormation - CREATE_IN_PROGRESS - AWS::S3::Bucket - NextStaticAssetsS3Bucket
CloudFormation - CREATE_IN_PROGRESS - AWS::S3::Bucket - ServerlessDeploymentBucket
CloudFormation - CREATE_IN_PROGRESS - AWS::S3::Bucket - NextStaticAssetsS3Bucket
CloudFormation - CREATE_IN_PROGRESS - AWS::S3::Bucket - ServerlessDeploymentBucket
CloudFormation - CREATE_COMPLETE - AWS::S3::Bucket - NextStaticAssetsS3Bucket
CloudFormation - CREATE_COMPLETE - AWS::S3::Bucket - ServerlessDeploymentBucket
CloudFormation - CREATE_COMPLETE - AWS::CloudFormation::Stack - my-app-dev
...
Nextjs Application InfoApplication URL: https://xxxxx.execute-api.ap-northeast-1.amazonaws.com/dev

The Application URL (https://xxxxx.execute-api.ap-northeast-1.amazonaws.com/dev) is your app url.

And if you want to remove it entirely, do it with:

$ serverless remove

And make sure to remove S3 bucket as well:

$ aws s3 rb s3://my-app-assets-dev --force

Apply custom domain

Now that you notice that deployed application url is just a random name like `https://xxxxx.execute-api.ap-northeast-1.amazonaws.com/` . If you want to use custom domain name, you can create your own domain and apply it to serverless framework using serverless-domain-manager.

And you also might want to deploy a different environment like, staging and production . In order to deal with it, use a subdomain and finally you want them to be like this:

  • https://staging.mydomain.com // for staging environment
  • https://mydomain.com // for production environment

Before go to next, follow the instructions to create your domain and request a certificate:

Let’s suppose that you use a domain called mydomain.me in this article.

Install serverless-domain-manager :

$ yarn add -D serverless-domain-manager

Edit serverless.yml :

service: ${self:custom.name}provider:
name: aws
runtime: nodejs10.x
stage: ${opt:stage, 'staging'}
region: ap-northeast-1
plugins:
- serverless-nextjs-plugin
- serverless-domain-manager # <- add here
custom:
name: my-app
serverless-nextjs:
assetsBucketName: 'my-app-assets-${self:provider.stage}'
customDomain: # <- add here
domainName: '${self:provider.stage}.mydomain.me'
certificateName: '*.mydomain.me'
basePath: ''
stage: ${self:provider.stage}
createRoute53Record: true
package:
# exclude everything
# page handlers are automatically included by the plugin
exclude:
- ./**

domainName and certificateName is supposed to be applied to your domain. If you create a domain called yourdomain.me , it should be like this:

customDomain:
domainName: '${self:provider.stage}.yourdomain.me'
certificateName: '*.yourdomain.me'
basePath: ''
stage: ${self:provider.stage}
createRoute53Record: true

Create staging domain:

$ serverless create_domain --stage staging...
Serverless: 'staging.mydomain.me' was created/updated. New domains may take up to 40 minutes to be initialized.

After 40 minutes you can deploy a staging environment:

$ serverless deploy -v --stage staging

Good. Now you can access https://staging.mydomain.me .

serverless.yml could be split into each environment file for maintainability according to the guide. So you can split it like this:

  • serverless.staging.yml
  • serverless.production.yml

Create serverless.staging.yml :

# serverless.staging.ymlcustom:
customDomain:
domainName: '${self:provider.stage}.mydomain.me'
certificateName: '*.mydomain.me'
basePath: ''
stage: ${self:provider.stage}
createRoute53Record: true

And edit serverless.yml :

# serverless.ymlservice: ${self:custom.name}provider:
name: aws
runtime: nodejs10.x
stage: ${opt:stage, 'staging'}
region: ap-northeast-1
plugins:
- serverless-nextjs-plugin
- serverless-domain-manager
custom:
name: my-app
serverless-nextjs:
assetsBucketName: 'my-app-assets-${self:provider.stage}'
customDomain:
domainName: ${file(./serverless.${self:provider.stage}.yml):custom.customDomain} # <- add here
package:
# exclude everything
# page handlers are automatically included by the plugin
exclude:
- ./**

Create serverless.production.yml :

# serverless.production.ymlcustom:
customDomain:
domainName: 'mydomain.me'
basePath: ''
stage: ${self:provider.stage}
createRoute53Record: true

In production domainName should be mydomain.me and certificateName is not needed anymore.

Let’s deploy production environment. Create domain:

$ serverless create_domain --stage production...
Serverless: 'mydomain.me' was created/updated. New domains may take up to 40 minutes to be initialized.

After 40 minutes you can deploy a production environment:

$ serverless deploy -v --stage production

Now you can access https://mydomain.me .

Restrict IP Address

You can control access to your app using a resource policy in API Gateway and there is a great description in AWS website. Serverless framework supports it.

Add resourcePolicy to serverless.yml :

service: ${self:custom.name}provider:
name: aws
runtime: nodejs10.x
stage: ${opt:stage, 'staging'}
region: ap-northeast-1
resourcePolicy: ${file(./serverless.${self:provider.stage}.yml):resourcePolicy} // <- add here
...

And add resourcePolicy to each environment like this:

# serverless.staging.ymlresourcePolicy:
- Effect: Allow
Principal: "*"
Action: execute-api:Invoke
Resource:
- execute-api:/*/*/*
Condition:
IpAddress:
aws:SourceIp:
- "xxx.xxx.xxx.xx"

aws:SrouceIp is a whitelist of specific IP address ranges you can access. After deploy there will be resource policy in API Gateway console:

API Gateway console

Conclusion

That’s it for the part one. At this point you should have learn to:

  • Setup Next.js
  • Deploy app through Serverless framework
  • Create custom domain
  • Restrict IP Address

In the next part, we’ll cover CI deploy with CircleCI.

And you can see my repo here.

https://github.com/manakuro/nextjs-with-aws-lambda-sample

manato

Manato Kuroda Blog

Manato

Written by

Manato

Web Developer, JavaScript, Angular, Vue.js, React, TypeScript, Go, PHP and Ruby on Rails / https://manatoworks.me

manato

manato

Manato Kuroda Blog

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade