Choosing a stack is a product decision before it is a technical one. This article documents why this portfolio runs on Bun, Astro, and MDX — and, more importantly, the reasoning that led me to turn down more sophisticated alternatives. Nothing here is theory: every claim points to something verifiable in this repository.
The problem
I needed a portfolio that could carry professional authority as a Software Engineer going deeper into Applied AI Engineering. The requirements were concrete: bilingual content (Portuguese and English), project cases and articles with their own identity, solid SEO, high performance, genuine accessibility, and maintenance simple enough that I would never have to think about it twice. No backend, no platform team, no infrastructure budget — just me, the code, and the content.
The question behind the architecture
The guiding question was simple: what is the simplest solution capable of solving this problem? Not “which stack is trending,” and not “which one shows off the most skill.” Technology chosen for hype often charges interest in complexity. Every layer I added had to justify itself against this specific problem — a mostly static content site maintained by one person.
Why Bun
Bun is this project’s toolchain: package manager, runner for the development scripts, and runtime for the validation tooling. The dev server, build, artifact validation, asset measurement, and release-check scripts are TypeScript executed directly by Bun, with no separate transpilation layer for tooling. I make no comparative claims I haven’t measured here — the decision was about structural simplicity: one tool where there would otherwise be several. What is observable is objective but contextual: in this implementation, on the local environment, the full build renders every page in about two seconds.
Why Astro
This site’s content is predominantly static, so HTML is generated at build time and served as files — no application server, no database, no per-request runtime. Astro was chosen for exactly that model: it lets me write composable editorial content while shipping almost no JavaScript to the browser. The script embedded in the home page is 277 bytes (198 gzipped) and exists for exactly one job: the mobile menu. Zero bytes of client-side framework — measured, not estimated. React was never installed because no problem required React. This model also simplifies SEO: every page arrives as complete HTML, with its own canonical and alternates.
Why MDX
Articles and cases live versioned alongside the code, in the same repository, under the same review process. There is no CMS or backend to maintain, update, or pay for — and no content outside version control. MDX gives the content structure (schema-validated frontmatter requiring a bilingual pair, slug, category, and review status), which makes every piece of writing an artifact as reviewable as any other code. Publishing means merging and building; rolling back means reverting a commit.
Why not Next.js, SPA, or CMS
Not because they are bad technologies — they are excellent for the right problems. A full-stack framework would solve problems I don’t have: per-request rendering, API routes, global client state. A client-rendered SPA would add JavaScript runtime and browser state to a site whose main job is delivering ready-to-read content. A CMS would trade versioned files for an external dependency with logins, backups, and a bill. None of that complexity was justified by the current problem, so none of it got in.
Bilingualism as an architecture requirement
Bilingualism is not a plugin bolted on afterward — it shaped the
architecture. Portuguese lives at /, English at /en/, and every
published item is required to exist in both languages: the publication
model rejects at build time anything published without its reviewed
counterpart. Every page carries its own canonical and three alternates
(current language, opposite language, and x-default), and the artifact
validator checks the pairs across every generated page. Incomplete content
simply never reaches production: drafts are excluded from the production
build and included only in the preview build. The projects page
and the about page exist in both languages because the
system would accept nothing less.
Quality as part of the product
Here, quality is not a promise — it is a pipeline. Every change goes through strict typechecking (zero errors), linting, verified formatting, unit tests for the publication model (4/4), thirteen browser tests (reciprocal PT/EN navigation, keyboard menu with Escape, no-JavaScript navigation, localized 404, explicit external links, 320px reflow with automated WCAG checks), and artifact validation in both build modes. If something breaks, the build says so — before any human needs to check.
CI/CD pipeline with GitHub Actions covering type checking, linting, unit tests, Playwright E2E tests, artifact validation, and production builds. Delivery follows a Continuous Delivery model: the same CI-approved artifact is published after explicit manual approval, without rebuilding, to a dedicated production branch and then deployed to Hostinger.
Content distribution
In addition to the CI/CD pipeline, I implemented a multichannel content distribution workflow. A single manual approval in GitHub Actions resolves the bilingual article pair and automatically publishes the English version to DEV.to and a tailored Portuguese post to LinkedIn. The workflow uses the official APIs, isolated secrets, canonical-based idempotency, a publication ledger on a dedicated branch, and partial-failure recovery — and has already published successfully to both channels.
Measured performance
What follows are laboratory measurements, never real-user data. I ran
Lighthouse 13.4.1 in headless Chromium, mobile simulation (412×823
viewport, simulated network and CPU throttling), three runs per page,
serving the local static build — methodology recorded in
scripts/lighthouse.ts, results in reports/lighthouse-summary.json. On
the home page: performance 100 and accessibility 100 across all three
runs, LCP between roughly 1.5 s and 1.7 s, CLS around 0.0006, zero TBT,
and about 93.5 KB of initial transfer. The asset budget
(reports/assets.json) shows where the lightness comes from: roughly
6 KB of gzipped CSS, about 0.2 KB of inline JavaScript, and fonts totaling
around 48 KB. Lab numbers inform decisions; they prove nothing about real
user experience, and I would never present them as such.
A bug worth finding
The artifact validator flagged a broken link on the Portuguese 404 page:
/404/. The cause was in the header’s language switcher, which used
Astro.url.pathname as the current-language link — and with trailing
slashes always on, that pathname renders as /404/. But the actual
artifact Astro generates for 404.astro is 404.html, a special 404
document for static hosting; the /404/ route never existed. The fix went
into the routing model (the 404 page now links its canonical
/404.html and /en/404/ paths), not into the test. Weakening the
validator would have hidden the symptom and destroyed its value. The
episode became a working rule: fix the publication model, never work
around the validator.
Trade-offs
Every choice has a cost, and these are mine, accepted: no backend, no database, no CMS — any new content requires a commit, a build, and a deploy. No React at launch — richer interactivity in the future will require revisiting the decision. No analytics — there is no real-usage telemetry, so the lab metrics above are the ceiling of what I can claim today. And bilingualism costs double review: every piece must exist, make sense, and be approved in both languages. A deliberate cost, because international reach and consistency across languages are part of the product I want to build.
Conclusion
Mature engineering is not about choosing the most sophisticated stack — it is about choosing complexity proportional to the problem. This portfolio could have been a full-stack monolith, an SPA, or a CMS instance; those approaches could work, but they would introduce capabilities and operational costs this project’s requirements never asked for. Technology starts from the human problem, not from the code: understand what needs to change, choose deliberately, and examine the outcome with evidence. FROM REAL PROBLEMS TO INTELLIGENT PRODUCTS — including when building the showcase itself.
Back to articles