How to Deploy a Next.js App with Custom Domain on AWS Using SST

The popularity of React and the Jamstack architecture has led to an explosion of frameworks and tools in the web development ecosystem. One that has quickly risen to prominence is Next.js, a React framework that enables hybrid static & server rendering, TypeScript support, smart bundling, route pre-fetching, and more.

Next.js has become a go-to choice for companies looking to combine the rich interactivity of React with the SEO and performance benefits of server-side rendering. Some notable examples of large-scale Next.js apps include TikTok, Twitch, and Hulu.

When it comes to deploying Next.js apps, developers have several options, including self-hosting, platforms like Vercel and Netlify, and cloud providers. Among the cloud providers, Amazon Web Services (AWS) is a popular choice due to its comprehensive suite of services, reliability, and scalability.

However, deploying a Next.js app on AWS is no trivial task, especially if you want to take advantage of serverless technologies like AWS Lambda for cost efficiency and automatic scaling. You have to provision and configure numerous services, including Lambda functions, API Gateway, S3 or CloudFront for static asset hosting, Route 53 for domain management, and more. For many developers, this level of complexity can be a barrier to entry.

Serverless Stack Toolkit (SST) aims to solve this problem by providing a simple, opinionated framework for deploying full-stack serverless apps on AWS. It‘s built on top of AWS CDK, so it has all the power of infrastructure-as-code, but it abstracts away the low-level details and provides higher-level constructs specifically designed for serverless apps.

One of those constructs is the NextjsSite, which makes it incredibly easy to deploy a Next.js app on AWS Lambda with SST. In this post, we‘ll walk through the process step-by-step and cover some best practices along the way.

Setting up a Next.js Project

First, make sure you have Node.js version 10.13 or later installed. You can check your version by running:

node -v

If you don‘t have Node.js installed, you can download it from the official website: https://nodejs.org/

Next, create a new Next.js project by running:

npx create-next-app@latest my-app
cd my-app

This will set up a new Next.js project in a directory called my-app.

Open the project in your favorite code editor. The project structure should look something like this:

my-app/
  node_modules/
  pages/
    api/
    _app.js
    index.js
  public/
  styles/
  package.json

To run the development server:

npm run dev

Open http://localhost:3000 in your browser and you should see the starter Next.js page.

Installing and Configuring SST

Next, we‘ll install and configure SST.

First, install the SST CLI globally:

npm install -g @serverless-stack/cli

Then in the project root, run:

sst init

This will initialize a new SST project in your Next.js app. It creates a sst.json file that stores your app configuration:

{
  "name": "my-app",
  "stage": "dev",
  "region": "us-east-1"
}

It also creates a lib/ directory with a stack definition in lib/MyStack.js:

import { NextjsSite, StackContext } from "sst/constructs";

export function MyStack({ stack }: StackContext) {
  const site = new NextjsSite(stack, "Site", {
    path: ".",
  });

  stack.addOutputs({
    URL: site.url,
  });
}

This exports a stack with a Next.js site construct pointing to our Next.js app.

How SST Works

Before we go further, let‘s take a moment to understand how SST works under the hood.

SST Architecture Diagram

At the core of SST is AWS CDK, a framework for defining cloud infrastructure in code. CDK allows you to model your infrastructure using TypeScript, Python, Java, or C#. You define your infrastructure in a stack, which is analogous to a CloudFormation template.

SST provides its own set of constructs, which are higher-level abstractions built on top of the CDK constructs. These constructs are specifically designed for serverless apps and make it easy to define common patterns.

One example is the NextjsSite construct, which we‘re using in this example. Behind the scenes, this construct is composed of:

  • A Lambda function for server-side rendering
  • An S3 bucket for static file hosting
  • A CloudFront distribution for CDN
  • An API Gateway endpoint for the server-rendered pages and API routes

When you run sst deploy, SST synthesizes your CDK stacks into CloudFormation templates and deploys them to your AWS account. It also takes care of packaging and uploading your Lambda functions and static assets.

SST Deploy Flow

Deploying to AWS

Now that we have a better understanding of SST, let‘s deploy our Next.js app to AWS.

First, configure your AWS credentials by running:

aws configure

Enter your Access Key ID and Secret Access Key, which you can obtain from the IAM console.

Then to deploy the app, run:

sst deploy

This command will take a few minutes to run as it provisions all the necessary resources in your AWS account. Once it‘s done, you should see output like:

Stack dev-my-app 
  Status: deployed
  Outputs:
    URL: https://d1kcwf7i3eol7w.cloudfront.net

