Building Microfrontends and Microservices in Turborepo on AWS

Building Microfrontends and Microservices in Turborepo on AWS

Stewart Moreland

Stewart Moreland

Executive summary

A single monorepo can successfully host both microfrontends (MFEs) and microservices if (a) the repo structure makes ownership boundaries explicit, (b) your build system (Turborepo) is configured for deterministic caching and change-scoped execution, and (c) deployment contracts treat each MFE/service as independently releasable artifacts—even when code lives together. Turborepo's task graph, cacheable outputs, remote caching (including artifact signing), and strict environment-variable controls provide the mechanics for incremental builds and safe CI [1].

For MFEs on AWS, the most operationally stable default is static asset delivery via S3 + CloudFront with Origin Access Control (OAC) to keep buckets private, path-based CloudFront behaviors per MFE, and versioned asset paths to avoid frequent invalidations [2]. Client-side composition typically uses a shell that loads MFEs at runtime from either (1) a manifest/import map endpoint or (2) Module Federation remoteEntry.js coordinates. AWS Prescriptive Guidance explicitly describes a manifest-based discovery service and positions Module Federation and single-spa as common CSR composition tools [3].

For services, a pragmatic portfolio approach reduces risk: deploy "spiky / event-driven / low-ops" workloads to Lambda, deploy "steady-state / latency-sensitive / dependency-heavy" services to ECS (often on Fargate), and use EKS when you need Kubernetes ecosystem features and can justify its baseline control-plane cost and operational overhead. Canary and rollback controls should be "native" for each runtime: CloudFront staging/continuous deployment policies for CDN config changes, Lambda weighted aliases for function canaries, and ECS blue/green deployments for containerized services [4].

React 19 adds server-oriented capabilities (Server Components and Server Functions) and modern streaming SSR APIs (renderToReadableStream), but how these features integrate into a microfrontend build/composition approach is often framework/bundler-dependent; treat cross-MFE RSC composition details as unspecified unless your chosen framework explicitly supports it in an MFE setting [5].

Reference architecture for a combined monorepo

A combined MFE + microservice monorepo works best when you separate three planes:

  • Build plane: Turborepo tasks (build/test/lint/deploy) with cacheable outputs, remote cache, and strict env rules [1].
  • Delivery plane: CloudFront as the single global entry point for web delivery; S3 for static MFEs; optional origins for SSR/BFF; API origins for services.
  • Runtime plane: Lambda/API Gateway (serverless), ECS/ALB (containers), EKS (Kubernetes).

Deployment topology diagram

Loading diagram...

CloudFront supports multiple origins, and each cache behavior maps to a single origin (or origin group). Plan your path patterns so each MFE/service "front door" is unambiguous and cacheable.

Table: single vs multiple CloudFront distributions for MFEs

DecisionSingle distribution (path-based behaviors)Multiple distributions (per-MFE or per-domain)
Team autonomyMedium: shared CloudFront config changes require coordinationHigh: teams can change caching/headers/origins independently
Blast radiusLarger: misconfiguration affects all MFEsSmaller: changes isolated to one distribution
Routing modelSimpler: one domain, consistent cookies/authMore complex: cross-domain cookies/CORS and DNS sprawl
TLS/DNS overheadLower: fewer certs/domains to manageHigher: each distribution needs cert + DNS
CDN config canaryStrong: use CloudFront staging + continuous deployment policyPer-distribution canary; more moving parts overall
Operational costUsually lowerUsually higher (more distributions, logs, policies, monitoring)

CloudFront continuous deployment uses a primary distribution plus a staging distribution and routes a portion of traffic based on weight or headers, which is easier to operate when you have fewer distributions [4].

Monorepo layout and Turborepo configuration

Repo layout blueprint and ownership boundaries

A practical layout makes "what is deployable" obvious:

Loading diagram...

Use Turborepo Boundaries (tags + rules) to enforce "MFEs don't import each other" and to constrain which shared packages are allowed. This prevents accidental "federated monolith" coupling as the repo grows [1].

Turborepo task graph: minimal production-grade turbo.json

Key rules from the Turborepo docs:

  • Task outputs must be declared if you want tasks to be cached and restored [1].
  • Use cache: false for side-effect tasks (like deployment) so they do not restore from cache artifacts.
  • If a task needs to respect dependency changes, configure dependsOn appropriately; otherwise you can get incorrect cache hits (e.g., typechecking an app without noticing a breaking change in a shared UI package).

Example turbo.json (root):

