Przejdź do treści

Mateusz Kluza — Portfolio
Dwujęzyczna strona artysty (kontrabasista, kompozytor, pedagog) w Next.js 14 + Strapi v5 — z dostępnością WCAG, JSON-LD i własnym CMS na wydarzenia.

KLIENTMateusz Kluza
TERMIN2026-07
ROLAFull-Stack Developer / Architect
STATUSZakończone

"Strona, która brzmi jak jej właściciel — elegancka, dyskretna i dopięta na najdrobniejszy guzik. Od koncepcji po ostatni commit."

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)

  1. Discovery i koncepcja — ustalenie wizji wizualnej (elegancja, minimalizm, typography-driven), struktury treści i architektury informacji.
  2. Frontend (Next.js 14) — budowa strony w App Router z Server Components domyślnie, dwujęzyczne routing (/ PL, /en/ EN), sekcje landing page.
  3. CMS (Strapi v5) — dedykowany content type „Concert" z polami: tytuł, slug, data, venue, opis (HTML), galeria, cover image, program, bilety, layout okładki.
  4. Dostępność i SEO — JSON-LD (Person, MusicEvent, ItemList), dynamiczny sitemap, breadcrumbs, lightbox z focus trap i ARIA dialog.
  5. 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: getConcertBySlug opakowany w React.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() przed dangerouslySetInnerHTML.
  • 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)

MetrykaPrzedPo wdrożeniuZnaczenie
Obecność w sieciTylko social mediaWłasna domena + dwujęzyczna stronaPełna kontrola nad narracją i brandingiem
Zarządzanie treściąRęczne / przez developeraSamodzielne via Strapi CMSNiezależność, zero kosztów bieżących
SEOBrak structured dataJSON-LD + dynamic sitemapWyższa widoczność w Google na nazwisko
DostępnośćNiezagospodarowanaWCAG: focus trap, ARIA, skip linkStrona dostępna dla wszystkich
WydarzeniaRęczne aktualizacjeAutomatyczny podział nadchodzące/archiwumZawsze 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żki hreflang dla robotów Google.
  • Bezpieczny portal dla Lightboxa: Aby uniknąć problemów z przycinaniem obrazów przez właściwości overflow: hidden nadrzędnych sekcji na landing page, lightbox renderuje się bezpośrednio w document.body przez 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: 300 na 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) vs vertical (300px kolumna + clickable lightbox) — elastyczność per wydarzenie.
  • Dwujęzyczność statyczna: Treści sekcji w content/pl.ts i content/en.ts — pełna kontrola, nie automatyczne tłumaczenie.

Artefakty

Kontakt

Masz podobne wyzwanie? Napisz do mnie — wrócę z propozycją kolejnych kroków.

Napisz wiadomość