Deploy Next.js to AWS Lambda using the Serverless Application Framework.
This demo uses the following tools:
and is based on the following projects:
- nextjs-fargate-demo: We deploy the same Next.js application.
- aws-lambda-serverless-reference: A reference Serverless Application Framework project with additional Terraform support for IAM permission boundaries.
The main goals of this demo project are as follows:
-
Slim down a Next.js Lambda deployment: The Next.js
target: "serverless"
Node.js outputs are huge. Like really, really big because each page contains all the dependencies. This project aims to usetarget: "server"
Node.js outputs to achieve a smaller package.Here's our starting point with
serverless
target:$ yarn clean && yarn build && yarn lambda:sls package --report $ du -sh .serverless/blog.zip && zipinfo -1 .serverless/blog.zip | wc -l 4.0M .serverless/blog.zip 290 $ du -sh .next/serverless/pages/index.js 2.7M .next/serverless/pages/index.js
Here's with
server
target:$ yarn clean && yarn build && yarn lambda:sls package --report $ du -sh .serverless/blog.zip && zipinfo -1 .serverless/blog.zip | wc -l 3.0M .serverless/blog.zip 1291 $ du -sh .next/server/pages/index.js 12K .next/server/pages/index.js
While the package sizes at 2 pages are comparable for the overall zip, the
server
(12K) vsserverless
(2.7M) per page cost ofpages/index.js
, and each additional page, becomes apparent. -
Single Lambda/APIGW proxy: The Next.js
target: "serverless"
requires you to either manually create a routing solution based on Next.js generated metadata files or use something like next-routes. However,target: "server"
contains a router itself for one endpoint. Thus, by using theserver
target we can avoid one of the biggest pains of deploying to a single Lambda target for an entire Next.js application.
We use the production-only Node server found in next/dist/server/next-server.js
instead of the development augmented core server found in next/dist/server/next.js
. This has a few extra constraints, but ends up being a good choice for the following reasons:
- Both
next-server.js
andnext.js
get to use the built-in Next.js router that is unavailable when usingserverless
target. - The traced file bundle for
next-server.js
is much slimmer as tracing can easily skip build dependencies likewebpack
,babel
, etc. that come in withnext.js
- Next.js itself now follows this exact model for their experimental tracing support and we can see a similar server configuration here.
We package only the individual files needed at runtime in our Lambda using the Serverless Application Framework with the serverless-jetpack plugin. The Jetpack plugin examines all the application entry points and then traces all imports and then creates a zip bundle of only the files that will be needed at runtime.
For those doing their own Lambda deployments (say with Terraform), we provide a standalone CLI, trace-pkg, to produce traced zip bundles from entry points.
Part of the underlying bundle size problem is that the next
package ships with a ton of build-time and development-only dependencies that artificially inflate the size of a bundle suitable for application deployment. By using the next-server.js
runtime and file tracing, we get the smallest possible package for cloud deployment that is still correct.
To read more about file tracing and integration with your applications, see
- Jetpack: trace your way to faster and smaller Serverless packages
- trace-pkg: Package Node.js apps for AWS Lambda and beyond
Some caveats:
- Static files: To make this demo a whole lot easier to develop/deploy, we handle serve static assets from the Lambda. This is not what you should do for a real application. Typically, you'll want to stick those assets in an S3 bucket behind a CDN or something. Look for the
TODO(STATIC)
comments variously throughout this repository to see all the shortcuts you should unwind to then reconfigure for static assets "the right way". - Deployment URL base path: We have the Next.js blog up at sub-path
/blog
. A consumer app may go instead for root and that would simplify some of the code we have in this repo to make all the dev + prod experience work the same. - Lambda SSR + CDN: Our React SSR hasn't been tuned at all yet for caching in the CDN like a real world app would want to do.
Start with:
$ yarn install
Then we provide a lot of different ways to develop the server.
The built-in Next.js dev server, compilation and all.
$ yarn dev
and visit: http://127.0.0.1:3000/blog/
We have a Node.js custom express
server that uses almost all of the Lambda code, which is sometimes an easier development experience that serverless-offline
. This also could theoretically serve as a real production server on a bare metal or containerized compute instance outside of Lambda.
$ yarn clean && yarn build
$ yarn start
and visit: http://127.0.0.1:4000/blog/
This uses serverless-offline
to simulate the application running on Lambda.
$ yarn clean && yarn build
$ yarn lambda:localdev
and visit: http://127.0.0.1:5000/blog/
We target AWS via a simple command line deploy using the serverless
CLI. For a real world application, you'd want to have this deployment come from your CI/CD pipeline with things like per-PR deployments, etc. However, this demo is just here to validate Next.js running on Lambda, so get yer laptop running and fire away!
Environment:
Defaults:
SERVICE_NAME=nextjs-serverless
: Name of our service.AWS_REGION=us-east-1
: RegionSTAGE=localdev
: Default for local development on your machine.
For deployment, switch the following variables:
STAGE=sandbox
: Our cloud sandbox. We will assume you're deploying here for the rest of this guide.
Get AWS vault
This allows us to never have decrypted credentials on disk.
$ brew install aws-vault
We will assume you have an AWS_USER
configured that has privileges to do the rest of the cloud provisioning needed for the Serverless application deployment.
We will use serverless
to deploy to AWS Lambda.
Deploy the Lambda app.
# Build for production.
$ yarn clean && yarn build
# Deploy
$ STAGE=sandbox aws-vault exec AWS_USER -- \
yarn lambda:deploy
# Check on app and endpoints.
$ STAGE=sandbox aws-vault exec AWS_USER -- \
yarn lambda:info
See the aws-lambda-serverless-reference docs for additional Serverless/Lambda (yarn lambda:*
) tasks you can run.
As a useful helper we've separately hooked up a custom domain for STAGE=sandbox
at:
https://nextjs-sls-sandbox.formidable.dev/blog/
ℹ️ Note: We set
BASE_PATH
to/blog
and not/${STAGE}/blog
like API Gateway does for internal endpoints for our references to other static assets. It's kind of a moot point because frontend assets shouldn't be served via Lambda/APIGW like we do for this demo, but just worth noting that the internal endpoints will have incorrect asset paths.
Active: Formidable is actively working on this project, and we expect to continue for work for the foreseeable future. Bug reports, feature requests and pull requests are welcome.