feat: host frontend on S3 + CloudFront (static SPA), remove Amplify - #268
Merged
Conversation
The app is fully client-rendered (JWT in localStorage, all data via apiFetch), so switch Next to `output: 'export'` and host the static bundle on S3+CloudFront instead of Amplify SSR. - next.config.ts: output: 'export', trailingSlash, images.unoptimized; drop the dev rewrites (they don't run in export — prod routing is NEXT_PUBLIC_API_BASE_URL). - projects/[id]: export requires generateStaticParams in a Server Component, but the page is 'use client'. Split into a server wrapper (page.tsx, emits a throwaway shell) + ProjectDetailClient.tsx (the existing UI, reads id via useParams). Real /projects/:id is client-rendered; deep links resolve through the CloudFront SPA fallback (404 -> /index.html). Verified: `next build` exports out/ with index.html + per-route pages + projects/placeholder shell. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace Amplify SSR hosting (blocked indefinitely on the build's "Unable to assume specified IAM Role" quirk) with a static-SPA deploy that fits the app. - infrastructure/aws/frontend_hosting.tf: private S3 bucket + CloudFront (OAC), SPA fallback (403/404 -> /index.html), index-rewrite CloudFront Function, HTTPS via the default CloudFront cert. Outputs bucket + distribution id. - .github/workflows/frontend-deploy.yml: on push to apps/frontend/**, build the export with NEXT_PUBLIC_API_BASE_URL, s3 sync out/, invalidate CloudFront. Uses the OIDC apply role + production environment. - Remove amplify.tf + amplify_notifications.tf (apply tears down the Amplify app, SSR role, and EventBridge notification plumbing) + the now-unused api_base_url var. - Docs: root/frontend/infra/.github AGENTS.md updated to the static-hosting model. Depends on the OIDC roles (infrastructure/aws/oidc.tf) existing for the deploy workflow's apply role. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Auto-formatted .tf files with terraform fmt - Updated README.md with terraform-docs Co-authored-by: nourshoreibah <nourshoreibah@users.noreply.github.com>
Contributor
Terraform Plan 📖
|
nourshoreibah
marked this pull request as ready for review
July 1, 2026 04:06
Contributor
✅ Terraform Apply — succeeded1/1 workspaces applied Output |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Amplify SSR hosting is wedged on the build's
Unable to assume specified IAM Rolequirk (correct trust, regional principal, sts:TagSession — all tried, all fail; it's the documented Amplify-SSR-with-IaC-role issue). The app is actually a client-side SPA (all'use client', JWT in localStorage, data viaapiFetch) — SSR buys nothing. So drop Amplify and host the static export on S3 + CloudFront.What
Frontend (already committed on this branch):
next.config.ts→output: 'export',trailingSlash,images.unoptimized; dev rewrites removed (don't run in export).projects/[id]split into a server wrapper (generateStaticParams) +ProjectDetailClient.tsx(existing UI). Verifiednext buildemitsout/withindex.html, per-route pages, and aprojects/placeholdershell.Infra:
frontend_hosting.tf— private S3 + CloudFront (OAC), SPA fallback (403/404 →/index.html), index-rewrite CloudFront Function, HTTPS. Outputs bucket + distribution id + public domain.amplify.tf+amplify_notifications.tf(apply tears down the Amplify app, SSR role, EventBridge) + unusedapi_base_urlvar.CI:
frontend-deploy.yml— push toapps/frontend/**→ build export (withNEXT_PUBLIC_API_BASE_URLresolved from API Gateway) →s3 sync→ CloudFront invalidation. OIDC apply role +productionenv.Docs: AGENTS.md (root/frontend/infra/.github) updated to the static model.
/projects/:id routing — verified, not broken
[id]route is in the client bundle → client-side navigation renders it, no server round-trip./projects/123: S3 404 → CloudFront rewrites to/index.html(200) → Next client router renders it →apiFetchloads the project. Same client-fetch behavior as today.Dependencies / sequencing
branch-ci-applyrole (feat: GitHub OIDC provider + scoped CI roles (plan RO / apply RW) #266) — that must be applied first. (If you'd rather ship before OIDC, say so and I'll point the deploy at the current key temporarily.)branch-amplify-ssr-role+ EventBridge notification resources are destroyed. The other Amplify PRs (fix: make Amplify frontend SSR deploy functional #256/fix: add regional Amplify principal to SSR role trust #265) become moot — close them.frontend_cloudfront_domainoutput (dxxxx.cloudfront.net).🤖 Generated with Claude Code