{"slug": "why-i-built-my-portfolio-with-bun-astro-mdx-instead-of-a-more-complex-stack", "title": "Why I Built My Portfolio with Bun + Astro + MDX Instead of a More Complex Stack", "summary": "A software engineer building a personal portfolio while moving into applied AI engineering chose Bun, Astro, and MDX over heavier full-stack, SPA, or CMS alternatives, arguing that stack selection is a product decision before a technical one. The site generates static HTML at build time, ships 277 bytes of JavaScript (198 gzipped) for the mobile menu, and uses schema-validated MDX frontmatter that rejects at build time any article published without its reviewed bilingual counterpart. The engineer reports the full build renders every page in roughly two seconds on the local environment.", "body_md": "Choosing a stack is a product decision before it is a technical one. This\n\narticle documents why this portfolio runs on Bun, Astro, and MDX — and,\n\nmore importantly, the reasoning that led me to turn down more sophisticated\n\nalternatives. Nothing here is theory: every claim points to something\n\nverifiable in this repository.\n\nI needed a portfolio that could carry professional authority as a Software\n\nEngineer going deeper into Applied AI Engineering. The requirements were\n\nconcrete: bilingual content (Portuguese and English), project cases and\n\narticles with their own identity, solid SEO, high performance, genuine\n\naccessibility, and maintenance simple enough that I would never have to\n\nthink about it twice. No backend, no platform team, no infrastructure\n\nbudget — just me, the code, and the content.\n\nThe guiding question was simple: what is the simplest solution capable of\n\nsolving this problem? Not \"which stack is trending,\" and not \"which one\n\nshows off the most skill.\" Technology chosen for hype often charges\n\ninterest in complexity. Every layer I added had to justify itself against this specific\n\nproblem — a mostly static content site maintained by one person.\n\nBun is this project's toolchain: package manager, runner for the\n\ndevelopment scripts, and runtime for the validation tooling. The dev\n\nserver, build, artifact validation, asset measurement, and release-check\n\nscripts are TypeScript executed directly by Bun, with no separate\n\ntranspilation layer for tooling. I make no comparative claims I haven't\n\nmeasured here — the decision was about structural simplicity: one tool\n\nwhere there would otherwise be several. What is observable is objective\n\nbut contextual: in this implementation, on the local environment, the full\n\nbuild renders every page in about two seconds.\n\nThis site's content is predominantly static, so HTML is generated at build\n\ntime and served as files — no application server, no database, no\n\nper-request runtime. Astro was chosen for exactly that model: it lets me\n\nwrite composable editorial content while shipping almost no JavaScript to\n\nthe browser. The script embedded in the home page is 277 bytes (198\n\ngzipped) and exists for exactly one job: the mobile menu. Zero bytes of\n\nclient-side framework — measured, not estimated. React was never installed\n\nbecause no problem required React. This model also simplifies SEO: every\n\npage arrives as complete HTML, with its own canonical and alternates.\n\nArticles and cases live versioned alongside the code, in the same\n\nrepository, under the same review process. There is no CMS or backend to\n\nmaintain, update, or pay for — and no content outside version control. MDX\n\ngives the content structure (schema-validated frontmatter requiring a\n\nbilingual pair, slug, category, and review status), which makes every piece\n\nof writing an artifact as reviewable as any other code. Publishing means\n\nmerging and building; rolling back means reverting a commit.\n\nNot because they are bad technologies — they are excellent for the right\n\nproblems. A full-stack framework would solve problems I don't have:\n\nper-request rendering, API routes, global client state. A client-rendered\n\nSPA would add JavaScript runtime and browser state to a site whose main\n\njob is delivering ready-to-read content. A CMS would trade versioned files for an external dependency with logins,\n\nbackups, and a bill. None of that complexity was justified by the current\n\nproblem, so none of it got in.\n\nBilingualism is not a plugin bolted on afterward — it shaped the\n\narchitecture. Portuguese lives at `/`, English at `/en/`, and every\n\npublished item is required to exist in both languages: the publication\n\nmodel rejects at build time anything published without its reviewed\n\ncounterpart. Every page carries its own canonical and three alternates\n\n(current language, opposite language, and x-default), and the artifact\n\nvalidator checks the pairs across every generated page. Incomplete content\n\nsimply never reaches production: drafts are excluded from the production\n\nbuild and included only in the preview build. The [projects page](https://marcelotaparelli.com.br/en/projects/)\n\nand the [about page](https://marcelotaparelli.com.br/en/about/) exist in both languages because the\n\nsystem would accept nothing less.\n\nHere, quality is not a promise — it is a pipeline. Every change goes\n\nthrough strict typechecking (zero errors), linting, verified formatting,\n\nunit tests for the publication model (4/4), thirteen browser tests\n\n(reciprocal PT/EN navigation, keyboard menu with Escape, no-JavaScript\n\nnavigation, localized 404, explicit external links, 320px reflow with\n\nautomated WCAG checks), and\n\nartifact validation in both build modes. If something breaks, the build\n\nsays so — before any human needs to check.\n\nCI/CD pipeline with GitHub Actions covering type checking, linting, unit\n\ntests, Playwright E2E tests, artifact validation, and production builds.\n\nDelivery follows a Continuous Delivery model: the same CI-approved\n\nartifact is published after explicit manual approval, without rebuilding,\n\nto a dedicated production branch and then deployed to Hostinger.\n\nWhat follows are laboratory measurements, never real-user data. I ran\n\nLighthouse 13.4.1 in headless Chromium, mobile simulation (412×823\n\nviewport, simulated network and CPU throttling), three runs per page,\n\nserving the local static build — methodology recorded in\n\n`scripts/lighthouse.ts`, results in `reports/lighthouse-summary.json`. On\n\nthe home page: performance 100 and accessibility 100 across all three\n\nruns, LCP between roughly 1.5 s and 1.7 s, CLS around 0.0006, zero TBT,\n\nand about 93.5 KB of initial transfer. The asset budget\n\n(`reports/assets.json`) shows where the lightness comes from: roughly\n\n6 KB of gzipped CSS, about 0.2 KB of inline JavaScript, and fonts totaling\n\naround 48 KB. Lab numbers inform decisions; they prove nothing about real\n\nuser experience, and I would never present them as such.\n\nThe artifact validator flagged a broken link on the Portuguese 404 page:\n\n`/404/`. The cause was in the header's language switcher, which used\n\n`Astro.url.pathname` as the current-language link — and with trailing\n\nslashes always on, that pathname renders as `/404/`. But the actual\n\nartifact Astro generates for `404.astro` is `404.html`, a special 404\n\ndocument for static hosting; the `/404/` route never existed. The fix went\n\ninto the routing model (the 404 page now links its canonical\n\n`/404.html` and `/en/404/` paths), not into the test. Weakening the\n\nvalidator would have hidden the symptom and destroyed its value. The\n\nepisode became a working rule: fix the publication model, never work\n\naround the validator.\n\nEvery choice has a cost, and these are mine, accepted: no backend, no\n\ndatabase, no CMS — any new content requires a commit, a build, and a\n\ndeploy. No React at launch — richer interactivity in the future will\n\nrequire revisiting the decision. No analytics — there is no real-usage\n\ntelemetry, so the lab metrics above are the ceiling of what I can claim\n\ntoday. And bilingualism costs double review: every piece must exist, make\n\nsense, and be approved in both languages. A deliberate cost, because\n\ninternational reach and consistency across languages are part of the\n\nproduct I want to build.\n\nMature engineering is not about choosing the most sophisticated stack —\n\nit is about choosing complexity proportional to the problem. This\n\nportfolio could have been a full-stack monolith, an SPA, or a CMS\n\ninstance; those approaches could work, but they would introduce\n\ncapabilities and operational costs this project's requirements never asked\n\nfor. Technology starts from the human\n\nproblem, not from the code: understand what needs to change, choose\n\ndeliberately, and examine the outcome with evidence. FROM REAL PROBLEMS TO\n\nINTELLIGENT PRODUCTS — including when building the showcase itself.", "url": "https://wpnews.pro/news/why-i-built-my-portfolio-with-bun-astro-mdx-instead-of-a-more-complex-stack", "canonical_source": "https://dev.to/marcelotaparelli/why-i-built-my-portfolio-with-bun-astro-mdx-instead-of-a-more-complex-stack-2cmd", "published_at": "2026-09-13 13:05:47+00:00", "updated_at": "2026-09-13 13:39:58.728093+00:00", "lang": "en", "topics": ["developer-tools", "ai-products"], "entities": ["Bun", "Astro", "MDX", "React"], "alternates": {"html": "https://wpnews.pro/news/why-i-built-my-portfolio-with-bun-astro-mdx-instead-of-a-more-complex-stack", "markdown": "https://wpnews.pro/news/why-i-built-my-portfolio-with-bun-astro-mdx-instead-of-a-more-complex-stack.md", "text": "https://wpnews.pro/news/why-i-built-my-portfolio-with-bun-astro-mdx-instead-of-a-more-complex-stack.txt", "jsonld": "https://wpnews.pro/news/why-i-built-my-portfolio-with-bun-astro-mdx-instead-of-a-more-complex-stack.jsonld"}}