paulserban.eu

Portfolio Edition

Paul Serban

AI & full-stack engineer · agentic platforms · distributed systems

← Back to portfolio

Featured

AWS Multi-Subdomain Static Hosting

Four production domains on S3 + CloudFront + Route 53

Role: Architect & lead implementer

  • #acm
  • #aws
  • #cloudfront
  • #jamstack
  • #route 53

Problem

Four independently deployable frontends needed production HTTPS subdomains with CDN delivery, private origins, and deep-link behaviour that matched how the apps actually emit HTML - without waiting on IaC or CI.

Approach

Designed a one-subdomain / one-distribution / one-private-bucket topology in the AWS console - ACM in us-east-1, OAC-locked S3, viewer-request rewrites for Astro directories, and SPA fallback for the quiz - as a deliberate bootstrap before Terraform and CI/CD.

Outcome

Live production URLs for all four surfaces with TLS, edge caching, and correct deep links; a proven reference topology later encoded in Terraform modules.

surfaces
4
edge_stacks
4
public_buckets
0
View code Live demo

Problem

prj--personal-portfolio--v3 is not one website. It is a platform of four static surfaces - Astro portfolio, Astro blog, Astro news-feed, and a Vite/React quiz PWA - that must ship on their own production hostnames:

SurfaceProduction hostname
Portfoliopaulserban.eu
Blogblog.paulserban.eu
Quizquiz.paulserban.eu
News-feednews-feed.paulserban.eu

A single bucket-behind-one-CDN collapses blast radius poorly, couples release cadence, and fights the monorepo's actual deploy units. Public S3 website hosting is the wrong security default. And Astro's trailingSlash: 'always' directory layout (post/{slug}/index.html) does not work behind CloudFront's root-only default_root_object without an explicit edge rewrite.

I needed a production topology that was correct enough to ship, auditable enough to teach, and repeatable enough to automate later - without blocking on Terraform modules or GitHub Actions OIDC first.

Solution

I bootstrapped the full edge topology in the AWS console first: one Route 53 hosted zone, four ACM certificates (DNS-validated, issued in us-east-1 for CloudFront), four private S3 buckets, four CloudFront distributions with Origin Access Control, and A/AAAA alias records per hostname.

That order was intentional. Console work is how you discover the real contract - certificate region constraints, OAC policy shape, Function association limits, SPA vs SSG error behaviour - before freezing it into modules. Terraform and CI/CD encode the same topology later; they are covered in separate case studies.

Architecture

Browser
   │
   ▼
Route 53  (paulserban.eu zone)
   │  A/AAAA alias per hostname
   ▼
CloudFront x 4   ← ACM TLS (us-east-1), HTTPS redirect, security headers
   │  OAC (SigV4)
   │  viewer-request Function: /path/ → /path/index.html
   ▼
S3 x 4  (private buckets; no public ACLs)
   ├── paulserban.eu           → portfolio SSG
   ├── blog.paulserban.eu      → blog SSG
   ├── quiz.paulserban.eu      → quiz SPA (+ soft 404 → /index.html)
   └── news-feed.paulserban.eu → news-feed SSG
LayerResponsibility
Route 53Single public hosted zone; alias records tip each hostname at its distribution
ACMPer-hostname (or SAN) certs in us-east-1, DNS-validated against the zone
CloudFrontTLS termination, cache, compress, HSTS/security headers, OAC toward S3
CF FunctionViewer-request rewrite so Astro directory URLs resolve to index.html
S3Private object store only; bucket policy allows s3:GetObject from that distribution's ARN

Shared content media (assets.paulserban.eu) stays on a separate assets CDN; site deploys never own those objects. That keeps publishing images independent of app releases.

Approach

As architect and lead implementer, I treated hosting as a platform seam, not a deploy afterthought: each surface gets an isolated blast radius, a private origin, and edge behaviour that matches how the build actually emits files.

Platform topology first

Edge behaviour that matches the apps

Operational design (human-scale)

Stack

LayerChoices
DNSRoute 53 public hosted zone paulserban.eu
TLSACM, DNS validation, CloudFront-compatible region (us-east-1)
EdgeCloudFront, CachingOptimized, SNI, compress, HTTPS redirect
OriginS3 private + OAC + bucket owner enforced + SSE-S3
RewriteCloudFront Functions (cloudfront-js-2.0) on viewer-request
DeliveryFour independent static origins; no runtime origin servers

Design decisions that mattered

What shipped

Outcome

The platform has a production edge contract: one subdomain, one distribution, one private bucket - bootstrapped in the console so the architecture was proven under real DNS, TLS, and deep-link traffic before automation. Publishing surfaces stay independently releasable, origins stay closed, and the same shape scales into Terraform and CI without redesign.

Part of prj--personal-portfolio--v3

This hosting topology is the production counterpart to the monorepo's local and content seams. Related pieces: