DEV Community

Cover image for Deploy Next.js using Amplify & AWS CDK
RedRobot.dev
RedRobot.dev

Posted on • Originally published at redrobot.dev

Deploy Next.js using Amplify & AWS CDK

In this guide, we'll dive into the process of deploying a Next.js application using AWS CDK (Cloud Development Kit) and Amplify Gen 2. We'll explore how these powerful tools can be combined to create a seamless, serverless deployment pipeline for your Next.js projects. You'll learn how to leverage AWS CDK's infrastructure-as-code capabilities to define your cloud resources, while harnessing Amplify's built-in support for Next.js to simplify hosting and continuous deployment. By the end of this journey, you'll have a robust understanding of how to create scalable, easily maintainable Next.js deployments on AWS.

Original Post

In this comprehensive guide, we'll explore the process of deploying a Next.js application using AWS CDK (Cloud Development Kit) and Amplify Gen 2. We'll demonstrate how these tools can be combined to create a seamless, serverless deployment pipeline for your Next.js projects. You'll learn to harness AWS CDK's infrastructure-as-code capabilities to define and manage your cloud resources efficiently, while leveraging Amplify's specialized support for Next.js to streamline hosting and enable continuous deployment. By the end of this tutorial, you'll have gained a robust understanding of how to deploy your Next.js app to AWS.

Final Output

Here is the github repo

AWS Amplify

AWS Amplify is a comprehensive service designed to simplify full-stack application development and deployment on AWS. It provides a unified, streamlined developer experience by abstracting away the complexity of managing multiple AWS services. Amplify leverages a suite of powerful AWS tools including S3, API Gateway, CodeBuild, Cognito, and Lambda, enabling developers to create robust infrastructure supporting their required features without needing deep expertise in cloud architecture.

One of Amplify's key strengths is its dedicated UI libraries for popular frameworks such as React, Next.js, Angular, and Vue, facilitating faster integration and development. The core concept behind Amplify is elegantly simple: using only TypeScript code, developers can express their app's data model, business logic, authentication, and authorization rules. Amplify then automatically configures the appropriate cloud resources, eliminating the need to manually stitch together underlying AWS services. This approach positions Amplify as a comprehensive, end-to-end full-stack development framework.

Amplify Gen 2 represents AWS's latest effort to further streamline the development process for full-stack applications. It builds upon the foundations of the original Amplify, offering enhanced features and improved performance. This new generation aims to make it even easier for developers to harness the power of AWS services, reducing the learning curve and allowing teams to focus more on building innovative applications rather than managing infrastructure. With Amplify Gen 2, AWS continues to bridge the gap between development and operations, empowering developers to create sophisticated, scalable applications with greater efficiency and less complexity.

Topics Covered in this guide

In this tutorial, we'll walk through the process of deploying and updating a Next.js application using AWS CDK and Amplify. We'll cover the entire workflow from initial development to automated deployment. Here's what we'll accomplish:

  1. Create a Next.js application: We'll start by building a starter Next.js app that utilizes both Server-Side Rendering (SSR) and Static Site Generation (SSG) capabilities.
  2. Version control with GitHub: We'll push our frontend code to a GitHub repository, setting the stage for continuous integration and deployment.
  3. AWS CDK and Amplify setup: Using AWS CDK and Amplify libraries, we'll create an Amplify project that specifies the build steps and connects to our GitHub repository. This step will demonstrate how to define your cloud infrastructure as code.
  4. Initial deployment: We'll trigger an initial deployment to test our setup and ensure everything is working correctly.
  5. Implement and deploy changes: Finally, we'll make updates to our frontend, push these changes to GitHub, and observe how Amplify automatically detects and deploys these updates, showcasing the power of continuous deployment.

Prerequisites

  • An AWS account
  • Node.js and npm installed
  • AWS cli and cdk setup and configured
  • Basic knowledge in Javascript and Typescript

Github Project

Open up Github, create a new repo and name it nextjs-aws-amplify. See this repo as an example:
https://github.com/ababakanian/nextjs-aws-amplify

Pull the code locally (replace the username with yours)

git clone git@github.com:<your-user-name>/nextjs-aws-amplify.git
Enter fullscreen mode Exit fullscreen mode

Get an access token

In addition, lets create an access token in github. Go to the Github Personal access token page page and click on Generate new token (classic)

github access token

select a name for the access token and from teh select scopes list select the repo option. Scroll down and click on Generate token.

Copy the generate token and lets add it to an env file.

cd nextjs-aws-amplify
touch .env
Enter fullscreen mode Exit fullscreen mode

open the .env file and add a variable for the github token in the env file.
So your .env file should look this this.

