Table of contents
In this blog post, we will create an awesome React TypeScript App hosted in an S3 static website bucket. For that, we will use AWS CDK and Projen.
But first I will explain what AWS CDK and Projen are and why I strongly recommend using Projen for AWS CDK.
Then we dive into creating the React App which will be afterward deployed in an S3 bucket with AWS CDK.
AWS CDK
AWS CDK (GitHub) stands for Cloud Development Kit and is an open-source framework for creating and managing AWS resources. By using languages familiar to the developer such as TypeScript or Python, the infrastructure is described as code. In doing so, CDK synthesizes the code into AWS Cloudformation Templates and can optionally deploy them right away.

AWS CDK has been experiencing a steady increase in enthusiastic developers since 2019 and already has a strong and helpful community that is very active on Slack.
There is of course much more to say about AWS CDK and I recommend you to explore it.
Projen
Projen allows sophisticated management of project configuration files through code. With just a few lines of typed JavaScript code, an entire repository can be configured.
Here is an example:
const { AwsCdkTypeScriptApp } = require('projen');
const project = new AwsCdkTypeScriptApp({
authorAddress: 'damadden88@googlemail.com',
authorName: 'martin.mueller',
cdkVersion: '1.99.0',
cdkVersionPinning: true,
defaultReleaseBranch: 'main',
name: 'cdk-s3-website',
});
project.synth();
These few lines create all the GitHub project files your heart desires.
Among them the package.json
, .gitignore
, tsconfig.json
and many many more.
Projen emerged out of the AWS CDK community and contains helpful tools for creating AWS CDK Apps.
Why using Projen instead of CDK directly?
Projen is a lightweight wrapper around CDK so you are in fact using CDK under the hood. Furthermore it helps you with managing dependencies like those from AWS CDK. That’s a huge help as managing those dependencies in a package.json yourself is very time consuming and not pleasant. You will understand what I mean when we start with coding.

