My ongoing personal portfolio platform. It is built with Next.js, TypeScript, Tailwind, and next-intl, with content kept directly in the repo.
- Next.js 16
- React 19
- TypeScript
- Tailwind CSS 4
next-intl- Biome, Vitest, and Playwright
- Vercel
pnpm install
pnpm devOpen http://localhost:3000.
Use .env.example if you need to override the canonical site URL locally or in previews.
pnpm dev # Start the dev server
pnpm build # Build for production
pnpm start # Run the production build
pnpm lint # Run Biome checks
pnpm format # Format files
pnpm typecheck # Run TypeScript checks
pnpm test # Run unit/component tests
pnpm test:coverage # Run tests with coverage
pnpm test:e2e # Run Playwright smoke tests
pnpm check # Lint + typecheck + test + buildmessages/ UI translations
public/ Images, icons, maps, fonts, and other static files
src/
app/ Routes, layouts, metadata routes, and route tests
components/ UI components and component tests
i18n/ Locale routing and i18n helpers
lib/ Site config, typed content, validation, and helpers
test-utils/ Shared Vitest setup, mocks, and factories
proxy.ts next-intl middleware entrypoint
tests/ Playwright smoke tests
Tests live close to the code they cover:
*.test.ts/*.test.tsxundersrc/are Vitest tests.src/test-utils/contains shared testing helpers, not app code.*.spec.tsundertests/are Playwright browser tests.
Portfolio content is stored as typed files instead of a CMS:
- Projects:
src/lib/project-entries.ts - Posts:
src/lib/post-entries.ts - Gear:
src/lib/gear-entries.ts - Tools:
src/lib/tool-entries.ts
Collection images live under public/images/collections/<collection>/.
Keep slugs unique, use descriptive alt text, and prefer filenames that match the content slug. Content rules are covered by src/lib/content-validation.test.ts.
UI copy lives in:
messages/en.jsonmessages/pt.jsonmessages/da.json
When adding or renaming a key, update every locale file and run pnpm test.
Posts are currently English-only. The UI is localized, but long-form post bodies do not have translated versions yet.
Before shipping bigger changes, run:
pnpm check
pnpm test:e2epnpm test:e2e intentionally uses http://localhost:3000; using 127.0.0.1 can break local hydration with Next.js dev assets.
The site is built for Vercel.
- Set
NEXT_PUBLIC_SITE_URLfor each environment. - Run
pnpm buildbefore deploying manually. - Security headers live in
next.config.ts. - Metadata, sitemap, and robots output come from the typed content and locale config.