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)
- Discovery and concept — defining the visual direction (elegance, minimalism, typography-driven), content structure, and information architecture.
- Frontend (Next.js 14) — building the site with App Router, Server Components by default, bilingual routing (
/PL,/en/EN), landing page sections. - CMS (Strapi v5) — a dedicated "Concert" content type with fields: title, slug, date, venue, description (HTML), gallery, cover image, programme, tickets, cover layout.
- Accessibility and SEO — JSON-LD (Person, MusicEvent, ItemList), dynamic sitemap, breadcrumbs, lightbox with focus trap and ARIA dialog.
- 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:
getConcertBySlugwrapped inReact.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()beforedangerouslySetInnerHTML. - ISR:
revalidate: 300— the site refreshes every 5 minutes, balancing freshness and performance.
📈 Business impact (ROI)
| Metric | Before | After | Significance |
|---|---|---|---|
| Web presence | Social media only | Own domain + bilingual website | Full control over narrative and branding |
| Content management | Manual / via developer | Self-service via Strapi CMS | Independence, zero ongoing costs |
| SEO | No structured data | JSON-LD + dynamic sitemap | Higher Google visibility for the name |
| Accessibility | Unaddressed | WCAG: focus trap, ARIA, skip link | Website accessible to everyone |
| Events | Manual updates | Automatic upcoming/archive split | Always 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 correcthreflangpaths for Google crawlers. - Safe portal for the lightbox: To avoid image clipping caused by
overflow: hiddenon parent landing page sections, the lightbox renders directly indocument.bodyvia 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: 300on 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) vsvertical(300px column + clickable lightbox) — flexibility per event. - Static bilingualism: Section content in
content/pl.tsandcontent/en.ts— full control, not automatic translation.
Artifacts
- Website: https://mateuszkluza.pl