Prerequisites
- AWS CLI configured with valid credentials
- pnpm installed
- jq installed
- CloudFormation stack
AllianceRiskStackalready deployed
Quick Start
From the project root:scripts/deploy-web.sh, which handles the full deployment pipeline.
Deployment Pipeline
Thedeploy-web.sh script performs the following steps:
1. Fetch Stack Outputs
The deployment script queries CloudFormation stack outputs to get:WebBucketName— S3 bucket for static filesCloudFrontDistributionId— CloudFront distribution ID for cache invalidationApiUrl— API Gateway endpoint URLCognitoUserPoolId— Cognito User Pool IDCognitoClientId— Cognito App Client ID
2. Set Build-Time Environment Variables
Next.js bakesNEXT_PUBLIC_* environment variables into the static build:
3. Build Static Export
packages/web/out/:
packages/web/next.config.ts):
?id=xxx) instead of dynamic [id] routes.
4. Sync Static Assets to S3 (Immutable Cache)
Files with content-hashed filenames (JS, CSS, images) are uploaded with a 1-year cache:max-age=31536000— 1 year (365 days)immutable— Browser never revalidates (saves bandwidth)
_next/static/chunks/app-123abc.js. When code changes, the hash changes, so old files are never served.
5. Sync HTML/JSON to S3 (No Cache)
HTML and JSON files are uploaded with no caching to always serve the latest version:max-age=0— Don’t cache locallymust-revalidate— Always check with server
.txt Files: Next.js App Router generates .txt files for RSC (React Server Components) payloads. These are used for client-side navigation.
6. Create CloudFront Invalidation
CloudFront caches files at edge locations. After uploading new files, we invalidate the entire cache:/* counts as 1 path.
CloudFront Configuration
URL Rewrite Function
Next.js static export generates.html files (/login.html, /dashboard.html), but users navigate to extensionless URLs (/login, /dashboard).
A CloudFront Function rewrites requests:
- User navigates to
/login→ CloudFront requests/login.htmlfrom S3 - User navigates to
/dashboard/→ CloudFront requests/dashboard.htmlfrom S3 - User requests
/_next/static/chunks/app-123abc.js→ No rewrite (has extension)
Error Responses (SPA Routing)
CloudFront is configured to serveindex.html for 403/404 errors:
/dashboard?id=123), CloudFront doesn’t find /dashboard?id=123.html in S3 and returns 404. We intercept this and serve index.html, which boots the React app and handles the route.
Error Caching: ErrorCachingMinTTL: 0 ensures 404s are not cached (important during development).
Build-Time Environment Variables
These variables are baked into the JavaScript bundle:| Variable | Source | Used By |
|---|---|---|
NEXT_PUBLIC_API_URL | CloudFormation output ApiUrl | API client (lib/api-client.ts) |
NEXT_PUBLIC_COGNITO_USER_POOL_ID | CloudFormation output CognitoUserPoolId | Cognito auth (lib/cognito.ts) |
NEXT_PUBLIC_COGNITO_CLIENT_ID | CloudFormation output CognitoClientId | Cognito auth (lib/cognito.ts) |
packages/web/src/lib/api-client.ts):
Deployment Order
When deploying from scratch:Troubleshooting
Build did not produce out/ directory
Cause: next.config.ts is missing output: 'export'
Solution: Ensure next.config.ts has:
CloudFront serves old content after deployment
Cause: Invalidation not yet propagated (takes 1-2 minutes) Solution: Wait 1-2 minutes, or check invalidation status:404 on client-side routes
Cause: CloudFront error responses not configured Solution: Ensure the CloudFormation template hasCustomErrorResponses for 403/404 → /index.html
API calls fail with CORS errors
Cause:NEXT_PUBLIC_API_URL not set or incorrect
Solution: Verify the build logs show the correct API URL:
Authentication fails (Cognito errors)
Cause:NEXT_PUBLIC_COGNITO_USER_POOL_ID or NEXT_PUBLIC_COGNITO_CLIENT_ID not set or incorrect
Solution: Verify the build logs show the correct Cognito IDs:
.txt files return 404 in browser (but work in Next.js)
Expected behavior. The CloudFront Function blocks direct browser navigation to .txt files and serves .html instead. Next.js client-side navigation includes the RSC: 1 header, so .txt files work correctly during navigation.
Performance Optimization
- CloudFront CDN: Global edge locations reduce latency
- Gzip Compression: Enabled by default on CloudFront
- Immutable Cache: 1-year cache for hashed assets (JS, CSS, images)
- HTTP/2: Enabled for multiplexing
- Price Class 100: Cheaper edge locations (North America + Europe)
Security Best Practices
- HTTPS Only: CloudFront redirects HTTP → HTTPS
- S3 Private: Bucket accessible only via CloudFront OAI (Origin Access Identity)
- No Secrets in Frontend: All sensitive data (DB credentials, API keys) lives in the backend
- CORS: API Gateway configured to allow requests from CloudFront origin
Cache Strategy Summary
| File Type | Cache-Control | Why |
|---|---|---|
*.js, *.css, *.png, etc. | public, max-age=31536000, immutable | Content-hashed filenames, never change |
*.html, *.json, *.txt | public, max-age=0, must-revalidate | Always serve latest version |