# Why I Built My Portfolio with Bun + Astro + MDX Instead of a More Complex Stack

> Source: <https://dev.to/marcelotaparelli/why-i-built-my-portfolio-with-bun-astro-mdx-instead-of-a-more-complex-stack-2cmd>
> Published: 2026-09-13 13:05:47+00:00

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.

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 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.

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.

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.

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.

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 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](https://marcelotaparelli.com.br/en/projects/)

and the [about page](https://marcelotaparelli.com.br/en/about/) exist in both languages because the

system would accept nothing less.

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.

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.

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.

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.

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.