json
{
"$schema": "https://turborepo.dev/schema.json",
"envMode": "strict",
"globalDependencies": [".npmrc", "pnpm-lock.yaml"],
"globalEnv": ["NODE_ENV"],
"tasks": {
"lint": {},
"check-types": {
"dependsOn": ["^check-types"]
},
"test": {
"dependsOn": ["^build"]
},
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", "build/**", ".next/**"]
},
"deploy": {
"dependsOn": ["^build"],
"cache": false
},
"dev": {
"persistent": true,
"cache": false
}
}
}

Incremental builds: change-scoped execution in dev and CI

Turborepo's --filter supports selecting packages by name, directory, or Git ranges/commits (including "dependents" selection via ...). This is the core primitive for "build only what changed" in a monorepo.

Examples you can standardize in docs/scripts:

bash
# Build only MFEs changed since the previous commit
turbo run build --filter={./apps/*}[HEAD^1]
# Build everything that depends on changes in a branch (useful for PR validation)
turbo run build --filter=...[origin/my-feature]
# Deploy only one MFE (and its dependencies) in CI
turbo run deploy --filter=apps/mfe-catalog

This approach pairs naturally with Turborepo's CI guidance: rely on caching and targeted filters rather than building the whole repo on every change [1].

Remote caching: performance and integrity controls

Container builds: turbo prune --docker for minimal build contexts

💡 Efficient Container Builds

Use turbo prune --docker to create a partial monorepo slice containing only the target and its internal dependencies and a pruned lockfile, arranged for Docker layer caching. This is the monorepo-native alternative to copying the entire repo into each Docker build.

Microfrontend implementation and S3 + CloudFront delivery

Composition contracts: manifest vs import maps vs Module Federation

A production MFE platform needs a discovery mechanism ("what MFEs exist and where are their artifacts?") and a runtime composition mechanism ("how does the shell load them?").

Manifest-based discovery (recommended baseline) AWS Prescriptive Guidance describes a manifest that contains micro-frontend metadata (name, URL, version, fallback behavior) served by a "discovery service" and queried by the shell after load [3]. In a monorepo, this manifest is also the cleanest place to encode release decisions ("catalog v1.8.2 + checkout v2.3.0").

Import maps (web-native mapping layer) Import maps were incorporated into the HTML standard; they map module specifiers to URLs and can act as a vendor-neutral composition tool. In practice, you host importmap.json behind CloudFront, and the shell loads it before importing MFEs.

Module Federation (code-sharing + runtime remotes) Webpack Module Federation explicitly supports consuming modules from independent builds at runtime. For MFEs in React, a standard approach is to share react and react-dom as singletons to avoid double-React issues; version constraints live in your federation config and build process.

Because AWS guidance lists Module Federation as a common choice for CSR MFEs, and because it provides a ready "remoteEntry" contract, it's often the fastest path to operational MFEs—provided you accept the coupling to Webpack (or compatible tooling) [3].

Project templates: shell and MFE packages

A practical set of templates should standardize:

  • basePath (e.g., /mfe-catalog/) to align app router, asset routing, and CloudFront behaviors.
  • Build output structure (e.g., dist/).
  • Deployment contract (publish versioned artifacts, update manifest pointer).
  • Interop boundary: exported mount(el, props) function or route component exports.

Example "remote MFE" export contract (framework-agnostic, works with Module Federation or dynamic import):

typescript
// apps/mfe-catalog/src/mount.ts
import { createRoot } from "react-dom/client";
import App from "./App";
export function mount(el: HTMLElement, props: { basePath: string }) {
const root = createRoot(el);
root.render(<App basePath={props.basePath} />);
return () => root.unmount();
}

React 19 specifics: this pattern is unaffected by React 19 features; however, if you plan to use Server Components/Server Functions in MFEs, confirm your framework/bundler's support for these features across independently deployed artifacts (often unspecified in MFE contexts) [5].

Routing and CloudFront behavior mapping

Rule: browser routing must be consistent with CDN routing.

For SPA MFEs served from S3, CloudFront Functions can rewrite "directory-like" or extensionless URLs to index.html. AWS provides an official example that adds index.html to URIs missing file names/extensions.

CloudFront Function (viewer request) — scoped to one MFE base path:

javascript
async function handler(event) {
var request = event.request
var uri = request.uri
if (!uri.startsWith('/mfe-catalog/')) return request
if (uri.endsWith('/')) {
request.uri += 'index.html'
} else if (!uri.includes('.')) {
request.uri += '/index.html'
}
return request
}

CloudFront Functions are explicitly designed for lightweight, high-scale URL rewrites, header manipulation, and cache-key normalization; use Lambda@Edge when you need heavier compute, third-party libraries, network access, or request body access.

CloudFront custom error responses can also be used to map certain origin errors to custom pages and response codes, but for multi-origin / multi-MFE setups, viewer-request rewrites are usually more precise and avoid "accidentally masking" true 404s for missing assets.

Deploying MFEs to S3 + CloudFront with OAC

CloudFormation: OAC + S3 bucket policy skeleton (adapt fields as needed):

yaml
Resources:
SiteOAC:
Type: AWS::CloudFront::OriginAccessControl
Properties:
OriginAccessControlConfig:
Name: mfe-oac
OriginAccessControlOriginType: s3
SigningBehavior: always
SigningProtocol: sigv4
SiteBucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref SiteBucket
PolicyDocument:
Version: '2012-10-17'
Statement:
- Sid: AllowCloudFrontRead
Effect: Allow
Principal:
Service: cloudfront.amazonaws.com
Action: s3:GetObject
Resource: !Sub '${SiteBucket.Arn}/*'
Condition:
StringEquals:
AWS:SourceArn: !Sub 'arn:aws:cloudfront::${AWS::AccountId}:distribution/${CloudFrontDistribution}'

Caching, invalidation, and versioning strategy

Use versioning as the default release mechanism. CloudFront explicitly recommends file versioning when you update files frequently and documents that invalidations wipe all cached variants regardless of headers (important when you vary cache by headers/cookies).

A practical MFE artifact layout:

  • Immutable build artifacts: s3://mfe-bucket/mfe-catalog/1.8.2/assets/app.<hash>.js (cache "long")
  • One mutable pointer: s3://mfe-bucket/mfe-catalog/manifest.json (cache "short" or invalidate on release)

CloudFront caching behavior depends on Cache-Control/Expires headers, TTL settings, and versioning practices; AWS documents using Cache-Control headers on S3 objects and combining headers + TTL + invalidations to manage updates.

Security headers: CSP/CORS at CloudFront

CloudFront response headers policies let you add/remove headers on responses CloudFront sends to viewers, and CloudFormation supports configuring a Content-Security-Policy string in a response headers policy.

CloudFormation snippet (CSP via ResponseHeadersPolicy):

yaml
Resources:
SecurityHeadersPolicy:
Type: AWS::CloudFront::ResponseHeadersPolicy
Properties:
ResponseHeadersPolicyConfig:
Name: mfe-security-headers
SecurityHeadersConfig:
ContentSecurityPolicy:
ContentSecurityPolicy: "default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'"
Override: true

CORS can also be managed through response headers policies (or managed policies), which is particularly useful when MFEs are served under one domain and APIs under another.

Cross-MFE state and communication

AWS Prescriptive Guidance recommends event-based cross-interactivity handling for microfrontends that need to react to each other (e.g., global UI behavior) [6]. The safe default is:

  • Keep business state inside the MFE boundary.
  • Use events for cross-MFE signals (e.g., cart.updated, auth.changed).
  • Prefer BFFs per user experience when APIs would otherwise be overly chatty [7].

Microservice implementation and deployment options

Service templates inside the monorepo

Standardize "service types" so teams don't invent new deployment patterns per service.

Lambda + API Gateway template (serverless)

AWS SAM's AWS::Serverless::Function resource provides shorthand syntax that is transformed into CloudFormation resources during deployment, making it well-suited for service templates in a monorepo.

Minimal SAM template:

yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Resources:
OrdersApi:
Type: AWS::Serverless::Api
Properties:
StageName: prod
OrdersFunction:
Type: AWS::Serverless::Function
Properties:
Runtime: nodejs20.x
Handler: dist/handler.main
CodeUri: .
MemorySize: 512
Timeout: 10
Events:
GetOrders:
Type: Api
Properties:
RestApiId: !Ref OrdersApi
Path: /orders
Method: GET

API Gateway authorization choices you can standardize by service class:

  • HTTP APIs: JWT authorizers are first-class for stage/route auth and integrate with OIDC/OAuth2 flows.
  • REST APIs: Lambda authorizers support custom auth logic (token or request authorizers).
  • Cognito integration: API Gateway can use Cognito user pools as authorizers for REST APIs.

ECS template (containers)

Use ECS with an Application Load Balancer when you need Layer-7 routing and containerized deployments; the ECS docs highlight that ALBs support path-based routing and are commonly used with ECS.

To build images efficiently from a monorepo, use turbo prune --docker to shrink build context, then do a multi-stage Docker build [1].

Docker build flow (conceptual):

bash
# Create pruned build context for the service
turbo prune services/payments-ecs --docker
# Build from pruned output
docker build -f services/payments-ecs/Dockerfile -t payments:local ./out

Push to ECR uses aws ecr get-login-password | docker login ... and then docker push; AWS documents this exact authentication flow.

EKS template (Kubernetes)

EKS adds a fixed per-cluster control-plane cost, and AWS publishes pricing tiers for standard vs extended Kubernetes version support. Use EKS when multi-service scheduling, Kubernetes-native tooling, and multi-tenant cluster governance are a primary goal—not merely to host a few APIs.

For observability, AWS explicitly documents using AWS Distro for OpenTelemetry (ADOT) to collect metrics/traces for ECS/EKS and send them to X-Ray and other backends.

Table: service runtime trade-offs

RuntimeStrengthsCosts/risks you must plan forBest fit
Lambda (+ API Gateway)Minimal ops; built-in scaling; canary via weighted aliasesCold start risk; request/compute pricing; packaging size constraintsSpiky APIs, event-driven workloads, "lots of small services"
ECS on Fargate (+ ALB)Container flexibility; ALB routing; blue/green deploymentsAlways-on baseline cost; capacity tuning; container build/push pipelineSteady traffic, heavier deps, long-lived connections
EKSKubernetes ecosystem; standardized platform for many servicesControl-plane fee; cluster ops; security hardening and upgradesLarge fleets, K8s-native orgs, shared cluster platforms

Lambda pricing is request- and duration-based, while Fargate pricing is based on vCPU/memory/ephemeral storage consumption, and EKS adds an explicit per-cluster fee.

CI/CD pipelines, release flows, and environment management

CI strategy: "shared pipeline, scoped execution" as the default

A monorepo does not require "one giant CI job." The scalable pattern is:

  1. One orchestrating workflow (GitHub Actions or CodePipeline) that:
  2. Runs turbo run tasks scoped by --filter or Git ranges
  3. Publishes artifacts only for changed MFEs/services
  4. Updates the discovery layer (manifest/import map)
  5. Optionally triggers invalidations for a small set of pointers (manifest/index)

This aligns with Turborepo's own CI guidance: rely on caching + filters rather than complex change detection logic [1].

Table: per-MFE CI vs shared CI

ModelProsConsWhen to choose
Shared CI (one workflow, Turborepo-scoped tasks)Fast setup; consistent policy; scalable via caching/filtersRequires discipline to avoid coupling in shared stepsDefault for most orgs
Per-MFE CI (separate workflows/pipelines)High autonomy; isolated failuresDuplicate config; harder to enforce standardsWhen teams require independent release cadence and policy

Remote caching + filter-based execution is what makes the shared-CI model performant [1].

GitHub Actions: concrete pipeline with OIDC to AWS

For secure AWS access from CI, use OIDC (no long-lived AWS keys in secrets). GitHub documents the OIDC-to-AWS model and provides a trust-policy pattern using the sub claim to scope which repo/branch (or environment) may assume a role.

Example workflow (build + deploy changed MFEs; deploy a Lambda service):

yaml
name: ci
on:
push:
branches: [main]
pull_request:
permissions:
id-token: write
contents: read
jobs:
build-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 9
- name: Install
run: pnpm -w install --frozen-lockfile
- name: Turbo build/test (affected)
run: |
turbo run lint check-types test --cache-dir=.turbo
turbo run build --filter={./apps/*}[HEAD^1]
deploy:
if: github.ref == 'refs/heads/main'
needs: build-test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 9
- name: Install
run: pnpm -w install --frozen-lockfile
- name: Configure AWS (OIDC)
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/monorepo-deployer
aws-region: us-east-1
- name: Build changed MFEs
run: turbo run build --filter={./apps/*}[HEAD^1]
- name: Publish MFEs to S3 (example)
run: |
aws s3 sync apps/mfe-catalog/dist s3://my-mfe-bucket/mfe-catalog/${GITHUB_SHA}/ \
--cache-control "public,max-age=31536000,immutable"
aws s3 cp infra/manifest/prod.json s3://my-mfe-bucket/manifest.json \
--cache-control "public,max-age=60"
- name: Deploy Orders Lambda (SAM)
run: |
cd services/orders-lambda
sam build
sam deploy --no-confirm-changeset --stack-name orders-prod

This workflow's authentication model and the need to scope "who can assume the role" are backed by GitHub's OIDC docs and AWS's security guidance on IAM roles for GitHub workflows.

AWS CodeBuild and CodePipeline: concrete examples

CodeBuild buildspec structure AWS defines buildspec.yml as a YAML collection of commands/settings CodeBuild uses to run builds; it can be stored in-repo and can be overridden per project.

CodeBuild caching CodeBuild supports S3 caching or local caching (including Docker layer cache). S3 caching allows reuse across build hosts and can use dynamic keys (e.g., hash of lockfile).

Example buildspec.yml (monorepo build + ECR push):

yaml
version: 0.2
phases:
install:
commands:
- corepack enable
- pnpm -w install --frozen-lockfile
build:
commands:
- turbo run build --filter=services/payments-ecs
- turbo prune services/payments-ecs --docker
- docker build -t payments:$CODEBUILD_RESOLVED_SOURCE_VERSION ./out
- aws ecr get-login-password --region $AWS_REGION | docker login --username AWS --password-stdin $ECR_REGISTRY
- docker tag payments:$CODEBUILD_RESOLVED_SOURCE_VERSION $ECR_REGISTRY/payments:$CODEBUILD_RESOLVED_SOURCE_VERSION
- docker push $ECR_REGISTRY/payments:$CODEBUILD_RESOLVED_SOURCE_VERSION
cache:
key: pnpm-$(codebuild-hash-files pnpm-lock.yaml)
paths:
- 'node_modules/**/*'

The ECR login/push flow is documented by AWS ECR; CodeBuild cache key generation is documented via codebuild-hash-files and cache.key patterns.

CodePipeline CodePipeline pipelines must have at least two stages with a source stage first; artifacts flow between actions, and CloudFormation provides an AWS::CodePipeline::Pipeline resource for IaC-defined pipelines.

CloudFormation skeleton for a pipeline resource (conceptual):

yaml
Resources:
Pipeline:
Type: AWS::CodePipeline::Pipeline
Properties:
RoleArn: arn:aws:iam::123456789012:role/codepipeline-role
ArtifactStore:
Type: S3
Location: my-pipeline-artifacts-bucket
Stages:
- Name: Source
Actions: []
- Name: Build
Actions: []
- Name: Deploy
Actions: []

Environment management: secrets, config, and Turborepo determinism

A production guide should standardize three environment layers:

  1. Build-time env (affects bundling and test outcomes)
  2. Deploy-time env (targets AWS accounts/stacks, S3 prefixes, CloudFront IDs)
  3. Runtime env (service endpoints, secrets, feature flags)

Turborepo supports strict filtering of environment variables; Strict Mode can filter out CI vendor variables unless included through env configuration keys. This is essential for deterministic caching and for preventing "prod build reuses dev cache" [1].

For AWS-side secrets/config:

  • Parameter Store SecureString uses KMS encryption for sensitive values and is recommended for secret data.
  • Secrets Manager supports rotation and reduces the need to redeploy clients when credentials rotate.
  • CodeBuild and ECS can inject Secrets Manager values as environment variables.

Quality, security, observability, and operational runbooks

Testing strategy across MFEs and services

A practical monorepo guide should mandate four test layers and wire them into Turborepo tasks:

  • Unit/component tests per package (fast, cached).
  • Contract tests for cross-boundary contracts: event payload schemas, API OpenAPI specs, and MFE exposed module interfaces (for Module Federation).
  • Integration tests for real dependencies (service-to-service, MFE-to-BFF).
  • E2E tests against a composed staging environment (shell + MFEs + services).

Turborepo task configuration must reflect dependency changes for tasks like check-types, or you risk incorrect green builds when shared package interfaces change [1].

Observability: CDN logs, service traces, and build telemetry

CloudFront logging options include standard access logs and real-time logs attached to cache behaviors; logs include request time, path, response, and processing details useful for debugging MFE asset 404s, cache hit anomalies, and latency regressions.

For service tracing:

  • Lambda Active tracing generates trace segments and sends them to X-Ray.
  • API Gateway supports X-Ray active tracing for REST APIs (with documented limitations).
  • ADOT can collect traces/metrics for ECS/EKS and send them to X-Ray and other backends.

Turborepo also documents an experimental observability mode to export run summaries to OTLP collectors, which you can use to understand CI performance regressions at the task-graph level.

Security runbooks: S3 privacy, CSP/CORS, and auth patterns

S3 privacy: enforce OAC and bucket policies bound to a specific CloudFront distribution ARN to prevent direct S3 access and limit confused-deputy risk [2].

CSP: set CSP using CloudFront response headers policies (or CloudFront Functions/Lambda@Edge response manipulation); CloudFormation supports CSP configuration and override behavior.

CORS: for HTTP APIs, API Gateway supports built-in CORS configuration; for CDN responses, CloudFront response headers policies can standardize CORS headers.

Auth: standardize where auth is enforced:

  • At API boundary: API Gateway JWT/Lambda/Cognito authorizers.
  • At CDN boundary: CloudFront Functions can validate hashed tokens like JWTs for lightweight authorization decisions; use Lambda@Edge for heavier logic.

Rollout and rollback runbooks: MFEs and services

Microfrontends (static):

  • Primary rollback: update manifest/import map pointer to previously known-good versions (no CDN invalidation needed if assets are versioned) [3].
  • If you must invalidate: remember invalidation removes all cached variants of the file, regardless of header-based cache variations, so be careful invalidating commonly varied content.
  • For CDN config changes: use CloudFront continuous deployment with a staging distribution and weight-based or header-based routing [4].

Lambda services:

  • Use versions + aliases; configure weighted routing for canary traffic shifts and fast rollback by moving alias back.

ECS services:

  • Use blue/green deployments (ECS + CodeDeploy) to validate new revisions and roll back quickly.

API Gateway:

  • Canary releases can be configured per stage (REST APIs), and can be promoted/reset through documented workflows.

Sequential implementation checklist

  1. Establish repository conventions: /apps, /services, /packages, /infra; define CODEOWNERS per bounded context; add "do not import MFEs into MFEs" rule.
  2. Add Turborepo with a root turbo.json; define build, test, check-types, lint, dev, deploy; ensure deploy-like tasks are cache: false [1].
  3. Configure caching correctly: declare outputs for build tasks so artifacts restore on cache hits; validate determinism in CI.
  4. Turn on remote caching; configure artifact signing (remoteCache.signature) and key management (TURBO_REMOTE_CACHE_SIGNATURE_KEY) for integrity.
  5. Configure environment determinism: set envMode: strict; declare globalEnv/globalDependencies; document how env changes impact caches.
  6. Implement Boundaries tags/rules: tag MFEs, shared libs, and internal-only packages; block illegal imports.
  7. Create MFE templates: shell + at least one remote MFE; standardize basePath routing and the mount/export contract.
  8. Implement discovery: choose manifest (recommended baseline) or import maps; define schema with name, version, url, fallback [3].
  9. Choose composition: start with dynamic import/import maps; add Module Federation if you need runtime shared dependency de-duplication.
  10. Provision S3 buckets for each MFE (or shared bucket with prefixes) and configure CloudFront OAC + bucket policy binding distribution ARN [2].
  11. Build a CloudFront distribution with path-based behaviors (/mfe-a/*, /mfe-b/*, /assets/*); attach response headers policy (CSP/CORS).
  12. Add CloudFront Function for SPA routing rewrite to index.html per MFE basePath.
  13. Standardize caching: versioned artifacts for long TTL; short TTL for manifest/import map; avoid frequent invalidations.
  14. Create service templates: SAM-based Lambda service, ECS container service (with turbo prune --docker), optional EKS helm-based service.
  15. Implement API auth pattern: JWT authorizers for HTTP APIs where suitable; Lambda/Cognito authorizers for REST APIs; document token/cookie strategy for MFEs.
  16. Build CI with scoped execution: use turbo run ... --filter=[git range]; ensure remote cache works in CI [1].
  17. Implement GitHub Actions OIDC role and least-privilege IAM policies for S3/ECR/CloudFront deploy actions; ban long-lived AWS keys in secrets.
  18. Implement CodeBuild/CodePipeline alternative: buildspec-driven builds with S3 caching and artifact publishing; pipeline IaC using AWS::CodePipeline::Pipeline.
  19. Add rollout controls: CloudFront continuous deployment for CDN changes; Lambda weighted aliases; ECS blue/green; API Gateway canary stages [4].
  20. Add observability: enable CloudFront logs; enable X-Ray for Lambda and API Gateway where supported; deploy ADOT for ECS/EKS telemetry.
  21. Write operational runbooks: "rollback manifest," "rollback Lambda alias," "rollback ECS via blue/green," "invalidate only pointer files," and incident triage based on CloudFront logs + X-Ray traces.
  22. Finalize DNS/TLS: request/import ACM cert in us-east-1 for CloudFront and create Route 53 alias record to the distribution.