$npx -y skills add ziniman/ai-instruct --skill aws-spa-deployUse this skill whenever the user is deploying a React/Vite single-page app to AWS, or mentions Amplify, CDK, or wiring up Lambda + API Gateway for a frontend. Covers Amplify hosting, custom domains, CDK backend (Lambda + API Gateway), SES email, CORS configuration, and environmen
| 1 | # Deploying a Static SPA on AWS |
| 2 | |
| 3 | > Applies to: React/Vite SPAs on AWS (Amplify + CDK) | Updated: February 2026 |
| 4 | |
| 5 | A focused guide for deploying a React/Vite single-page app on AWS with optional API backend using CDK (Cloud Development Kit). Covers Amplify hosting, Lambda + API Gateway, and SES email. |
| 6 | |
| 7 | ## Contents |
| 8 | |
| 9 | 1. [Before you start](#before-you-start) |
| 10 | 2. [Architecture](#architecture) |
| 11 | 3. [Costs at a glance](#costs-at-a-glance) |
| 12 | 4. [Amplify Hosting](#amplify-hosting) |
| 13 | 5. [CDK Backend](#cdk-backend) |
| 14 | 6. [SES Email](#ses-email) |
| 15 | 7. [Common problems](#common-problems) |
| 16 | |
| 17 | --- |
| 18 | |
| 19 | ## Before you start |
| 20 | |
| 21 | Answer these questions before generating any code. Each has a default assumption - confirm or override before proceeding. |
| 22 | |
| 23 | **Q: Do you need a backend API, or is this a static site with no server logic?** |
| 24 | Default: static only - if yes, skip the entire CDK section. |
| 25 | |
| 26 | **Q: Do you have a custom domain name you want to use?** |
| 27 | Default: no - skip the domain and SSL subsections. |
| 28 | |
| 29 | **Q: What is your target AWS region, and does it match your default AWS CLI region?** |
| 30 | Default: us-east-1. Check with `aws configure get region`. If they differ, pass `--region` explicitly to every CLI command and use the explicit bootstrap form (see CDK section). |
| 31 | |
| 32 | **Q: Do you need to send emails from the site (contact forms, notifications)?** |
| 33 | Default: no - skip the SES section entirely. |
| 34 | |
| 35 | **Q: Is this a brand new AWS account?** |
| 36 | Default: no (existing account). If new: SES starts in sandbox mode (can only send to verified addresses), and CDK bootstrap is required before any deploy. |
| 37 | |
| 38 | **Q: What is the expected traffic?** |
| 39 | Default: low (a few visitors per day) - free tier covers this entirely. See cost table below. |
| 40 | |
| 41 | > **AI assistant:** If the user only needs static hosting, generate only the Amplify Hosting section. Only include CDK/Lambda/SES content if they confirm they need a backend. |
| 42 | |
| 43 | --- |
| 44 | |
| 45 | ## Architecture |
| 46 | |
| 47 | ``` |
| 48 | Static Site (Amplify = S3 + CloudFront) |
| 49 | | |
| 50 | +--> API Gateway (HTTP API) --> Lambda --> SES / DynamoDB / etc. |
| 51 | OR |
| 52 | +--> Lambda Function URL --> Lambda --> SES / DynamoDB / etc. |
| 53 | ``` |
| 54 | |
| 55 | - Static site hosted on **Amplify** (auto-deploys on git push, CDN via CloudFront) |
| 56 | - Backend in a separate **CDK stack** deployed independently |
| 57 | - `VITE_*` environment variables bridge frontend to backend (baked into the JS bundle at build time, not available at runtime) |
| 58 | |
| 59 | **This guide uses Amplify Gen 1 (console-based) for static hosting only.** If the Amplify console shows a code-first Gen 2 setup (with an `amplify/` directory in the repo), the hosting and SPA rewrite rules still apply the same way, but the backend configuration sections differ from what is described here. |
| 60 | |
| 61 | --- |
| 62 | |
| 63 | ## Costs at a glance |
| 64 | |
| 65 | | Service | Free tier | What triggers billing | |
| 66 | |---|---|---| |
| 67 | | Amplify Hosting | 1,000 build minutes/month, 5 GB storage, 15 GB served | Exceeding any of those limits | |
| 68 | | Lambda | 1M requests/month, 400,000 GB-seconds compute | High request volume or large memory × long duration | |
| 69 | | API Gateway (HTTP API) | 1M requests/month for 12 months | After 12 months or >1M/month | |
| 70 | | Lambda Function URL | Same as Lambda - no API Gateway charge | High Lambda invocations | |
| 71 | | SES | 62,000 emails/month when sending from EC2/Lambda | Dedicated IPs, high volume beyond free tier | |
| 72 | | ACM SSL cert | Free | Never (ACM certs are always free) | |
| 73 | |
| 74 | For a typical low-traffic SPA (contact form, a few hundred visitors/month), the effective monthly cost is $0. |
| 75 | |
| 76 | --- |
| 77 | |
| 78 | ## Amplify Hosting |
| 79 | |
| 80 | 1. Create an Amplify app in the console and connect your git repo. |
| 81 | 2. Amplify auto-detects Vite and sets the build command to `npm run build` with output directory `dist`. Verify this in the build settings. |
| 82 | 3. Add a **SPA rewrite rule** under App settings > Rewrites and redirects: |
| 83 | - Source: `/<*>` → Target: `/index.html` → Type: `404-200` |
| 84 | - The type must be `404-200` (not 301 or 302). A redirect would cause the browser to navigate to `/index.html` on every deep link, breaking the URL. A rewrite silently serves `index.html` while keeping the original URL. |
| 85 | 4. If you want `www` to redirect to apex: add `https://www.yourdomain.com` → `https://yourdomain.com` with type `301`. |
| 86 | |
| 87 | ### Custom Domain and SSL |
| 88 | |
| 89 | Add your domain under App settings > Domain management. Amplify provisions an SSL certificate via ACM (AWS Certificate Manager) automatically. |
| 90 | |
| 91 | **ACM cert region gotcha:** The certificate is always provisioned in `us-east-1` regardless of your Amplify app's region. CloudFront requires certificates in `us-east-1`. If you chec |