tituscoleman.com: a static Astro portfolio on S3 and CloudFront

How tituscoleman.com is built: a static Astro site with its own design system, SEO and llms.txt, served from S3 and CloudFront at minimal cost.

The problem

What I built

A static portfolio in Astro. Each project is a Markdown file, and every field at the top of it maps to something visible: the card badge, tags, status, stack and repo label. Everything else, from link-preview images to the AI-readable summary, is generated from those same files at build time.

How it works

Icon-Architecture-Group/32/AWS-Cloud-logo_32AWSRequestBuild and deploylookupHTTPSon missdeployIcon-Resource/General-Resource/Res_User_48_LightVisitorAny browserIcon-Architecture/48/Arch_Amazon-Route-53_48DNSRoute 53Icon-Architecture/48/Arch_Amazon-CloudFront_48Edge cacheCloudFront · FunctionIcon-Architecture/48/Arch_Amazon-Simple-Storage-Service_48Site filesS3 · privateWrite-upsMarkdown, MDXAstroStatic buildAstro · HTML, CSS
Icon-Architecture-Group/32/AWS-Cloud-logo_32AWSlookupHTTPSon missdeployIcon-Resource/General-Resource/Res_User_48_LightVisitorAny browserIcon-Architecture/48/Arch_Amazon-Route-53_48DNSRoute 53Icon-Architecture/48/Arch_Amazon-CloudFront_48Edge cacheCloudFront · FunctionIcon-Architecture/48/Arch_Amazon-Simple-Storage-Service_48Site filesS3 · privateAstroStatic buildAstro · HTML, CSSWrite-upsMarkdown, MDX
Static files in a private bucket, cached at the edge. No servers to run.
  • Hosting: a private S3 bucket that only CloudFront can read, with the certificate and DNS in Route 53. www and the raw CloudFront address redirect to tituscoleman.com.
  • Deploys: a branch can be deployed by hand to a password-protected staging copy as a final check, and merging into main deploys to production. Each environment has its own deploy role, and only main can use the production one.
  • No stored AWS keys: GitHub Actions signs in to AWS through OIDC, and the deploy role only trusts this repository.

Design system

Five named colors, in a light and a dark theme.

  • Shadow Grey#231f20Background
  • Lavender Blush#eee5e9Text
  • Tomato Jam#bb4430Dots, lights, accents
  • Tropical Teal#7ebdc2Artwork panels
  • Amethyst Smoke#aa78a6Badges, selection
  • Light and dark: the site follows the visitor’s system setting, and a toggle in the header switches it. The choice is remembered and applied before the page first paints, so it never flashes the other theme. Artwork on the teal panels stays dark in both.
  • Contrast checked, not assumed: Tomato Jam is 3.1:1 on Shadow Grey and 4.3:1 on Lavender Blush, under the 4.5:1 small text needs, so accent text is lightened in dark mode (5.6:1) and darkened in light mode (5.3:1). Each text color’s token records its ratio beside it.
  • Type: one family, Geist, with Geist Mono for labels, tags and numbers. Sizes scale fluidly with the screen instead of jumping at breakpoints.
  • Texture: a faint paper grain over the background, kept low enough that it doesn’t move text contrast.
  • Generated artwork: projects without a screenshot get isometric block art seeded by the project’s id, so it is unique per project and stable between builds. Architecture diagrams like the one above are drawn in the same tokens, with a cut-down version for the project card.
  • Details: section dividers drawn as audio waveforms, each shaped by its own seed, and a split-flap board under the name that cycles through roles. Screen readers get one plain sentence instead of the board.

Motion

  • Smooth scrolling with Lenis.
  • Hero node band: a field of nodes that comes online in a wave, nodes flashing as they boot, a few busy at any moment, and nodes brightening near the pointer. It fades into the background as you scroll away.
  • Split-flap role board: each tile flips through characters to the next role, like an old station departure sign.
  • Name strip: on project pages, the project name and its stack slide sideways in step with scrolling.
  • Page transitions: the browser’s own cross-document view transitions, with no JavaScript. Browsers without support simply navigate.
  • Kept light: the animation library loads only on pages that animate, after the page is already readable, so the hero’s main text is visible on first paint.
  • Reduced motion: visitors who ask for it get none of the above. Smooth scrolling and page transitions turn off, the node band draws once and holds still, and the role board stays on its first role.

SEO and AI tools

  • One URL per page: every page lives at a trailing-slash address, and the edge redirects the other form to it. Each page has a canonical link, its own title and its own description.
  • Structured data: Person, WebSite, BlogPosting and BreadcrumbList, all pointing at one shared Person record, so search engines connect every write-up to the same author, GitHub, LinkedIn, résumé and certifications.
  • Link previews: a 1200×630 image for every project, generated at build time in the site’s palette and type.
  • Discovery: sitemap, RSS feed, a robots file that allows all crawlers, and a real 404 status for missing pages.
  • llms.txt: a plain-Markdown summary of the site for AI tools, following the llms.txt convention. It is built from the same data as the pages, so it never drifts from them, and drafts never appear in it.

Keeping the cloud bill small

  • No servers: the site is static files. Nothing runs between visits, so there is nothing to pay for when nobody is visiting.
  • Caching does the work: file names under /_astro/ include a content hash and are cached for a year. Pages stay cached at the edge until the next deploy clears them. Most requests never reach S3.
  • Cheapest edge tier: CloudFront’s lowest price class, which serves from North America, Europe and Israel.
  • Compression and modern protocols: responses are compressed, and CloudFront serves HTTP/2 and HTTP/3.
  • Errors cached too: missing-page responses are cached for a minute, so repeated bad requests don’t each reach S3.

Get in touch

Text me:

Open in new tab

Loading…