Skip to content

Mateusz Kluza — Portfolio
Bilingual website for a classical artist (double bassist, composer, educator) built with Next.js 14 + Strapi v5 — with WCAG accessibility, JSON-LD, and a custom CMS for events.

CLIENTMateusz Kluza
TERM2026-07
ROLEFull-Stack Developer / Architect
STATUSCompleted

"A website that sounds like its owner — elegant, understated, and buttoned down to the last detail. From concept to final commit."

Mateusz Kluza — Portfolio: a bilingual artist website with headless CMS and full accessibility

The website of Mateusz Kluza — double bassist, composer, and educator. The project combines an elegant, minimalist aesthetic with modern engineering: Next.js 14 (App Router), Strapi v5 as a headless CMS, bilingualism (PL/EN), and rigorous accessibility and SEO support.

📋 Project metrics

  • Status: Completed (production)
  • Role: Full-Stack Developer / Architect
  • Scope: Design and implementation of the full website — frontend, CMS, deployment
  • Goal: A professional, bilingual web presence for a classical artist, with self-managed content

🚀 Project evolution (Product Journey)

  1. Discovery and concept — defining the visual direction (elegance, minimalism, typography-driven), content structure, and information architecture.
  2. Frontend (Next.js 14) — building the site with App Router, Server Components by default, bilingual routing (/ PL, /en/ EN), landing page sections.
  3. CMS (Strapi v5) — a dedicated "Concert" content type with fields: title, slug, date, venue, description (HTML), gallery, cover image, programme, tickets, cover layout.
  4. Accessibility and SEO — JSON-LD (Person, MusicEvent, ItemList), dynamic sitemap, breadcrumbs, lightbox with focus trap and ARIA dialog.
  5. Deployment — Docker Compose + Traefik (Dokploy), container isolation, ISR (revalidate: 300).

🎯 Business problem

A classical artist needs a professional web presence that doesn't look like a template. Existing solutions (social media, simple pages) offered no control over content, bilingualism, or search positioning.

❌ Pain points and challenges

  • No dedicated home: Social media as the sole information source — no own domain or control over the narrative.
  • Bilingualism: The need to support PL and EN without duplicating code and content.
  • Event management: Manual concert additions, no automatic split into upcoming/archive.
  • SEO for a niche: Competition for the artist's name in Google required structured data and a dynamic sitemap.
  • Accessibility: A classical artist's website must be accessible to everyone, including screen reader users.
  • Photo galleries: Photo sessions required an elegant lightbox with keyboard support and focus trap.

💡 Why it works (Engineering approach)

  • Server Components by default: The site is fast and SEO-friendly. Client components only where necessary (galleries, lightbox).
  • Headless CMS (Strapi v5): Mateusz adds events, galleries, and photos himself — without touching code.
  • React.cache deduplication: getConcertBySlug wrapped in React.cache — a single fetch per page (generateStaticParams + generateMetadata + page).
  • JS-side filtering: Concerts fetched once (fetchAllConcerts), split into upcoming/archive in Next.js — a workaround for Strapi v5 date filter bugs.
  • Portal-based lightbox: Gallery and cover lightbox use React Portal to document.body — escaping stacking contexts for correct layering.
  • HTML sanitisation: HTML content from Strapi is sanitised with DOMPurify.sanitize() before dangerouslySetInnerHTML.
  • ISR: revalidate: 300 — the site refreshes every 5 minutes, balancing freshness and performance.

📈 Business impact (ROI)

MetricBeforeAfterSignificance
Web presenceSocial media onlyOwn domain + bilingual websiteFull control over narrative and branding
Content managementManual / via developerSelf-service via Strapi CMSIndependence, zero ongoing costs
SEONo structured dataJSON-LD + dynamic sitemapHigher Google visibility for the name
AccessibilityUnaddressedWCAG: focus trap, ARIA, skip linkWebsite accessible to everyone
EventsManual updatesAutomatic upcoming/archive splitAlways up to date, zero manual work

Lighthouse Scores

Native image optimisation, elimination of unused code, and a classless structure yielded excellent performance results:

  • Desktop: 🟢 100 Performance | 🟢 93 Accessibility | 🟢 100 Best Practices | 🟢 100 SEO
  • Mobile: 🟢 94 Performance | 🟢 93 Accessibility | 🟢 100 Best Practices | 🟢 100 SEO

"A website that sounds like its owner — elegant, understated, and buttoned down to the last detail. From concept to final commit."

🛠️ Technical challenges (Engineering Deep Dive)

  • Bilingual routing and dynamic paths: Configuring dual language structures (/wydarzenia/[slug] in Polish and /en/events/[slug] in English) required precise parameter mapping in the Next.js engine so that the dynamic sitemap generated correct hreflang paths for Google crawlers.
  • Safe portal for the lightbox: To avoid image clipping caused by overflow: hidden on parent landing page sections, the lightbox renders directly in document.body via a React Portal. A strict focus trap locks tab navigation inside the modal, with full keyboard support (Esc, arrow keys).

🛠️ Architecture and tech stack

  • Frontend: Next.js 14 (App Router), TypeScript, Tailwind CSS, next/font (EB Garamond + Inter)
  • CMS: Strapi v5 + PostgreSQL (headless, server-side only)
  • Images: next/image with remotePatterns for Strapi, medium/large formats
  • HTML sanitisation: isomorphic-dompurify
  • PDF: Playwright (Chromium)
  • Structured Data: JSON-LD (Person, ProfilePage, WebSite, MusicEvent, ItemList)
  • Accessibility: Skip link, semantic HTML, aria-labelledby, role="dialog", aria-modal, focus trap, prefers-reduced-motion
  • Infra: Docker Compose + Traefik (Dokploy), container isolation (mateusz-kluza-portfolio- prefix)
  • ISR: revalidate: 300 on all Strapi fetches

🎨 Key design decisions

  • Typography-driven design: EB Garamond (serif) for headings, Inter (sans) for body — classical elegance + readability.
  • Alternating sections: bg-ink / bg-graphite — alternating section backgrounds, never two of the same side by side.
  • Cover layout variants: horizontal (full-width banner) vs vertical (300px column + clickable lightbox) — flexibility per event.
  • Static bilingualism: Section content in content/pl.ts and content/en.ts — full control, not automatic translation.

Artifacts

Contact

Have a similar challenge? Write to me — I will come back with a proposal for next steps.

Send message