Alternative to Vercel Deploying a Next.js Application on AWS Amplify using AWS CDK

A screen capture of a developer making this tutorial

Table of Contents

  1. Introduction
  2. An Overview of AWS Amplify and Hosting Next.js
  3. Prerequisites
  4. Setting Up the Next.js Starter Kit
  5. Introducing AWS CDK and Its Key Concepts
  6. Configuring the AWS CDK Stack for Deployment
  7. Deploying with AWS Amplify
  8. Tips for Effective Maintenance
  9. Conclusion

Introduction

While Vercel stands out as a popular platform for deploying Next.js applications, there are alternative paths that cater to specific requirements and offer greater flexibility. Enter AWS Amplify with AWS CDK. With this guide, we’ll explore the step-by-step process of deploying your Next.js app on AWS Amplify using AWS CDK. Ready to embark on this voyage? Let’s dive right in!

An Overview of AWS Amplify and Hosting Next.js

AWS Amplify

AWS Amplify is a development platform from Amazon Web Services (AWS) that makes it easy for developers to build and deploy applications. Amplify abstracts the process of configuring cloud services and provides a straightforward way to interact with them.

Key Features of AWS Amplify:

  • Backend Functions: Simplifies setting up cloud services like authentication, APIs, storage, and more.
  • Hosting & CI/CD: Automated deployment, hosting, and continuous integration/continuous deployment options are available out of the box.
  • Scalability: Built on top of AWS, it offers immense scalability options for your applications.
  • Integrated Development: Amplify’s libraries and UI components help in seamlessly integrating cloud services with your app.
  • Monitoring with CloudWatch: AWS Amplify integrates seamlessly with CloudWatch, offering real-time monitoring of your Next.js application. This allows you to track and diagnose issues, set alarms, and gain insights into application performance and usage patterns.

Hosting Next.js on AWS Amplify

Next.js, a popular React framework with server-side rendering capabilities, can be efficiently hosted on AWS Amplify. The primary advantages include:

  • SSR (Server-Side Rendering): AWS Amplify fully supports Next.js’s SSR, allowing for faster page loads and enhanced SEO capabilities.
  • Environment Variables: Easy configuration and usage of environment variables to manage secrets and application settings.
  • Continuous Deployment: Integrated CI/CD from your repository, ensuring that your Next.js app is always up to date on the cloud.
  • Branching: Features like branch-based deployments make it simpler to manage different stages of your application.

In essence, AWS Amplify offers a comprehensive hosting solution for Next.js, merging the flexibility of AWS infrastructure with the simplicity of frontend-focused deployment.

— -

Prerequisites

Before we proceed, ensure:

- An AWS account is set up

  • Node.js and npm are installed
  • AWS CLI is configured with the right permissions
  • You’re familiar with Next.js, AWS CDK, and pnpm (a fast, disk-space efficient package manager)

— -

Setting Up the Next.js Starter Kit

Kickstart your Next.js app:

1. Initialize a new project: npx create-next-app nextjs-cdk-app 2. Enter the project directory: cd nextjs-cdk-app

Introducing AWS CDK and Its Key Concepts

The AWS Cloud Development Kit (CDK) lets us define cloud resources using familiar programming languages. In this guide, we’re using TypeScript. Here are some elements you should be familiar with:

- App: The main unit where cloud components are defined.

  • Stack: A collection of AWS resources.
  • BuildSpec: A collection of build commands and related settings.

Configuring the AWS CDK Stack for Deployment

Here’s the meat of our deployment: the CDK Stack. Below is an outline of the steps our provided code will execute:

  1. Define Role: The stack starts by defining a role for Amplify. This role has permissions to manage resources for our application. Note: This role is the role for Amplify’s build, and not the runtime role!
  2. Source Code Provider: We’re linking our application to a GitHub repository. The GitHub token is stored securely in AWS Secret Manager. Each code change will trigger a rebuild and automatically update.
  3. Amplify App Creation: Here, we configure and create our Amplify app.
  4. Main Branch Attachment: We attach our main branch and define its settings.
import {App, AutoBranchCreation} from '@aws-cdk/aws-amplify-alpha';
import {CfnOutput, SecretValue, Stack, StackProps} from 'aws-cdk-lib';
import {BuildSpec} from 'aws-cdk-lib/aws-codebuild';
import {Construct} from 'constructs';
import {ManagedPolicy, Role, ServicePrincipal} from 'aws-cdk-lib/aws-iam';
import {GitHubSourceCodeProvider} from '@aws-cdk/aws-amplify-alpha/lib/source-code-providers';
 
import {environmentVariables} from './environmentVariables';
import {CfnApp, CfnBranch} from "aws-cdk-lib/aws-amplify";
import {configuration} from "./config";
 
export const autoBranchCreation: AutoBranchCreation = {
  autoBuild: true,
  patterns: ['feature/*'],
  pullRequestPreview: true,
};
 