GITHUB_ACCESS_TOKEN=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
Enter fullscreen mode Exit fullscreen mode

Note: Do no copy this value, this is not going to work for you as it belongs to our repo and also it's been deleted.

Add the .env file to your .gitignore list, touch .env and then open the file and add .env.

Create a Next.js Project

Make sure you are in the nextjs-aws-amplify folder, then create a NextJS project. Open a terminal and type:

npx create-next-app@latest
Enter fullscreen mode Exit fullscreen mode

enter frontend for the app name. Then you will be presented with several options, choose the options as you
see here:

Need to install the following packages:
create-next-app@14.2.5
Ok to proceed? (y) y

✔ What is your project named? … test-frontend
✔ Would you like to use TypeScript? … No / **Yes**
✔ Would you like to use ESLint? … No / **Yes**
✔ Would you like to use Tailwind CSS? … No / **Yes**
✔ Would you like to use `src/` directory? … No / **Yes**
✔ Would you like to use App Router? (recommended) … No / **Yes**
✔ Would you like to customize the default import alias (@/*)? … **No** / Yes
Enter fullscreen mode Exit fullscreen mode

We will come back and modify the frontend code later. However, lets push this code to github and get the github config info.

git add .
git commit -m "initial commit, added frontend nextjs"
git push origin main
Enter fullscreen mode Exit fullscreen mode

create a .nvmrc file and add the value nodejs.

echo "v20.11.1" > .nvmrc
Enter fullscreen mode Exit fullscreen mode

Create AWS CDK

By leveraging an IaC system like AWS cdk, you can automate and replicate all the setup and configuration work typically done through the console. This approach significantly speeds up your website deployment process.

  1. Initialize a new CDK project, make sure you are in the top level nextjs-aws-amplify folder created earlier. Then create an infrastructure folder and initialize it with cdk code.
mkdir infrastructure && cd infrastructure
cdk init app --language typescript
Enter fullscreen mode Exit fullscreen mode

If you haven't setup cdk yet, visit [AWS CDK Getting started (https://docs.aws.amazon.com/cdk/v2/guide/getting_started.html)

  1. Install the amplify library
npm i @aws-cdk/aws-amplify-alpha@2.39.1-alpha.0 --save
Enter fullscreen mode Exit fullscreen mode

Note: At the time of writing this post the library is in alpha stage. Check the npm page here to see if the version has left the alpha stage.

  1. Install supporting libraries:
npm i dotenv --save
Enter fullscreen mode Exit fullscreen mode
  1. Create a file named in lib named lib/amplify-gen-stack.ts with the following content:
import * as amplify from "@aws-cdk/aws-amplify-alpha"

import { Construct } from "constructs"
import { CfnOutput, SecretValue, Stack, StackProps } from "aws-cdk-lib"
import { BuildSpec } from "aws-cdk-lib/aws-codebuild"
import { ManagedPolicy, Role, ServicePrincipal } from "aws-cdk-lib/aws-iam"
import { CfnApp, CfnBranch } from "aws-cdk-lib/aws-amplify"

export interface AmplifyStackProps extends StackProps {
  githubOauthToken: SecretValue
  repoOwner: string
  repoName: string
  domain: string
}

export class AmplifyStack extends Stack {
  constructor(scope: Construct, id: string, props: AmplifyStackProps) {
    super(scope, id, props)

    const amplifyApp = new amplify.App(this, "AmplifyAppResource", {
      appName: props.repoName,
      description: "Nextjs frontend",
      role: new Role(this, "AmplifyRoleWebApp", {
        assumedBy: new ServicePrincipal("amplify.amazonaws.com"),
        managedPolicies: [
          ManagedPolicy.fromAwsManagedPolicyName("AdministratorAccess-Amplify"),
        ],
      }),
      sourceCodeProvider: new amplify.GitHubSourceCodeProvider({
        oauthToken: props.githubOauthToken,
        owner: props.repoOwner,
        repository: props.repoName,
      }),
      buildSpec: BuildSpec.fromObjectToYaml({
        version: "1.0",
        applications: [
          {
            appRoot: "frontend",
            frontend: {
              phases: {
                preBuild: {
                  commands: [
                    "nvm install",
                    "nvm use",
                    "export NODE_OPTIONS=--max-old-space-size=8192",
                    "npm install",
                  ],
                },
                build: {
                  commands: ["npm run build"],
                },
              },
              artifacts: {
                baseDirectory: ".next",
                files: ["**/*"],
              },
            },
          },
        ],
      }),
    })

    const cfnApp = amplifyApp.node.defaultChild as CfnApp
    cfnApp.platform = "WEB_COMPUTE"

    const mainBranch = amplifyApp.addBranch("main", {
      autoBuild: true,
      stage: "PRODUCTION",
    })

    const domain = amplifyApp.addDomain(props.domain, {
      enableAutoSubdomain: true,
    })
    domain.mapRoot(mainBranch)
    domain.mapSubDomain(mainBranch, "www")

    const cfnBranch = mainBranch.node.defaultChild as CfnBranch
    cfnBranch.framework = "Next.js - SSR"

    new CfnOutput(this, "AmplifyAppURL", {
      value: `https://main.${amplifyApp.defaultDomain}`,
      description: "Amplify App URL",
    })
  }
}
Enter fullscreen mode Exit fullscreen mode

