Architected & developed
princelundgren.com
My portfolio — the one you're reading right now. I designed and built every layer of it myself, from the front end to the cloud it runs on.
Visit princelundgren.com
- Problem
- I wanted my portfolio to be as carefully made as the work it shows — a fast, hand-built site, not a template — running on infrastructure worth showing off. It's served from a dedicated workload account while the princelundgren.com DNS zone stays in a separate one, with zero manual DNS steps and no long-lived credentials anywhere.
- Approach
- The front end is a static Astro build — a bespoke design system, an illustrated SVG hero, and effectively zero client-side JavaScript — deployed onto a small set of AWS CDK stacks. Cross-account DNS is automated through a tightly-scoped assumed role, and the ACM certificate is owned by a provider-framework Lambda (a plain CloudFormation certificate deadlocks when its validation records live in another account). CloudFront fronts a private S3 bucket via Origin Access Control, with HTTP/3, a strict security-header policy, and standard logging v2. CI deploys through GitHub OIDC — no stored keys — using a two-pass cache strategy and scoped invalidation.
- Outcome
- A site that posts a clean 100 across Lighthouse and ships zero client-side JavaScript (bar a 384-byte print helper) behind an enforced CSP. Underneath, hands-off certificate renewal, fully automated OIDC deploys, and cdk-nag-clean infrastructure with unit tests on its security properties — the whole architecture written up as a portfolio piece in its own right.
This site is its own case study, in two halves: a fast, hand-built front end, and
the cross-account AWS infrastructure it runs on. The front end had to earn its place —
built by hand, not from a template — and the platform had a specific brief: serve
the apex princelundgren.com from a dedicated workload account while the
Route 53 zone stays in a separate one, automated end to end with no long-lived
credentials.
The site itself
It’s a hand-built Astro 6 static site — no framework runtime, no page builder, and effectively zero client-side JavaScript (the lone exception is a 384-byte print helper). Even the illustrated hero — me at a desk of clouds, drones, and a 3D printer — is hand-drawn SVG, animated with CSS.
The look is a small bespoke design system — warm paper and ink, a single accent, a display serif, a paper-grain texture, and one orchestrated page-load reveal — so it feels made, not generated. Speed and accessibility come by construction: a clean 100 across Lighthouse, first paint under a second, AA contrast throughout, plus per-page metadata, generated Open Graph cards, and structured data.
Under the hood
Workload account
DNS account
That brief is harder than it sounds. An apex domain can’t be delegated to another account the way a subdomain can.
So the workload account has to write a couple of records into a zone it doesn’t
own: the ACM validation records, and the apex/www alias records pointing at
CloudFront. The clean way to bridge that is a single, tightly-scoped IAM role
in the DNS account that the workload account assumes — able to change records in
exactly one hosted zone and nothing else.
The certificate is the sharp edge. A CloudFormation Certificate resource blocks
until it’s validated, but you can’t read its validation record until it exists,
and that record has to land in the other account — a deadlock. So the cert is
owned by a small provider-framework Lambda that drives the lifecycle over the
SDK: request → write the validation records cross-account → poll to issued → clean
up on delete. No deadlock, and renewals are hands-off forever after.
The rest of the stack
- Private S3 + CloudFront with Origin Access Control — the bucket is never public; CloudFront signs every origin request.
- Hardened edge — HTTP/3, TLS 1.2_2021, a response-headers policy (HSTS
preload, CSP, frame and referrer controls), a single edge function that does the
www→apex redirect and pretty-URL rewriting, and standard logging v2. - No keys in CI — GitHub Actions authenticates with OIDC and can only assume
the CDK bootstrap roles; deploys run a two-pass
s3 sync(immutable hashed assets, then revalidated HTML) and a scoped cache invalidation. - Verified, not vibes —
cdk synthis cdk-nag clean, with unit tests asserting the load-bearing security properties.