export const buildSpec = BuildSpec.fromObjectToYaml({
  version: '1.0',
  applications: [
    {
      appRoot: "source",
      frontend: {
        phases: {
          preBuild: {
            commands: [
              // Install the correct Node version, defined in .nvmrc
              'nvm use',
              // Install pnpm
              'corepack enable',
              'corepack prepare pnpm@latest --activate',
              // Avoid memory issues with node
              'export NODE_OPTIONS=--max-old-space-size=8192',
              'pnpm install --virtual-store-dir ./node_modules/.pnpm',
              // Ensure node_modules are correctly included in the build artifacts
              'pnpm install',
            ],
          },
          build: {
            commands: [
              // Allow Next.js to access environment variables
              // See https://docs.aws.amazon.com/amplify/latest/userguide/ssr-environment-variables.html
              `env | grep -E '${Object.keys(environmentVariables).join('|')}' >> .env.production`,
              // Build Next.js app
              'pnpm next build --no-lint',
            ],
          },
        },
        artifacts: {
          baseDirectory: '.next',
          files: ['**/*'],
        },
      },
    },
  ],
});
export class AmplifyStack extends Stack {
  constructor(scope: Construct, id: string, props: StackProps) {
    super(scope, id, props);
 
    const role = new Role(this, 'AmplifyRoleWebApp', {
      assumedBy: new ServicePrincipal('amplify.amazonaws.com'),
      description: 'Custom role permitting resources creation from Amplify',
      managedPolicies: [ManagedPolicy.fromAwsManagedPolicyName('AdministratorAccess-Amplify')],
    });
 
    const sourceCodeProvider = new GitHubSourceCodeProvider({
      // GitHub token should be saved in a secure place, we recommend AWS Secret Manager:
      oauthToken: SecretValue.secretsManager(configuration.accessTokenName), // replace GITHUB_TOKEN_KEY by the name of the Secrets Manager resource storing your GitHub token
      owner: configuration.repoOwner,
      repository: configuration.repoName
    });
 
 
 
    const autoBranchDeletion = true;
 
    // Define Amplify app
    const amplifyApp = new App(this, 'AmplifyAppResource', {
      appName: configuration.repoName,
      description: 'NextJS APP deployed with Dev-kit',
      // ⬇️ configuration items to be defined ⬇️
      role,
      sourceCodeProvider,
      buildSpec,
      autoBranchCreation,
      autoBranchDeletion,
      environmentVariables,
      // ⬆️ end of configuration ⬆️
    });
 
    const cfnApp = amplifyApp.node.defaultChild as CfnApp;
    cfnApp.platform = 'WEB_COMPUTE';
 
    // Attach your main branch and define the branch settings (see below)
    const mainBranch = amplifyApp.addBranch('main', {
      autoBuild: false, // set to true to automatically build the app on new pushes
      stage: 'PRODUCTION',
    });
 
    const cfnBranch = mainBranch.node.defaultChild as CfnBranch;
    cfnBranch.framework = 'Next.js - SSR';
 
    new CfnOutput(this, `${configuration.repoName}-appId`, {
      value: amplifyApp.appId,
    });
 
    // const domain = amplifyApp.addDomain('your-domain.com', {
    //   autoSubdomainCreationPatterns: ['feature/*'],
    //   enableAutoSubdomain: true,
    // });
    //
    // domain.mapRoot(mainBranch);
  }
}

Deploying with AWS Amplify

With our stack defined, here’s the step to deploy:

First, create a configuration file in config.ts

    export const configuration = {
      accessTokenName: "__ACCESS_TOKEN__",
      repoOwner: "__REPO_OWNER__",
      repoName: "__REPO_NAME__",
    }
 
    import * as cdk from 'aws-cdk-lib';
 
    import { AmplifyStack } from './stack';
    import {configuration} from "./config";
 
    const app = new cdk.App();
 
    new AmplifyStack(app, `NextjsStack-${configuration.repoName}`, {
      description: 'Cloudformation stack containing the Amplify configuration',
    });

Deploy the CDK stack: cdk deploy

AWS Amplify will then pick up the changes and initiate the build and deployment process.

Tips for Effective Maintenance

  • Regularly Update: Ensure Next.js, AWS packages, and pnpm are up to date.
    • Monitoring: Use AWS CloudWatch to keep an eye on your application.
    • Backups: Regularly backup your app and database.
    • AWS Best Practices: Stay updated. AWS frequently revises its best practices.

Conclusion

Kudos! You’ve now deployed a Next.js app on AWS Amplify using AWS CDK. With this robust foundation, the opportunities are endless. You can find the complete code here

GitHub - devkit-io/nextjs-amplify-starter-kit

Simplified Deployment

If you’re feeling a bit lost with the CDK deployment steps, check out dev-kit.io. Dev-kit will automate the entire starter kit setup and deployment, giving a one-click template deploy into your AWS account, completely free.

Get started today!

By Cole Murray on October 16, 2023.