2026 · Design & engineering
Building simon.kodu.se
A portfolio that is itself a case study in modern web engineering — Next.js 16, React 19, Tailwind v4, Velite MDX, Lighthouse CI gating.
- Next.js 16
- React 19
- Tailwind v4
- Velite
- Biome
- Playwright
- Vitest
- Lighthouse CI
Last updated
Why
A portfolio should demonstrate, not just describe. Every architectural choice here is a talking point.
Architecture
- pnpm workspace monorepo —
apps/web+packages/ui,packages/content,packages/config. - Next 16 with
cacheComponents— static shell streams instantly; work grid streams in after Velite-compiled MDX resolves. - Velite MDX pipeline — work entries compiled to RSC-rendered components;
rehype-pretty-code,remark-gfm, slug + autolink headings. - Canvas particle system — 2D comet trails weave between DOM waypoints using catmull-rom interpolation, with reduced-motion, low-memory, and small-viewport opt-outs.
- Turbopack dev — sub-second HMR across the monorepo.
- Strict CSP —
script-src 'self' 'unsafe-inline'enforced via middleware; MDX evaluated server-side so no browsereval.
AI fit-score
Recruiter pastes job description (or URL) → backend returns match_percentage, ranked strengths, gaps, and a 3-sentence summary.
- Route handler at
/api/fit-score—gpt-5.5via Chat Completions,response_format: json_object,max_completion_tokens: 1500. Param shape branches on model family (gpt-5 usesmax_completion_tokens+ default temperature; legacy gpt-4o acceptedmax_tokens+temperature). - Prompt = candidate profile + JD + rules. Single source of truth (
lib/fit-score.ts); swap providers = swap caller, not prompt. Stable prefix kept first for prompt-cache hits. - Weighted scoring rubric — university courses count as real evidence, high-school courses as background only, courses with shipped projects weighted higher. Education-match bonus when ad asks for the candidate's programme; seniority cap only when ad explicitly demands years/full-time/senior.
- Baseline-familiarity rule — ubiquitous developer tools (GitHub, Excel, Slack, common IDEs) are assumed for any CS-bachelor candidate; never gap them as standalone. Git ⇒ GitHub, Jenkins ⇒ CI/CD inferred.
- Security-clearance handling — säkerhetsprövning / registerkontroll / försvarssekretess are employer-run vetting, not candidate skills. Swedish citizen ⇒ eligible; never gapped, never penalized.
- Newgrad-friendly detection + floor — regex flags ads with "nyexaminerad", "newly graduated", "från nyexaminerad till erfaren", etc. Post-LLM sanitizer applies a score floor (90/87/84/80 at 0/1/2/3 kept gaps) and a gap-removal bonus when the sanitizer drops false gaps.
- Explicit-de-prioritization rule — when an ad states attitude/learning matters more than exact tools, the merit list becomes flavor only — no gaps from it, floor lifted to ≥88.
- URL ingestion with SSRF guard — strips
<script>/<style>/HTML, blocks private/loopback hosts (10.,127.,192.168.,::1,fc/fd…), 8s timeout, 500 KB read cap, 14000-char prompt cap. - Strict JSON contract —
{ match_percentage, strengths[], gaps[], summary }(strengths 2–4, gaps 0–3). Parse failure → 500, no leaked prose. Preview/debug mode attaches a full scoring trace (llm_raw,jd_flags,gap_decisions, score pipeline) so every adjustment is auditable in the UI.
Performance budget
Lighthouse CI gates every PR: perf ≥ 0.9, a11y ≥ 0.95, best-practices ≥ 0.95, SEO ≥ 0.95, LCP ≤ 3.0s, CLS ≤ 0.05.
Tooling
- Biome — single-binary lint + format.
- Vitest + @testing-library/react — component unit tests.
- Playwright + @axe-core/playwright — e2e and a11y sweeps.
- Husky + lint-staged + commitlint — conventional commits enforced pre-commit.