For applications that display unique data per user, or render views frequently based on changing data, I’ve found SPAs to be a valuable tool. A SPA will perform most view logic in the client browser, and communicate with servers via web APIs to fetch or change data.
There are use cases for both static websites (like this blog), and SPAs (Single Page Applications). For a marketing site, landing page, or blog, a simple static website may be the best tool to deploy. The highlight of this use case is that the content of the site isn’t expected to frequently change, or be unique per user. It’s static!
It’s worth mentioning that there are other options to deliver features via the web. A traditional web application differs from a SPA by managing view logic on a web server. Round trips between a client browser and the web server happen on page changes, as view templates are hydrated and rendered on the web server. (SPAs and web apps are similar in that they’ll both fetch and update data from a web server after pages have loaded. SPAs accomplish this with API calls, and some client-side data fetching or state management tool such as Redux or Apollo. Web apps accomplish this with AJAX.)
The web dev ecosystem gets even more complicated with SSR (Server-Side Rendering), blurring the lines between a SPA and a traditional web application. That’s a topic for another day.
SPA Design Choices
React is my library of choice for building SPAs. In a future post, I’ll go into more detail about application design patterns, state management, data fetching, and UI frameworks. For this post, the goal is to deploy a SPA to the web! So this will be another AWS heavy post.
Speedrun: Create and Deploy a React SPA to AWS
I first created a private git repository on GitHub, and cloned it on my PC.
In that folder, I generated a React app in TypeScript language using Create React App, with one of the following commands
npx create-react-app . --template typescript
npx create-react-app my-app --template redux-typescript
I tweaked a few small details, like the page title, favicon, and some content on the page itself. But I largely left the output of CRA alone - I’ll develop the app later. With a DevOps, CI/CD, or even Product-Led-Growth mindset, I want to start being more aggressive pushing early progress out to a deploy endpoint. Iteration and cool features will come later.
The next step was to create an AWS S3 bucket provisioned for serving a website, as outlined in this walkthrough.
Since I wanted to use a subdomain, opsadmin.bfostdev.net, I named the S3 bucket opsadmin.bfostdev.net.
I then created a new SSL Certificate in AWS Certificate Manager.
Next, I created a Cloudfront Distribution, using the S3 bucket opsadmin.bfostdev.net.
I created an Origin Access Identity for the Cloudfront Distribution, and updated the S3 bucket policy to only allow that principal/identity access. This essentially locks the S3 bucket down, forcing traffic through the Cloudfront Distribution, which is what we want.
Finally, I updated the Route 53 DNS, adding an A record for opsadmin.bfostdev.net, pointing to the Cloudfront Distribution.
You can access the new SPA at https://opsadmin.bfostdev.net! This begs the question, what is opsadmin? That’s for a future post.
Deploying changes, and a note on CI/CD
Now, I have a React app that I can source control with Git, develop locally, and deploy/host in an AWS S3 bucket. While I will eventually want to implement something like Github Actions or AWS CodeBuild, along with a branching strategy like Atlassian Gitflow, I’m going to develop the opsadmin app as a solo contributor. This gives me a bit of leeway to be lazy with branching strategy and CI/CD for the time being. And in fact, being lazy or doing things semi-manually will inform my future CI/CD design choices.
So, here are the commands that I’ll run from my local PC when I have changes to the React app that I want to deploy. These commands
- build/compile/transpile the React app code into the minified, production-ready set of static html/css/js files
- pushes the static files to the AWS S3 bucket
- invalidates the CloudFront CDN cache, allowing my changes to quickly propagate out to the edge, aka, visitors to the site!
npm run build
aws s3 sync --delete ./build s3://$BUCKET_NAME
aws cloudfront create-invalidation --distribution-id $DISTRIBUTION_ID --paths /*