skip to content

You must serve a single-page app's static files from an S3 bucket and its JSON API from an Application Load Balancer, both under https://app.example.com. How do you shape the CloudFront distribution, and what does putting them on one domain buy you?

level: middleimportance: should knowfreq 58%

answer

  1. two origins, one hostname
  2. default behavior to the bucket
  3. ordered pattern for the API path
  4. write methods and caching off there
  5. same-origin means no CORS

basics

~20 s

Define two origins on one distribution: the S3 bucket and the ALB. Give the default cache behavior the bucket, add an ordered /api/* behavior targeting the ALB with write methods allowed and caching off. One hostname means one certificate, same-origin requests and no CORS.

solid answer

~50 s

One distribution, two origins. The **default cache behavior** targets the S3 bucket origin, locked down with Origin Access Control, viewer protocol policy `redirect-to-https`, and normal caching for the built assets. Then an ordered cache behavior for `/api/*` targets the ALB as a custom origin, with `AllowedMethods` extended to POST, PUT, PATCH and DELETE, the managed `CachingDisabled` cache policy so responses are never cached, and an origin request policy such as the managed `AllViewerExceptHostHeader` so the ALB receives the viewer's headers, cookies and query strings. Attach the ACM certificate for `app.example.com` and point a Route 53 alias at the distribution. The payoff is that the browser makes same-origin requests: no CORS preflights, cookies scoped to a single domain (so `SameSite` and host-only session cookies just work), one certificate, and API traffic still benefits from TLS termination and connection reuse at the edge.

go deeper

for a junior

Know that one distribution can hold several origins, and that a path pattern such as /api/* is what sends those requests to the load balancer instead of the bucket.

for a middle

Walk through the per-behavior settings you would change for the API path — allowed methods, caching disabled, an origin request policy that forwards headers and cookies — and explain why each matters.

for a senior

Justify the single-domain choice on cookie and CORS grounds, anticipate the traps (distribution-wide error responses, origin timeouts, a directly reachable ALB) and describe how you would roll the change out safely.

for a principal

Own the boundary decision: whether the front end and API share one distribution and therefore one blast radius and one change queue, or are split for independent ownership and failure isolation.

## The shape This is the standard whiteboard task for CloudFront, and the answer is always the same skeleton: one distribution, two origins, two cache behaviors. **Origin 1 — the SPA bucket.** An S3 bucket origin (the REST endpoint, not the website endpoint) holding `index.html` and the hashed asset bundle. Block Public Access stays on and an Origin Access Control lets only this distribution read it. **Origin 2 — the API.** The Application Load Balancer's DNS name as a custom origin, with the origin protocol policy set to `https-only` if the ALB has a certificate, or a VPC origin if you want the ALB to stay private. **Default cache behavior → the bucket.** Everything not claimed by another rule is a static asset or the SPA shell. `redirect-to-https` viewer protocol policy, compression on, GET/HEAD methods. **Ordered behavior `/api/*` → the ALB.** This is where candidates lose points, because an API behavior is not the default behavior with a different origin: - `AllowedMethods` must include POST, PUT, PATCH, DELETE and OPTIONS. Left at the default read-only set, every mutation returns 403 from the edge without the ALB ever seeing it. - Caching must be off for authenticated JSON — the managed `CachingDisabled` cache policy is the usual choice. - The origin request policy decides what reaches the ALB. By default CloudFront strips most headers and cookies and rewrites `Host` to the origin's domain. The managed `AllViewerExceptHostHeader` policy forwards the viewer's headers, cookies and query strings while leaving `Host` as the ALB's own name, which is what host-based listener rules and most frameworks expect. - Origin read timeout matters if any API call is slow; CloudFront's per-origin read timeout is what turns a slow backend into a 504 at the edge. ## Why one domain The design is not about caching the API — it usually is not cached at all. It is about the browser: - **No CORS.** `fetch('/api/orders')` from `app.example.com` is same-origin, so there are no preflights, no `Access-Control-Allow-*` maintenance, and no wildcard-origin mistakes. - **Cookies are simple.** A host-only, `Secure`, `HttpOnly`, `SameSite=Lax` session cookie set by the API is automatically sent with SPA requests, and nothing has to be relaxed to `SameSite=None` for a cross-site call. - **One certificate, one DNS name, one WAF attachment point.** - **Edge benefits for dynamic traffic.** Even uncacheable API calls get TLS terminated near the user and travel the rest of the way over warm connections on the AWS backbone, which typically shaves real latency off the handshake. ## The costs and gotchas - **Path collisions.** Your API must live under a prefix you can express as a path pattern. An API at the root that shares paths with SPA routes cannot be split this way. - **Distribution-wide settings leak.** Custom error responses, for example, are configured per distribution, so an error mapping added for the SPA also rewrites the API's error responses. - **Blast radius.** One distribution now fronts both the marketing-critical static site and the API; a bad behavior change affects both. - **A bare ALB is still reachable.** Splitting traffic at the edge does nothing to stop a client from calling the ALB's own DNS name directly, so the origin still needs locking down. ## The alternative The other option is `app.example.com` for the SPA and `api.example.com` pointing straight at the ALB. That is simpler to reason about and isolates failure domains, at the price of CORS, cross-site cookie handling, and losing edge termination for API traffic. Interviewers expect you to name the tradeoff rather than declare one design universally right.

  • Why does the API behavior usually need an origin request policy at all?
    Because CloudFront's default is to forward almost nothing: most viewer headers, cookies and query strings are dropped, so `Authorization` headers and session cookies never reach the ALB. An origin request policy such as the managed `AllViewerExceptHostHeader` forwards them while leaving `Host` set to the origin's domain, which is what ALB host-based routing and most frameworks expect.
  • What breaks if the API behavior is left with the default allowed-methods set?
    CloudFront rejects POST, PUT, PATCH and DELETE at the edge with 403 before contacting the origin, so logins and writes fail while GETs work — a confusing symptom because the ALB's access logs show nothing at all. Allowed methods are configured per cache behavior, so the fix is on the `/api/*` behavior only.
  • When would you not put the API behind the same distribution?
    When the API needs a separate failure domain or separate ownership, when its paths cannot be expressed as a distinct pattern, or when distribution-level settings for the SPA would corrupt API responses. Serving `api.example.com` directly from the ALB is simpler, at the cost of CORS, cross-site cookie handling and losing edge TLS termination.

saying these in an interview costs you the question

  • Adds the API origin but leaves the behavior read-only
  • Assumes CloudFront forwards all headers and cookies by default
  • Thinks the API must be cached to sit behind CloudFront
  • Believes one distribution can serve only one backend
  • Says CORS is still required for same-origin API calls

context