Bootstrap a React App on CloudFront
This guide walks through starting a new React single-page application (SPA) and getting it live on Bymiljøetaten's CloudFront + S3 platform.
What you end up with
- A React + TypeScript app scaffolded with Vite, running locally.
- An S3 bucket and CloudFront distribution provisioned through Terraform, with HTTPS and DNS handled automatically.
- A GitHub Actions workflow that builds the app and deploys it to S3 on every push.
Step 1: Scaffold the app
Vite sets up a working dev server and build pipeline with one command.
Replace kattehotell with your application's name.
npm run dev starts a local dev server with live reload. Open the printed
URL in a browser to confirm the starter page loads before continuing.
Step 2: Commit the app to a new repository
Push the scaffolded app to a new GitHub repository under BYM-IKT. This is
the application repository. It holds the React source code and stays
separate from the Terraform Infrastruktur-repo you configure next.
Step 3: Provision the infrastructure
Follow Step 1: Provision the S3 bucket and CloudFront distribution and Step 2: Request pipeline access from Team Cloud in the S3 bucket + CloudFront guide.
Step 4: Add the deploy workflow
In your application repository, create
.github/workflows/deploy-to-test.yml:
.
├── .github/
│ └── workflows/
│ └── deploy-to-test.yml
├── src/
│ ├── ...
│ └── main.tsx
├── ...
├── package.json
└── index.html
with the following content:
name: Build and deploy website to TEST
on:
push:
branches: [main]
env:
APP_URL: https://kattehotell.test.bymoslo.net # Replace with your application URL without quotes
AWS_ACCOUNT_ID: "<<AWS_ACCOUNT_ID>>"
S3_BUCKET_NAME: "<<S3_BUCKET_NAME>>"
PROJECT_DIRECTORY: "."
BUILD_PATH: "dist"
jobs:
deploy-to-test:
name: Build app and deploy to TEST
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
environment:
name: testing
url: ${{ env.APP_URL }}
steps:
- name: Check out repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- name: Setup up Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: 24
- name: Clean install dependencies
run: npm ci
- name: Build
env:
CI: false
run: npm run build
- name: Upload to S3 and invalidate CloudFront cache
uses: BYM-IKT/github-actions/upload-to-s3-and-invalidate-cloudfront@master
with:
aws-account-id: ${{ env.AWS_ACCOUNT_ID }}
s3-bucket-name: ${{ env.S3_BUCKET_NAME }}
build-directory: ${{ env.PROJECT_DIRECTORY }}/${{ env.BUILD_PATH }}
cloudfront-distribution-domain-name: ${{ env.APP_URL }}
Replace <<AWS_ACCOUNT_ID>> with your AWS account ID and <<S3_BUCKET_NAME>>
with the bucket name from Step 1 of the S3 bucket + CloudFront guide.
BUILD_PATH is set to dist because that's Vite's default build output
directory. Confirm this matches build.outDir in vite.config.ts if you
changed it.
Step 5: Push and verify
Push to main. The workflow builds the app and uploads it to S3, then
invalidates the CloudFront cache.
- Open the Actions tab in the application repository and confirm the workflow run succeeds.
- Visit
APP_URLfrom the workflow and confirm the app loads.
Common additions
BYM React apps commonly add these packages once the app is live:
| Design system | @oslokommune/punkt-react and @oslokommune/punkt-css provide the Oslo municipality's shared UI components. |
| Routing | react-router for client-side navigation between pages. |
| Sign-in | react-oidc-context and oidc-client-ts for authenticating users against OneLogin. If the app calls a protected API, see protecting API Gateway routes with a JWT authorizer for the matching backend setup. |
| HTTP client | axios for calling backend APIs. |
| Data fetching | @tanstack/react-query for caching and syncing server state. |
| Forms | react-hook-form for managing form state and validation. |
| Validation | zod for validating API responses and form input. |