I could have created this portfolio with some online template or no-code solution — but I didn't want to do that 🤓 Instead, I've used this portfolio as a way to showcase my skills and way of coding.
If you have a look inside this repository you'll find a well-organized, linted and strictly formatted project with a proper light / dark / system theme, full internationalization (English / Spanish / Swedish), and a real test suite.
The code is formatted with Prettier and linted with ESLint.
- Next.js 16 — App Router, Turbopack dev, static pre-render per locale
- React 19 + TypeScript 5.9
- Tailwind CSS v4 — CSS-first
@themetokens,classdark mode - Light / dark / system theme via CSS variables and a small
ThemeProvider - next-intl v4 — locale-prefixed routing (
/en,/es,/sv), per-locale metadata +hreflang - Vitest + React Testing Library + jsdom
- Local font: Clash Display via
next/font/local
- Install dependencies:
npm i - Run the app locally:
npm run dev— openhttp://localhost:3000(redirects to/en) - Typecheck:
npm run typecheck - Lint:
npm run lint - Production build:
npm run build
Powered by Vitest + React Testing Library + jsdom.
npm test # single-run
npm run test:watch # watch mode
npm run test:coverage # v8 coverage report (text + html + lcov)Coverage output lands in coverage/ (gitignored). The suite currently covers:
- Utilities:
cn,buildStyle(padding/margin shorthand expansion, animation-name mapping, 4px multiplier) - Interactive components:
Accordion,Button,ThemeToggle,LocaleSwitcher,Menu, navigationItems - i18n safety net:
test/i18n-keys.test.tsassertsmessages/es.jsonandmessages/sv.jsonhave the same key set asen.json— catches missing translations before they ship
The suite is stable across 5 consecutive runs and completes in ~4s locally.
Three locales: English (default), Spanish, Swedish.
- URLs are locale-prefixed (
/en/about,/es/about,/sv/about) - English is the hard default — visiting
/always redirects to/enregardless ofAccept-Language - Language + theme controls live in the header (desktop) and split between the hamburger menu (language) + footer (theme) on mobile
- Translations live in
messages/— one JSON file per locale, keyed by page/domain