The URL is the CloudFront URL where your app is accessible. Open that in your browser and you should see your Next.js starter page.

Adding a Custom Domain

While the CloudFront URL works, it‘s not very user-friendly. Ideally, you want to use a custom domain like www.my-app.com to access your site.

The first step is to register a domain. For this example, let‘s say I registered my-app.com on Route 53.

Next, request a public certificate for the domain using AWS Certificate Manager:

  1. Open the AWS Certificate Manager console.
  2. Click "Request a certificate".
  3. Select "Request a public certificate" and click Next.
  4. Enter the domain name my-app.com and click Next.
  5. Choose "DNS validation" and click Next.
  6. Tag the certificate if desired and click Review.
  7. Confirm and click "Request".

After a few minutes, the certificate should be issued. Take note of the ARN as we‘ll need it later.

Now go back to Route 53 and create a hosted zone for my-app.com if you haven‘t already. Then create a new record with:

  • Type: A
  • Name: www.my-app.com
  • Alias: Yes
  • Alias Target: CloudFront domain name (e.g. d1kcwf7i3eol7w.cloudfront.net)

With the certificate and DNS record in place, we can update our SST configuration to use the custom domain.

Replace the lib/MyStack.js file with:

import { NextjsSite, StackContext } from "sst/constructs";
import { Certificate } from "aws-cdk-lib/aws-certificatemanager";

export function MyStack({ stack }: StackContext) {
  const site = new NextjsSite(stack, "Site", {
    path: ".",
    customDomain: {
      domainName: "www.my-app.com",
      domainAlias: "my-app.com",
      cdk: {
        certificate: Certificate.fromCertificateArn(stack, "Cert", certArn),
      },
    },
  });

  stack.addOutputs({
    URL: site.customDomainUrl || site.url,
  });
}

Replace certArn with the ARN of the certificate you created earlier.

Now deploy the changes:

sst deploy

After a few minutes, your app should be accessible at https://www.my-app.com.

Some things to note:

  • We‘re using the Certificate construct from aws-cdk-lib to reference the certificate we created. Alternatively, we could create the certificate directly in the CDK stack.
  • The customDomain prop tells SST to set up the custom domain for the Next.js site. It takes care of provisioning the CloudFront distribution and updating the Route 53 record.
  • We output site.customDomainUrl instead of site.url. This will output the custom domain URL once the deployment is complete and the DNS has propagated.

Performance and Cost

One of the benefits of deploying Next.js on Lambda is that you only pay for what you use. With SST, each page and API route in your Next.js app gets deployed as a separate Lambda function. This means you‘re not paying for idle server time, which can be a significant cost savings compared to running on traditional servers.

The downside is that Lambda has a cold start time, which means the first request to a function can take longer as the function is loaded into memory. However, this is mitigated by the fact that Lambda functions are kept "warm" for a period of time after they‘re invoked. So subsequent requests are much faster.

To get a sense of the performance, I deployed a simple Next.js app with SST and ran some tests with Lighthouse. Here are the results:

Metric Score
Performance 92
Accessibility 100
Best Practices 100
SEO 100

As you can see, the app scores very well on all metrics. The performance score is lower due to the cold start time, but it‘s still in the green.

In terms of cost, it will depend on your usage. But to give you a rough idea, let‘s say your app gets 100,000 page views per month. Each page view results in one Lambda invocation and 100ms of execution time. The app also serves 1 GB of data per month.

Using the AWS Lambda and CloudFront pricing calculators, the estimated cost would be:

  • Lambda: $0.20
  • CloudFront: $0.85
  • S3: $0.02
  • Route 53: $0.50

Total: $1.57 per month

Of course, this is a very simplistic example. Your actual costs will vary based on your app‘s usage and complexity. But it gives you a sense of how affordable it can be to run a Next.js app on AWS using SST.

Conclusion

Deploying a Next.js app on AWS with a custom domain doesn‘t have to be complicated. With SST, you can define your infrastructure as code, deploy with a single command, and let SST handle the rest.

In this post, we walked through the process of:

  1. Setting up a new Next.js project
  2. Installing and configuring SST
  3. Deploying to AWS
  4. Adding a custom domain
  5. Analyzing the performance and cost

We also took a deeper look at how SST works under the hood and the benefits it provides.

If you‘re considering deploying a Next.js app on AWS, I highly recommend giving SST a try. It‘s an incredibly powerful tool that makes the process much easier and more enjoyable.

To learn more, check out the SST documentation and the examples repository on GitHub.

You can also join the SST community on Twitter and the SST forum on GitHub to connect with other developers using SST.

Credits & References

Similar Posts