Projen is widely used in the CDK Community and I strongly recommend you trying it.
Here is an awesome-projen list from existing Projen projects.
I really love Projen and use it a lot for my private projects and my work with clients. It lets me iterate and try out AWS CDK code very fast and after using Projen for many month it still feels like cheating <3. As an AWS cloud developer I want to run stuff quickly and not bother wit setting up other stuff like the repository.
It helps me to understand better and faster repos from other creators if they are using Projen for there project. So it becomes a kind of standard.
If you want to know more about Projen have a look into my other Projen posts.
Coding Preparations
Before we start coding we need to install the CDK. Find the concrete instructions here but it is easy just as:
npm i -g aws-cdk
For Projen I recommend setting up an alias as described here as you will run the Projen command quite a lot. Simply write in you shell profile:
alias pj='npx projen'
Cool now you just need to run pj
if you want to update the repository.
Let’s do it
Create and checkout a new repository in GitHub or any other git provider. I will take Github. Navigate into the project and open it with VS Code. I usually do:
cd cdk-s3-website
code .
Init the CDK Project
First lets checkout which project types it offers us:
pj new
(or npx projen new
if you didn’t do the alias)
It should show you a list of supported project types like those:
projen new awscdk-app-ts AWS CDK app in TypeScript.
projen new awscdk-construct AWS CDK construct library project.
projen new cdk8s-app-ts CDK8s app in TypeScript.
projen new cdk8s-construct CDK8s construct library project.
projen new java Java project.
projen new jsii Multi-language jsii library project.
projen new nextjs Next.js project without TypeScript.
projen new nextjs-ts Next.js project with TypeScript.
projen new node Node.js project.
projen new project Base project.
projen new python Python project.
projen new react React project without TypeScript.
projen new react-ts React project with TypeScript.
projen new typescript TypeScript project.
projen new typescript-app TypeScript app.
Cool or? I think its pretty cool to use Projen for those kinds of projects as it does all the setup for me and I don’t need to figure out each specific way of installing them natively.
Anyway we want to create an AWS CDK app so lets choose the first project type:
pj new awscdk-app-ts
That takes a bit but oh boy look at all those files it added for us like .gitignore
, package.json
or the tsconfig.json
. They all originated from defaults and what is defined in the .projenrc.js
file.
Yep, the .projenrc.js
file will become very important as it defines the setup of your project. Take your time and explore all those created files a bit.
What I really love about this approach is that those properties in the .projenrc.js file are typed. So with an IDEA like VS Code you just can hover over them ore jump to the definition and you the the documentation about the use of the property.
Let’s tweak the .projenrc.js
and see what happens.
I override the project properties and deleted the commented out ones:
const project = new AwsCdkTypeScriptApp({
authorAddress: 'damadden88@googlemail.com',
authorName: 'martin.mueller',
cdkVersion: '1.99.0',
cdkVersionPinning: true,
defaultReleaseBranch: 'main',
name: 'cdk-s3-website',
});
You see I did choose a newer CDK version. At the time of the writing 1.99
was the highest CDK version. If there should be a newer one take that!
Save the change and run:
pj
It will synth your requested changes from the .projenrc.js
file with like upgrading the CDK version in the package.json
file. After that, it conveniently installs those packages.
In a later section, we will bring in other CDK dependencies like creating an S3 bucket with enabled website hosting. Those will be managed with Projen as well. But before let’s create the React TypeScript App.
React TS App
So for setting up the React TypeScript App we can use Projen as well. We are starting with bringing in the web
library and defining the React TypeScript App as child project from our CDK App:
const { AwsCdkTypeScriptApp, web } = require('projen');
const project = new AwsCdkTypeScriptApp({
authorAddress: 'damadden88@googlemail.com',
...
});
project.synth();
const frontendProject = new web.ReactTypeScriptProject({
defaultReleaseBranch: 'main',
outdir: 'frontend',
parent: project,
name: 'cdk-s3-website',
});
The React App project will be generated in the subfolder called frontend.
For convenience reasons lets implement a Projen script for locally running the react app:
frontendProject.addTask('dev', {
description: 'Runs the application locally',
exec: 'react-scripts start',
});
At the end we need to tell Projen to synth the frontend project as well:
frontendProject.synth();
The whole .projenrc.js
file should look something like this:
const { AwsCdkTypeScriptApp, web } = require('projen');
const project = new AwsCdkTypeScriptApp({
authorAddress: 'damadden88@googlemail.com',
authorName: 'martin.mueller',
cdkVersion: '1.99.0',
cdkVersionPinning: true,
defaultReleaseBranch: 'main',
name: 'cdk-s3-website',
});
project.synth();
const frontendProject = new web.ReactTypeScriptProject({
defaultReleaseBranch: 'main',
outdir: 'frontend',
parent: project,
name: 'cdk-s3-website',
});
frontendProject.addTask('dev', {
description: 'Runs the application locally',
exec: 'react-scripts start',
});
frontendProject.synth();
Super let synth everything with pj
.
Now we go to frontend and run:
yarn dev
If you run into this error:
There might be a problem with the project dependency tree.
It is likely not a bug in Create React App, but something you need to fix locally.
The react-scripts package provided by Create React App requires a dependency:
"jest": "26.6.0"
Don't try to install it manually: your package manager does it automatically.
...
That is because we basically have two projects configured the aws cdk app and the react app and both using a different version of the jest library. Do the following to ignore the error as it will do us no harm.
Create a file frontend/.env
with a one liner:
SKIP_PREFLIGHT_CHECK=true
Ok that was a bit nasty but blame React for it as it produced the error.
OK, let’s run:
yarn dev
If not open automatically you should see a local running React app on https://localhost:3000
Super!
One more thing as we are already in the frontend and folder.
Please run:
yarn build
as we will use the React build to deploy it to an S3 static website bucket in the next section :).
S3 Static Website
Now, lets do some real AWS CDK magic as this blog post is about AWS CDK and we did not use it so far (shame on me).
First we need to add some more CDK S3 dependencies. That is done in the .projenrc.js
file with:
const project = new AwsCdkTypeScriptApp({
...
cdkDependencies: [
'@aws-cdk/aws-s3-deployment',
'@aws-cdk/aws-s3',
],
});
Run
pj
And check the package.json
.
We brought in those two dependencies now and it picked up the defined CDK version cdkVersion: '1.99.0'
.
Let’s go to the src/main.ts
and define the S3 static website:
import * as s3 from '@aws-cdk/aws-s3';
import * as s3deploy from '@aws-cdk/aws-s3-deployment';
import { App, CfnOutput, Construct, RemovalPolicy, Stack, StackProps } from '@aws-cdk/core';
export class MyStack extends Stack {
constructor(scope: Construct, id: string, props: StackProps = {}) {
super(scope, id, props);
const siteBucket = new s3.Bucket(this, 'SiteBucket', {
websiteIndexDocument: 'index.html',
websiteErrorDocument: 'error.html',
publicReadAccess: true,
cors: [
{
allowedMethods: [s3.HttpMethods.GET, s3.HttpMethods.HEAD],
allowedOrigins: ['*'],
allowedHeaders: ['*'],
exposedHeaders: ['ETag', 'x-amz-meta-custom-header', 'Authorization', 'Content-Type', 'Accept'],
},
],
removalPolicy: RemovalPolicy.DESTROY,
autoDeleteObjects: true,
});
// Deploy site contents to S3 bucket
new s3deploy.BucketDeployment(this, 'BucketDeployment', {
sources: [s3deploy.Source.asset('./frontend/build')],
destinationBucket: siteBucket,
});
new CfnOutput(this, 'bucketWebsiteUrl', {
value: siteBucket.bucketWebsiteUrl,
});
}
}
// for development, use account/region from cdk cli
const devEnv = {
account: 123456789, // you aws account id
region: 'us-east-1',
};
const app = new App();
new MyStack(app, 'my-stack-dev', { env: devEnv });
// new MyStack(app, 'my-stack-prod', { env: prodEnv });
app.synth();
We defined an S3 static website bucket and told CDK to upload the build files from React in ./frontend/build
to it.
As you might noticed I used explicit defined deploy account information instead of those CDK_DEFAULT_
ones.
So instead of:
const devEnv = {
account: process.env.CDK_DEFAULT_ACCOUNT,
region: process.env.CDK_DEFAULT_REGION,
};
I used:
const devEnv = {
account: '123456789', // you aws account id
region: 'us-east-1',
};
This has couples of advantages like that you have you configuration as code and you don’t need to hide it in env variables during builds. As well CDK will make a check that your configured AWS credentials like in ~/.aws/credentials
are matching.
That prevents accidental deployment to the wrong account which is really helpful.
OK, you might think but you showing critical aws information with revealing the account id. I do challenge you and that is not true. With only seeing the account id you can not hack into the account. I am happy if you prove me otherwise.
Super!
Let’s deploy it.
Go to the root folder and do:
pj
yarn deploy
CDK will tell you which resources needs to be applied or updated to create the S3 bucket. Confirm it with y
.
When the deployment finished you will see the S3 URL like:
✅ my-stack-dev
Outputs:
my-stack-dev.bucketWebsiteUrl = http://my-stack-dev-sitebucket397a1860-6hzh1ardmfg0.s3-website-us-east-1.amazonaws.com
Open the URL.
Now you see your React TS App hosted in an S3 bucket.
How cool is that?!
What’s next?
Awesome, we set up a React TypeScript App in an S3 Static Website bucket. From here on we could start to make the React App good looking and create some nice components and routes. And lets bring in some more AWS Services for use in our backend. Like AWS Cognito for user provisioning and authentication.
With AWS Amplify it is very easy to integrate Cognito’s user authentication with our App as it comes with a nice Login UI and Cognito backend integration.

The S3 Static Website doesn’t use a Cloud Delivery Network (CDN) so far. A CDN keeps the latency when accessing your website low as it uses a network of servers around the world to cache your site. Setting up a CDN is easily done in AWS CDK.
You could set up an AWS API Gateway to use as a REST Server for our App or even cooler let’s do a GraphQL Server with AWS Appsync as I did here and here.
We hope, you’ve found this article useful! If so, please, help us to spread it to the world!
Stay tuned for the next blog post 🙂
Related articles
- How to use AWS CDK to deploy Python Lambda function
- Building Thumbnails And GIFs Generator Using Lambda And FFmpeg
- Serverless Framework – Building Web App using AWS Lambda Amazon API Gateway S3 DynamoDB and Cognito – Part 1
- How to install MERN stack on Ubuntu in the AWS cloud
- [The Ultimate Guide] – AWS Lambda Real-world use-cases
How useful was this post?
Click on a star to rate it!
We are sorry that this post was not useful for you!
Let us improve this post!
Tell us how we can improve this post?