Front-End Web & Mobile
Deploy a NextJS 13 application to Amplify with the AWS CDK
Modern application development often includes features such as authentication, API setup, and file storage. In a previous post we saw how AWS Amplify and the AWS CDK manage the undifferentiated heavy-lifting of standing up these services.
However, without a hosting platform your customers would never see your product. Fortunately, AWS Amplify Hosting is a platform that manages DNS routing, instant cache invalidation, managing staging environment, CI/CD with Git providers, and more.
While one can certainly click through the console to this up, in this post, we’ll use the AWS CDK to deploy a frontend using the NextJS React framework. In addition, we’ll pass environment variables from the backend to our frontend, so they can make use of them using the Amplify JavaScript libraries.
Application Overview
As mentioned, the starter application will contain a backend with managed services that most fullstack application would benefit from:
- Amazon Cognito: This will provide both user sign in with user pools, as well as setting service authorization via identity pools
- AWS AppSync: Our managed GraphQL API that offers real-time support via WebSockets
- Amazon DynamoDB: NoSQL database for persisting data
- Amazon CloudFront: Asset caching for the files stored in our S3 bucket
- Amazon Simple Storage Service (S3): File storage. Public images are delivered via CloudFront while protected content is served with a pre-signed URL.
To get started with this repository, clone this GitHub repo and run the following commands while in the project’s directory :
npm install
Once the packages are installed, if not already done so, open up the project in your code editor.
Stack setup
The existing service stacks are located in the bin/cdk-kitchen-sink.ts
directory. While each stack has their own implementation details that you can view, by changing the values in this project, consumers are able to easily port the application to fit their own needs.
In our case, we’ll create our AmplifyHostingStack
such that it expects certain variables to successfully deploy an existing frontend repo to the Amplify Hosting service:
// bin/cdk-appsync-images.ts
//… other stack files
const amplifyHostingStack = new AmplifyHostingStack(app, 'HostingStack', {
githubOauthTokenName: 'github-token',
owner: ‘your-user-name',
repository: 'your-frontend-repo-name’,
environmentVariables: {
USERPOOL_ID: authStack.userpool.userPoolId,
GRAPHQL_URL: apiStack.graphqlURL,
},
})
While we’ll have to create a file that makes use of this file, we’ll create it in such a way that these values are all that will be required.
It makes sense that to deploy a frontend repository from GitHub to Amplify Hosting, we’ll have to provide the repository name, owner, and a reference to the GitHub Auth Token (more on this below). In addition, we can also pull in values from the previous stacks and populate them as environment variables here.
It’s worth noting that environment variables are encrypted and at rest and will be available to frontend applications after a build via the process.env property.
Understanding our configuration
Now that we understand how we’d like to instantiate our stack, let’s actually create it.
Create a new file called NextjsHostingStack.ts
and add in the following:
import { CfnOutput, SecretValue, Stack, StackProps } from 'aws-cdk-lib'
import { Construct } from 'constructs'
import * as codebuild from 'aws-cdk-lib/aws-codebuild'
import {
App,
GitHubSourceCodeProvider,
RedirectStatus,
} from '@aws-cdk/aws-amplify-alpha'
interface HostingStackProps extends StackProps {
readonly owner: string
readonly repository: string
readonly githubOauthTokenName: string
readonly environmentVariables?: { [name: string]: string }
}
The snippet above represents the imports we’ll being make use of in this file and defines the props that are allowed to be passed in (note that these match what we defined in the bin
directory).
This project makes use of the alpha CDK construct for AWS Amplify. This was installed when running npm installed.
Next we’ll create our stack:
export class AmplifyHostingStack extends Stack {
constructor(scope: Construct, id: string, props: HostingStackProps) {
super(scope, id, props)
const amplifyApp = new App(this, 'AmplifyCDK', {
appName: 'NextJS app from CDK',
sourceCodeProvider: new GitHubSourceCodeProvider({
owner: props.owner,
repository: props.repository,
oauthToken: SecretValue.secretsManager(props.githubOauthTokenName),
}),
autoBranchDeletion: true,
})
}
}
This creates our stack, and makes use of the App
construct to connect to the Amplify Hosting service. We’ll add more to the App
object, though for now, we’re setting GitHub as the source code provider. This will enable CI/CD on the frontend repo. Note we are also setting autoBranchDeletion
to true. This means whenever a connected branch is deleted on GitHub, it will automatically be deleted in Amplify Hosting.
Next, we’ll set a routing rule that essentially says, “if a user goes to a page that is not present, redirect them with a 404 (Not Found) error”. We’ll also allow our environment variables to be passed in.
Add the following:
customRules: [
{
source: '/<*>',
target: ' /index.html',
status: RedirectStatus.NOT_FOUND_REWRITE,
},
],
environmentVariables: props.environmentVariables,
Because of all the frontend frameworks out there, and all of their various build processes, we need to tell Amplify how we’d like our project to be built, what artifacts matter, and what should be cached to speed up future deployments.
This can be accomplished by providing a buildspec.yml
file to Amplify Hosting. Fortunately, because many developers prefer to not mix YAML with another language, the Amplify construct provides a handy utility that converts an object to YAML format for us.
Still in the configuration object of our app, add the following:
buildSpec: codebuild.BuildSpec.fromObjectToYaml({
version: 1,
frontend: {
phases: {
preBuild: {
commands: ['npm ci'],
},
build: {
commands: ['npm run build'],
},
},
artifacts: {
baseDirectory: '.next',
files: ['**/*'],
},
cache: {
paths: ['node_modules/**/*'],
},
},
})
That’s it for configuration. Now we have a reusable way of deploying a NextJS application.
When deployed, we can tell Amplify Hosting which branch is our Production branch. In addition, we can also log out the appId of our App once it’s deployed. To do this, add the following to the end of our stack file (still within the stack itself):
amplifyApp.addBranch('main', {
stage: 'PRODUCTION',
})
new CfnOutput(this, 'appId', {
value: amplifyApp.appId,
})
Creating and Storing a GitHub Secret
As mentioned, our application needs permissions to pull our frontend repo and build our project. To create a new token for your GitHub account, click this link.
For the permissions, select the admin:repo_hook
permission set. Give your token a name and after clicking the button to create the token, copy the generated token to your clipboard.
We’ll store this secret in AWS Secrets Manager. Feel free to use the AWS CLI to create your secret, however this section will use the AWS Console.
In Secrets Manager, select “Store a new secret” to be presented with the following screen:
As seen in the above screenshot, select the option for “Other type of secret” and click the tab to enter the secret as “Plaintext” instead of a Key/value.
On the next screen, give your secret a name that matches what we specified in our HostingStack. In this case, the name should be github-token
Deploy
To test out our solution, create a new NextJS application using the following command:
npx create-next-app simple-nextjs
Once created, store the repository on GitHub and make sure the owner
and respository
fields match on our backend bin/cdk-kitchen-sink.ts
file.
Once set, to deploy our application, run the following command:
npx aws-cdk deploy --all
Cleanup
To remove the backend resources that were created, run the following command while in our backend repo:
npx aws-cdk destroy --all
In addition, remove the secret key stored in Secrets Manager incurring additional costs.
Conclusion
In this post we created a reusable CDK stack that deploys a NextJS 13 application to Amplify Hosting. This allows frontend applications to remain service consumers while also speeding up deployment times. To support NextJS 13 application, we also configured the platform to use the new WEB_COMPUTE
property. This enables faster deployment times, more features and increased reliability. To learn more about Amplify Hosting and the newly updated NextJS features, refer to AWS Amplify Hosting docs page.