The amplify.App construct has two main parts, the source code repo configuration and the build instructions.
We define the code repo location using

sourceCodeProvider: new amplify.GitHubSourceCodeProvider({
  oauthToken: props.githubOauthToken,
  owner: props.repoOwner,
  repository: props.repoName,
})
Enter fullscreen mode Exit fullscreen mode

and we define the build and artifacts information:

buildSpec: BuildSpec.fromObjectToYaml({
    version: "1.0",
    applications: [
      {
        appRoot: "frontend",
        frontend: {
          phases: {
            preBuild: {
              commands: [
                "nvm install",
                "nvm use",
                "export NODE_OPTIONS=--max-old-space-size=8192",
                "npm install",
              ],
            },
            build: {
              commands: ["npm run build"],
            },
          },
          artifacts: {
            baseDirectory: ".next",
            files: ["**/*"],
          },
        },
      },
    ],
  }),
})
Enter fullscreen mode Exit fullscreen mode

essentially we are installing node, and installing all the node packages in the prebuild step. Then we run npm run build to build the nextjs app.
The artifacts section specifies where our external files will be located.

To learn more check the AWS cdk Documentation

Lets use the stack in our bin/infrastructure.ts file:

#!/usr/bin/env node
import "source-map-support/register"
import * as cdk from "aws-cdk-lib"
import * as dotenv from "dotenv"
import { AmplifyStack } from "../lib/amplify-gen-stack"

dotenv.config({ path: "../.env" })

const {
  AWS_ACCOUNT_ID,
  AWS_REGION,
  DOMAIN,
  GITHUB_ACCESS_TOKEN,
  GITHUB_REPO_OWNER,
  GITHUB_REPO_NAME,
} = process.env

if (
  !AWS_ACCOUNT_ID ||
  !AWS_REGION ||
  !GITHUB_ACCESS_TOKEN ||
  !GITHUB_REPO_OWNER ||
  !GITHUB_REPO_NAME ||
  !DOMAIN
) {
  console.error("check .env file")
  throw new Error("check .env file")
}

const app = new cdk.App()
new AmplifyStack(app, "AmplifyStack", {
  githubOauthToken: cdk.SecretValue.unsafePlainText(GITHUB_ACCESS_TOKEN),
  repoOwner: GITHUB_REPO_OWNER,
  repoName: GITHUB_REPO_NAME,
  domain: DOMAIN,
  env: {
    account: AWS_ACCOUNT_ID,
    region: AWS_REGION,
  },
})
Enter fullscreen mode Exit fullscreen mode

add the remaining variable to the .env file we created earlier:

AWS_ACCOUNT_ID=AWS account number
AWS_REGION=your deployment region
DOMAIN=your domain name
GITHUB_REPO_OWNER=the github repo owner
GITHUB_REPO_NAME=the repo name
GITHUB_ACCESS_TOKEN=the github access token
Enter fullscreen mode Exit fullscreen mode

so your data should look something like this;

AWS_ACCOUNT_ID="975421487117"
AWS_REGION="us-east-1"
DOMAIN=redrobotexample.com
GITHUB_ACCESS_TOKEN=ghp_KGu0GbvmdOiFNUzK6mGGFRBxBMM3rc1sTq0C
GITHUB_REPO_OWNER=redrobotdev
GITHUB_REPO_NAME=nextjs-aws-amplify
Enter fullscreen mode Exit fullscreen mode

now deploy the stack, make sure you are in the infrastructure folder then call deploy.

cd infrastructure
cdk deploy
Enter fullscreen mode Exit fullscreen mode

Once deployment, you will see the following message in the console

AmplifyStack: creating CloudFormation changeset...

 ✅  AmplifyStack

✨  Deployment time: 51.82s

Outputs:
AmplifyStack.AmplifyAppURL = https://main.dqsf8v3g3qb5.amplifyapp.com
Stack ARN:
arn:aws:cloudformation:us-east-1:975050147627:stack/AmplifyStack/3e7e4c70-58c9-11ef-bff5-121072f7c15f

✨  Total time: 56.97s
Enter fullscreen mode Exit fullscreen mode

Deploy the Application

So we have successfully created an Amplify project, we need to go to teh AWS console and start a deployment.
Go to the AWS Console page, and open the AWS Amplify service page. You should see the nextjs-aws-amplify, project listed.

AWS Amplify

Click on the project and if you see a Migrate to Github app message, ignore it by clicking Remind me later.

Click on the main branch

nextjs-aws-amplify-overview

Then click on the Run job button

next-aws-amplify-run-job

Wait until the Deployments is done

nextjs-aws-amplify-deployments

Once deployed, the box should go green, and the status should say deployed

deployed-status

visit the domain name and you should see the Nextjs page.

website

Update the frontend

Amplify has CI/CD integrated into it's system, so we can make changes to our frontend and push our changed directly to github. Amplify will get notified of a recent push, and it will automatically pull the code, build and redeploy it.

Lets first install shadcn in our frontend. make sure you have traversed into the frontend folder cd frontend.

npx shadcn-ui@latest init
Enter fullscreen mode Exit fullscreen mode

then install the card and button components

npx shadcn-ui@latest add card button
Enter fullscreen mode Exit fullscreen mode

Open the file from the same frontend folder /src/app/page.tsx:

import Image from "next/image"
import Link from "next/link"

export default function Home() {
  return (
    <main className="flex min-h-screen flex-col space-y-4 items-center p-24">
      <Image
        className="relative dark:drop-shadow-[0_0_0.3rem_#ffffff70] dark:invert"
        src="/next.svg"
        alt="Next.js Logo"
        width={180}
        height={37}
        priority
      />
      <Link href="/invoice" className="underline">
        Invoice
      </Link>
    </main>
  )
}
Enter fullscreen mode Exit fullscreen mode

Lets create a new folder named invoice and page.tsx so you have the path nextjs-aws-amplify/src/app/invoice/page.tsx. Paste the following content in there:

export const dynamic = "force-dynamic"
import React from "react"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import Link from "next/link"

export default async function FinancePage() {
  const income = Math.floor(Math.random() * 100000) + 50000 // Random income between 50,000 and 150,000
  const profit = Math.floor(Math.random() * (income * 0.3)) // Random profit up to 30% of income

  return (
    <div className="container mx-auto p-4">
      <h1 className="text-2xl font-bold mb-4">Financial Dashboard</h1>
      <Link href="/">
        <Button className="mb-4">Home</Button>
      </Link>
      <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
        <Card>
          <CardHeader>
            <CardTitle>Income</CardTitle>
          </CardHeader>
          <CardContent>
            <p className="text-3xl font-bold">${income.toLocaleString()}</p>
          </CardContent>
        </Card>
        <Card>
          <CardHeader>
            <CardTitle>Profit</CardTitle>
          </CardHeader>
          <CardContent>
            <p className="text-3xl font-bold">${profit.toLocaleString()}</p>
          </CardContent>
        </Card>
      </div>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

commit and push the changes

git add .
git commit -m "added the invoice page"
git push origin main
Enter fullscreen mode Exit fullscreen mode

from the AWS console/Amplify Service page you will see a new deployment has started. Make sure the deployment goes through fine and refresh the website to see the affected changes.
Verify the changes has taken affect and you can see the invoice page.

Note: The home page is a Static page so it's not showcasing the power of amplify serverless nextjs deployment. Go to the invoice page and refresh the page and you will notice on each refresh the values changes. This page is Server Side Rendered and demonstrates the server side rendering capability of Next.js.

Conclusion

In this short guide, we've walked through the process of deploying a simple Next.js page using AWS CDK and Amplify Gen 2. We've covered everything from setting up a Next.js project and managing it with GitHub, to creating an AWS CDK stack for Amplify deployment, and finally implementing and automatically deploying updates. By leveraging the power of AWS CDK's infrastructure-as-code capabilities and Amplify's streamlined deployment pipeline, we've demonstrated how to create a scalable, easily maintainable Next.js application on AWS. This approach not only simplifies the deployment process but also enables continuous integration and deployment, allowing developers to focus more on building innovative features rather than managing infrastructure.

Interested in learning more?

If you are interested in creating a project from scratch using AWS CDK and serverless technologies, you can check out the course linked below. In this course, we go over each service in detail, explaining what they do, and actually create a dynamic application with user login and authentication.

Serverless Fullstack with AWS/CDK/NextJS & Typescript

Top comments (0)