Mateusz Kluza — Portfolio: dwujęzyczna strona artysty z headless CMS i pełną dostępnością
Strona internetowa Mateusza Kluzy — kontrabasisty, kompozytora i pedagoga. Projekt łączy elegancką, minimalistyczną estetykę z nowoczesną inżynierią: Next.js 14 (App Router), Strapi v5 jako headless CMS, dwujęzyczność (PL/EN) oraz rygorystyczne wsparcie dostępności i SEO.
📋 Metryki projektu
- Status: Zakończone (produkcja)
- Rola: Full-Stack Developer / Architect
- Zakres: Projekt i wdrożenie pełnej strony — frontend, CMS, deployment
- Cel: Profesjonalne, dwujęzyczne miejsce w sieci dla artysty klasycznego, z własnym zarządzaniem treścią
🚀 Ewolucja projektu (Product Journey)
- Discovery i koncepcja — ustalenie wizji wizualnej (elegancja, minimalizm, typography-driven), struktury treści i architektury informacji.
- Frontend (Next.js 14) — budowa strony w App Router z Server Components domyślnie, dwujęzyczne routing (
/PL,/en/EN), sekcje landing page. - CMS (Strapi v5) — dedykowany content type „Concert" z polami: tytuł, slug, data, venue, opis (HTML), galeria, cover image, program, bilety, layout okładki.
- Dostępność i SEO — JSON-LD (Person, MusicEvent, ItemList), dynamiczny sitemap, breadcrumbs, lightbox z focus trap i ARIA dialog.
- Deployment — Docker Compose + Traefik (Dokploy), izolacja kontenerów, ISR (
revalidate: 300).
🎯 Problem biznesowy
Artysta klasyczny potrzebuje profesjonalnej wizytówki w sieci, która nie wygląda jak szablon. Dotychczasowe rozwiązania (social media, proste strony) nie dawały kontroli nad treścią, dwujęzycznością ani pozycjonowaniem.
❌ „Bóle” i wyzwania
- Brak dedykowanego miejsca: Social media jako jedyne źródło informacji — brak własnej domeny i kontroli nad narracją.
- Dwujęzyczność: Konieczność obsługi PL i EN bez duplikacji kodu i treści.
- Zarządzanie wydarzeniami: Ręczne dodawanie koncertów, brak automatycznego podziału na nadchodzące/archiwum.
- SEO dla niszy: Konkurencja o nazwisko w Google wymagała structured data i dynamicznego sitemap.
- Dostępność: Strona artysty klasycznego musi być dostępna dla wszystkich, w tym osób korzystających z czytników ekranu.
- Galerie zdjęć: Sesje zdjęciowe wymagały eleganckiego lightboxa z obsługą klawiatury i focus trap.
💡 Dlaczego to działa? (Podejście inżynieryjne)
- Server Components domyślnie: Strona jest szybka i SEO-friendly. Client components tylko tam, gdzie konieczne (galerie, lightbox).
- Headless CMS (Strapi v5): Mateusz sam dodaje wydarzenia, galerie i zdjęcia — bez dotykania kodu.
- React.cache deduplikacja:
getConcertBySlugopakowany wReact.cache— jeden fetch na stronę (generateStaticParams + generateMetadata + page). - Filtrowanie JS-side: Koncerty pobierane raz (
fetchAllConcerts), dzielone na nadchodzące/archiwum w Next.js — obejście filtrów dat Strapi v5 (bug). - Portal-based lightbox: Galerie i cover lightbox używają React Portal do
document.body— ucieczka ze stacking contexts, poprawne warstwowanie. - HTML sanitization: Treści HTML z Strapi sanityzowane
DOMPurify.sanitize()przeddangerouslySetInnerHTML. - ISR:
revalidate: 300— strona odświeża się co 5 minut, balans między świeżością a wydajnością.
📈 Wpływ na biznes (ROI)
| Metryka | Przed | Po wdrożeniu | Znaczenie |
|---|---|---|---|
| Obecność w sieci | Tylko social media | Własna domena + dwujęzyczna strona | Pełna kontrola nad narracją i brandingiem |
| Zarządzanie treścią | Ręczne / przez developera | Samodzielne via Strapi CMS | Niezależność, zero kosztów bieżących |
| SEO | Brak structured data | JSON-LD + dynamic sitemap | Wyższa widoczność w Google na nazwisko |
| Dostępność | Niezagospodarowana | WCAG: focus trap, ARIA, skip link | Strona dostępna dla wszystkich |
| Wydarzenia | Ręczne aktualizacje | Automatyczny podział nadchodzące/archiwum | Zawsze aktualne, zero ręcznej pracy |
Lighthouse Scores
Natywna optymalizacja obrazów, eliminacja nieużywanego kodu oraz bezklasowa struktura zaowocowały doskonałymi wynikami wydajności:
- Desktop: 🟢 100 Performance | 🟢 93 Accessibility | 🟢 100 Best Practices | 🟢 100 SEO
- Mobile: 🟢 94 Performance | 🟢 93 Accessibility | 🟢 100 Best Practices | 🟢 100 SEO
„Strona, która brzmi jak jej właściciel — elegancka, dyskretna i dopięta na najdrobniejszy guzik. Od koncepcji po ostatni commit."
🛠️ Wyzwania techniczne (Engineering Deep Dive)
- Dwujęzyczny routing i dynamiczne ścieżki: Konfiguracja podwójnych struktur językowych (
/wydarzenia/[slug]w języku polskim oraz/en/events/[slug]w języku angielskim) wymagała precyzyjnego mapowania parametrów w silniku Next.js, tak aby dynamiczny sitemap generował poprawne ścieżkihreflangdla robotów Google. - Bezpieczny portal dla Lightboxa: Aby uniknąć problemów z przycinaniem obrazów przez właściwości
overflow: hiddennadrzędnych sekcji na landing page, lightbox renderuje się bezpośrednio wdocument.bodyprzez React Portal. Zaimplementowano rygorystyczny focus trap blokujący tabulację wewnątrz modala i pełną obsługę klawiatury (Esc, strzałki).
🛠️ Architektura i stack techniczny
- Frontend: Next.js 14 (App Router), TypeScript, Tailwind CSS, next/font (EB Garamond + Inter)
- CMS: Strapi v5 + PostgreSQL (headless, server-side only)
- Obrazy: next/image z remotePatterns dla Strapi, formaty medium/large
- Sanityzacja HTML: isomorphic-dompurify
- PDF: Playwright (Chromium)
- Structured Data: JSON-LD (Person, ProfilePage, WebSite, MusicEvent, ItemList)
- Dostępność: Skip link, semantic HTML,
aria-labelledby,role="dialog",aria-modal, focus trap,prefers-reduced-motion - Infra: Docker Compose + Traefik (Dokploy), izolacja kontenerów (prefiks
mateusz-kluza-portfolio-) - ISR:
revalidate: 300na wszystkich fetchach ze Strapi
🎨 Kluczowe decyzje projektowe
- Typography-driven design: EB Garamond (serif) dla nagłówków, Inter (sans) dla treści — elegancja klasyczna + czytelność.
- Alternating sections:
bg-ink/bg-graphite— naprzemienne tła sekcji, nigdy dwa te same obok siebie. - Cover layout variants:
horizontal(full-width banner) vsvertical(300px kolumna + clickable lightbox) — elastyczność per wydarzenie. - Dwujęzyczność statyczna: Treści sekcji w
content/pl.tsicontent/en.ts— pełna kontrola, nie automatyczne tłumaczenie.
Artefakty
- Strona: https://mateuszkluza.pl