Personal portfolio and case studies
Static Astro portfolio: typed bilingual case studies, paper-first editorial UI, near-zero JS by default, plus agent-readable surfaces (llms.txt, JSON Resume, MCP). Live www keeps deferred analytics and Cloudflare Bot Fight.
Rejected path: Use Next.js App Router for every page
Overview
I needed a place to show how I work, not a gallery of thumbnails. This site is static-first Astro with MDX case studies (EN source, pt-BR overlays), a typographic selected-work rail on the homepage, a Featured products shelf, Community year folios, Playwright on critical paths, and machine-readable endpoints so agents and tooling can query the same facts without scraping HTML.
Problem
Most portfolios look interchangeable: dark theme, role-first hero, card grids, and little proof of judgment. They also leave AI crawlers and agent tooling to guess from messy HTML.
Constraints
- Zero JavaScript by default on content pages; React only if an interaction earns it.
- Strong Lighthouse Accessibility and SEO on the live homepage; document Perf trade-offs honestly.
- Typed content collections for case studies, talks, and apps.
- Paper-first light UI with readable contrast; dark remains optional.
- Measure real traffic (deferred gtag + Vercel Analytics); do not delete analytics to chase a vanity score.
- Keep Cloudflare edge protections usable; do not CSP-block challenge bootstrap for a Best Practices bump.
- One canonical profile for humans and agents (PDF + JSON Resume + MCP).
- AI tools allowed for speed, not for unsupervised architecture.
Architecture
Astro for the shell and homepage selected-work list. MDX + i18n overlays for cases and products. Vanilla CSS tokens (IBM Plex) for a quiet editorial system. Year folios and two-column grids instead of card dashboards. Playwright against the built site. Agent layer: profile.ts feeds llms.txt, resume.json, Person JSON-LD, and an MCP server (stdio + Streamable HTTP on Vercel). Production ships deferred analytics after idle and a CSP compatible with Cloudflare challenge scripts.
Key Decisions
Astro over Next.js
A content site does not need a React runtime on every page.
- Use Next.js App Router for every page (rejected: JS cost without benefit)
Static selected-work list instead of a React hero carousel
The carousel needed an island for fade and paging. A typographic list proves the same work with zero client JS on the homepage.
- Keep HeroCarousel as a React island (rejected: interactivity without enough value)
Deferred analytics over no analytics
A portfolio that claims engineering judgment should still measure visits. Analytics boot after idle with anonymized IP. Accept a modest Lighthouse cost instead of a silent site.
- Omit all analytics for a clean 100 Performance (rejected: unmeasurable product, score vanity)
- Sync analytics in <head> (rejected: blocks LCP/TBT unnecessarily)
Cloudflare-compatible CSP over hash-locked scripts
Hash-only script-src blocked Cloudflare challenge bootstrap and forced turning Bot Fight off for score parity. Production CSP allows the challenge path so edge protection stays on.
- Hash CSP that blocks CF challenge to force Best Practices 100 (rejected: security feature disabled for vanity)
Paper-first light default over dark-first
Dark tech portfolios are saturated. Light paper with stronger type contrast reads clearer on a first pass.
- Keep dark as default (rejected: looks like every other eng portfolio)
One Products shelf for every shipped tool
StackBrief and Dieta e Treino are both live products people can open. A Lab bucket made the second one look unfinished.
- Separate Products and Lab (rejected: diluted Dieta e Treino)
Agent-readable profile (llms.txt + resume.json + MCP) from one source
Agents and IDE tools already fetch llms.txt and MCP. One profile.ts keeps Person schema, JSON Resume, and tools aligned without duplicating MDX bodies.
- HTML-only and hope scrapers guess correctly (rejected)
- Hand-maintained parallel resume JSON (rejected: drift)
Playwright over component unit tests
Browser tests catch the flows that matter on a static build.
Vanilla CSS over Tailwind
Scoped styles and variables were enough here.
Tech stack
- Astro 5
- TypeScript
- MDX + Zod collections
- Vanilla CSS (IBM Plex)
- Playwright (E2E)
- Vercel + Cloudflare
- Google Analytics (deferred gtag)
- Vercel Web Analytics
- MCP (Streamable HTTP + stdio)
Impact
Audited Aug 9 2026 with Lighthouse 12.8.2 on https://www.erbsu.com/en/ (Cloudflare + deferred analytics). Desktop stays near-perfect; mobile Perf is the main www cost. Playwright a11y/nav suite: 8/8. Agent-readable endpoints ship with the same profile source as the PDF.
Learnings
- Treating JavaScript as a cost means removing islands when a static list is enough, not deleting analytics or edge security to print a perfect scorecard.
- A 100 earned by stripping measurement and Bot Fight reads as score vanity; document the trade-off.
- First impression fails on defensive copy and template chrome before it fails on stack choice.
- Accessibility and contrast from the start beat a late polish pass.
- Agent-readable files do not replace organic discovery. They make citations accurate once someone (or an agent) already lands on the domain.