Last updated: 2026-10-06
The site is static HTML, one stylesheet, vanilla TypeScript and one serverless function. The goal is a fast first paint and no layout shift on every page.
- Static output. Astro renders every page at build time (
compressHTML: true,inlineStylesheets: "auto"). The only server code isapi/quote.ts. - Fonts self-hosted. Poppins 400, 500 and 600 and the Raleway variable font (weights 600 to 800) are WOFF2 files in
public/fonts/, declared withfont-display: swapand a Latinunicode-range. Only Poppins 400 and Raleway are preloaded (Layout.astro). - LCP image. The profile photo is preloaded with
fetchpriority="high"only on the pages that render it (the home and about pages in both languages,preloadHero). The image has explicitwidthandheight. - Images. Raster content images are WebP (
public/images/,public/brand/; the PWA and touch icons are PNG), declare their dimensions and useloading="lazy"anddecoding="async"when they are below the fold. - Caching.
vercel.jsonsetsCache-Control: public, max-age=31536000, immutableon/_astro/,/images/,/brand/,/icons/and/fonts/, and one day on the CV PDFs. Filenames under/_astro/are content hashed. - JavaScript. Plain TypeScript modules for navigation, theme, the quote form and the project filters; no framework runtime. The ecosystem project pages add no new library: the accordions reuse one bundled
src/scripts/disclosure.tsand one smallsrc/scripts/part-disclosure.ts, and the stack preview on cards and rows is capped at 6 chips. Astro View Transitions (ClientRouter) make navigation feel instant. - Third parties. None are loaded by default. Umami (cookieless,
defer) and the Web Vitals reporter load only when bothPUBLIC_UMAMI_*variables are set; Calendly loads on the contact page only. - CSS. One global stylesheet source with tokens; Tailwind generates utilities only for classes that appear as complete literals (checked by
npm run css:check).
- Lighthouse CI (
npm run lhci, joblighthousein CI) audits home, projects, about and the English home fromdist/(one run per page,lighthouserc.json). Thresholds: performance and best practices warn below 0.9; accessibility and SEO fail below 0.9. - Core Web Vitals (LCP, INP, CLS, FCP, TTFB) are sent to Umami as
web-vitalsevents when analytics is enabled (src/scripts/vitals.ts). - There is no numeric performance budget; Lighthouse scores are the guardrail.
- The navigation loader (
Layout.astro) stays visible for at least one second during a page transition. That is a deliberate visual choice, not a loading cost. style-srcstill allows'unsafe-inline'; reviewing it is tracked inROADMAP.md.
- New image: WebP, explicit dimensions,
alt, lazy unless it is above the fold. - New font weight: preload only if it is used on first paint.
- New script: keep it vanilla, import it where it is used, then run
npm run build && npm run csp:checkbecause inline scripts need a CSP hash. - New third party: needs a CSP entry and a reason in
security.md; prefer none. - Run
npm run build && npm run lhcibefore opening a pull request that changes layout, images or scripts.
Poppins 500 and 600 are not preloaded (see ROADMAP.md).