This portfolio asks a visitor to do two things quickly: understand what I build and inspect evidence for a project. Most of that journey is reading, following links and opening a case study. Those actions do not need a client-side application to render the page.

I use Astro’s static output for the site and keep project and article content in typed collections. The build turns those entries into HTML pages and assets before deployment. Cloudflare serves the generated dist directory; a visitor does not need a content API or database request to read the portfolio.

Keep the content in the document

Project titles, roles, descriptions, technologies, evidence labels and links are present in the generated page. The project schema also separates DEMONSTRATION, MEASURED, SIMULATED and ESTIMATED evidence. That gives the interface a way to distinguish a captured result from a benchmark or a model output instead of letting a polished image imply more than it shows.

This structure has a practical failure mode: if a small widget fails to load, the surrounding page and its ordinary links remain available. It also gives search engines and link previews useful page content without waiting for an application bundle to fetch and assemble it.

Hydrate only where interaction adds value

The Engineering X-Ray needs local state to switch between project views, so its Preact island loads with the page. Lab experiments are hydrated when they enter the viewport. The rest of the project cards, notes and navigation stay ordinary page content; they do not carry a framework runtime just to display text.

This is a boundary, not a rule that every interaction must be static. If a tool needs immediate feedback, client-side code can own that part while the page around it remains readable and linkable. Keeping the island small also makes it easier to review keyboard behavior and reduced-motion handling without turning the whole portfolio into one large application state machine.

Accept the navigation trade-off

Each route is a full document navigation. That means the browser reloads the page shell and client-side widget state resets when someone opens another page. In exchange, routes have direct URLs, navigation does not depend on a client router, and content updates follow the same build and review path as code changes.

Static delivery also has limits. Content changes require a new build and deployment, and a prebuilt page cannot personalize itself from a visitor account without adding another service. This portfolio does not need either capability today. If that changes, I would add the smallest service that answers the new requirement rather than introducing runtime state everywhere.

Validate the published artifact

The architecture is only useful if the output still works. The repository checks content and component types, runs unit and browser tests for interactive behavior, and audits links after the static build. Those checks can catch broken routes and regressions in tested flows; they do not replace testing on real devices or measure production traffic.

Astro documents output: 'static' as prerendering pages at build time by default; this repository sets that mode explicitly in astro.config.mjs at the measured revision (configuration reference). I measured the generated home route at source commit 180eff9 to check the cost of that choice.

PORTFOLIO BUILD / STATIC OUTPUT

One route, measured from the built files
16 OCT 2026 · 180eff9
56 generated pages4 X-Ray islands5 unique JS files
Files referenced by the generated home page
ResourceFilesRawGzip estimate
HTML document152.8 KB10.9 KB
JavaScript520.2 KB9.4 KB
Stylesheets352.5 KB12.8 KB
Measured subtotal9125.5 KB33.1 KB

Measured from local dist/index.html and its unique referenced JS/CSS files. Each file was compressed with .NET GZipStream at CompressionLevel.Optimal; this is not a browser network capture.

The subtotal excludes images, fonts, HTTP headers and cache effects. Astro emitted 55 route documents and one 404 page in this build.

The result is not “no JavaScript.” The home document references five unique JavaScript files: the shared X-Ray island and Preact renderer, plus the base, hero and signal modules. Four X-Ray cards reuse the same island module. The local gzip estimate for the document and these script and stylesheet files totals about 33.1 KB. Images, fonts, response headers and browser cache are outside that subtotal, and gzip output is not a substitute for a Safari or phone network trace.

To repeat the snapshot, run pnpm build, collect the unique JS and CSS paths referenced by dist/index.html, and sum their file sizes with the document. The figure compresses each file independently with .NET GZipStream at CompressionLevel.Optimal; its total is a local estimate, not a transfer-size guarantee.

The next measurement should use a real browser on target devices and include transfer encoding, image selection, font loading, and LCP/INP. That will show whether CSS or media dominate the visitor’s actual experience before I change the architecture. This build confirms one concrete trade-off: project evidence is in the document, while JavaScript is split across the controls that need it.

← All notes