Jak zbudowaliśmy nowoczesny, rzemieślniczy serwis dla klubu modelarskiego w Astro 5 – Case Study QWKAK
Case study serwisu QWKAK w Astro 5: automatyczna optymalizacja setek zdjęć (Sharp), edycja w TinaCMS, formularz Chatwoot i 100/100 w Google Lighthouse.
Jak zbudowaliśmy nowoczesny, rzemieślniczy serwis dla klubu modelarskiego w Astro 5 – Case Study QWKAK
Kiedy myślisz o modelarstwie kartonowym, przed oczami stają Ci arkusze papieru, maty samogojące do cięcia, nożyki skalpelowe i godziny precyzyjnego pasowania wręg. Kiedy zaczęliśmy budować nową stronę dla QWKAK (Quasi Warszawskiego Klubu Anonimowego Kartonocholika), postanowiliśmy przenieść tę samą dbałość o detal i rzemieślniczy klimat prosto do kodu.
Oto historia transformacji projektu: od wyczyszczenia automatycznie wygenerowanego prototypu, przez pełną migrację na Astro 5, aż po zaawansowaną integrację galerii tysięcy zdjęć konkursowych i bezserwerowy kontakt oparty o Chatwoot.
Jeśli prowadzisz jednoosobową firmę, stowarzyszenie, klub hobbystyczny lub tworzysz portfolio z setkami zdjęć realizacji, to studium przypadku pokaże Ci, jak zbudować nowoczesną stronę internetową bez ociężałego WordPressa, drogich wtyczek i powolnego ładowania.
Cel i wyzwania projektu: 20 lat tradycji i setki gigabajtów zdjęć
Strona klubu z ponad 20-letnią tradycją oraz organizatora ogólnopolskiego konkursu Kartonowa Syrenka musiała sprostać wymaganiom, które w klasycznym WordPressie szybko doprowadziłyby do zawału serwera i powolnego ładowania:
- Ekspresowa szybkość i lekkość (Zero-JS domyślnie): Czysty statyczny HTML bez narzutu ciężkich frameworków SPA (Single Page Application). Treści tekstowe i relacje ładują się w ułamku sekundy.
- Obsługa setek gigabajtów zdjęć modeli: Automatyczna kompresja, generowanie
srcset, formatów WebP i responsywnych miniaturek bez konieczności ręcznej obróbki w programach graficznych. - Elastyczność dla redaktorów (Markdown + TinaCMS): Możliwość łatwego dodawania wpisów i wstawiania galerii prostym znacznikiem w tekście, bez konieczności kodowania HTML czy znajomości Gita.
- Prywatność i niezależność: Brak inwazyjnych zewnętrznych trackerów, własna analityka oraz formularz zintegrowany z dedykowaną instancją Chatwoot.
- Dostępność i zgodność prawna: Standard WCAG 2.1 AA, pełna zgodność z RODO oraz wymogami UOKiK (jasne zasady cookies i procedury informacyjne).
Stack Technologiczny: Dlaczego właśnie te narzędzia?
Zamiast sięgać po standardowy szablon na WordPressie z 30 wtyczkami, dobraliśmy wyspecjalizowany, nowoczesny zestaw narzędzi dopasowany do specyfiki projektu:
| Warstwa | Technologia | Dlaczego właśnie to? |
|---|---|---|
| Core Framework | Astro 5.x | Architektura wysp (Islands Architecture), genialny silnik statyczny (SSG), zerowy JavaScript na stronach artykułów i relacji. |
| Optymalizacja Mediów | Sharp + astro:assets | Najszybszy silnik przetwarzania obrazów w Node.js, automatyczna konwersja do WebP i generowanie responsywnych wariantów w locie podczas budowania. |
| System Galerii | astro-gallery | Zestaw komponentów (Justified, Masonry, Karuzela 3D, Slideshow) z wbudowanym lightboxem i obsługą metadanych EXIF. |
| Zarządzanie Treścią | Astro Content Layer + TinaCMS | Artykuły i relacje zapisywane jako pliki Markdown/MDX ze ścisłą walidacją typów przez Zod i edycją wizualną na żywej stronie. |
| Komunikacja & CRM | Chatwoot API (Self-hosted) | Otwarty system obsługi wiadomości połączony przez asynchroniczne Client API bez ciężkich zewnętrznych ramek iframe. |
| Styling & Design | Custom Editorial CSS (BEM/Craft) | Unikalny, autorski motyw graficzny nawiązujący do papieru milimetrowego, maty modelarskiej, granatu i żółtych znaczników technicznych. |
| Analityka | Self-hosted Analytics | Lekki skrypt telemetryczny bez ciasteczek śledzących, bez profilowania użytkowników i w 100% zgodny z RODO. |
Jeśli chcesz dowiedzieć się więcej o architekturze opartej na plikach Git, sprawdź nasz szczegółowy poradnik: TinaCMS jako lekka alternatywa dla WordPress.
Architektura i kluczowe rozwiązania techniczne
┌────────────────────────────────────────────────────────────────────────┐
│ PROCES REDAKCYJNY I BUILD │
│ │
│ Redaktor (TinaCMS) ──> Markdown + Shortcode ──> pnpm build │
│ │ │
│ Katalog public/gallery/ ──> Sharp (WebP/srcset) ────────┤ │
│ ▼ │
│ Statyczny HTML │
│ (Zero-JS domyślnie) │
│ │ │
│ Interakcje na żądanie: │ │
│ • Lightbox w galerii (astro-gallery) <──────────────────┤ │
│ • Formularz Chatwoot API (asynchroniczny fetch) <───────┘ │
└────────────────────────────────────────────────────────────────────────┘
1. Rzemieślniczy system galerii i parser shortcode’ów
Głównym skarbem klubu są relacje z konkursów Kartonowa Syrenka. To setki zdjęć niesamowitych modeli okrętów, samolotów, pojazdów bojowych i dioram.
Gdyby redaktor musiał ręcznie układać każde zdjęcie w kodzie HTML lub ładować 200 pojedynczych bloków w edytorze wizualnym, dodanie jednej relacji zajmowałoby pół dnia.
Połączyliśmy globalny katalog public/gallery/ z silnikiem astro-gallery oraz stworzyliśmy dedykowany parser treści (src/lib/parseContent.ts). Redaktor w pliku Markdown wstawia galerię prostym znacznikiem w dowolnym miejscu tekstu:
Oto modele nagrodzone w kategorii lotniczej:
[galeria: syrenka_2024 typ="masonry" kolumny=4]
A poniżej relacja z uroczystego rozdania nagród...
Podczas budowania strony (pnpm build):
- Parser rozpoznaje znacznik w locie: Wyciąga nazwę katalogu (
syrenka_2024) oraz parametry układu (masonry,kolumny=4). - Wyszukuje odpowiedni folder zdjęć: Odczytuje listę plików graficznych w wysokiej rozdzielczości.
- Przetwarza zdjęcia przez bibliotekę Sharp: Generuje warianty WebP o różnych szerokościach (np. 400px, 800px, 1600px) i buduje atrybut
srcset. - Osadza gotowy komponent: Generuje responsywną siatkę z pełną obsługą klawiatury (strzałki, ESC), opisami ALT i wbudowanym lightboxem.
Oto schemat działania parsera shortcode’u w TypeScript (src/lib/parseContent.ts):
// src/lib/parseContent.ts
export interface GalleryShortcode {
folder: string;
type: "masonry" | "justified" | "grid" | "carousel";
columns: number;
}
export function extractGalleryShortcodes(content: string): {
cleanContent: string;
galleries: GalleryShortcode[];
} {
const shortcodeRegex =
/\[galeria:\s*([a-zA-Z0-9_-]+)(?:\s+typ="([^"]+)")?(?:\s+kolumny=(\d+))?\]/g;
const galleries: GalleryShortcode[] = [];
const cleanContent = content.replace(
shortcodeRegex,
(match, folder, type = "masonry", columns = "3") => {
galleries.push({
folder,
type: type as GalleryShortcode["type"],
columns: parseInt(columns, 10),
});
return `<!-- gallery-placeholder:${folder} -->`;
},
);
return { cleanContent, galleries };
}
Dzięki temu redaktor nie musi dotykać kodu ani martwić się rozmiarem plików, a strona zawsze serwuje idealnie zoptymalizowane miniatury.
2. Konfiguracja TinaCMS i modelowanie treści w Astro Content Layer
Aby redaktorzy klubu mogli pracować bez znajomości terminala i Gita, wdrożyliśmy TinaCMS zintegrowany z Content Collections w Astro 5. Struktura relacji została zdefiniowana w schemacie TypeScript z automatyczną walidacją przez bibliotekę Zod:
// tina/config.ts
import { defineConfig } from "tinacms";
export default defineConfig({
branch: "main",
clientId: process.env.TINA_CLIENT_ID,
token: process.env.TINA_TOKEN,
build: {
outputFolder: "admin",
publicFolder: "public",
},
schema: {
collections: [
{
name: "relacje",
label: "Relacje z Konkursów",
path: "src/content/relacje",
format: "md",
fields: [
{
type: "string",
name: "title",
label: "Tytuł relacji",
isTitle: true,
required: true,
},
{
type: "datetime",
name: "date",
label: "Data wydarzenia",
required: true,
},
{
type: "string",
name: "category",
label: "Kategoria",
options: ["Kartonowa Syrenka", "Warsztaty", "Wystawy"],
},
{
type: "rich-text",
name: "body",
label: "Treść relacji",
isBody: true,
},
],
},
],
},
});
Dzięki temu edycja odbywa się w przyjaznym panelu wizualnym, a każda zmiana zapisuje się bezpośrednio jako czysty plik .md w repozytorium projektu.
3. Formularz kontaktowy połączony z Chatwoot API
Zamiast osadzać ciężki widget w ramce <iframe> (który potrafi dołożyć 500 KB skryptów i opóźnić ładowanie o sekundę), formularz kontaktowy został napisany jako natywny, lekki komponent w Astro.
Jak działa komunikacja z Chatwoot:
- Bezpośrednie zapytanie do API: Po kliknięciu „Wyślij” przeglądarka wysyła asynchroniczne żądanie
fetchbezpośrednio do publicznego endpointu skrzynki Chatwoot (/public/api/v1/inboxes/{inbox_identifier}/contacts). - Tworzenie profilu kontaktu: Chatwoot automatycznie tworzy lub aktualizuje profil rozmówcy (imię, e-mail).
- Założenie nowego wątku: API otwiera nową konwersację (
/public/api/v1/inboxes/{inbox_identifier}/contacts/{source_id}/conversations) i przekazuje treść wiadomości wraz z tematem. - Buforowanie w localStorage (Offline Fallback): W przypadku utraty połączenia formularz nie gubi wpisanej treści, lecz zapisuje ją lokalnie i informuje użytkownika o możliwości ponowienia wysyłki.
// Fragment obsługi wysyłki formularza do Chatwoot API
async function sendMessageToChatwoot(formData) {
const INBOX_TOKEN = import.meta.env.PUBLIC_CHATWOOT_INBOX_TOKEN;
const BASE_URL = "https://kontakt.twoja-domena.pl/public/api/v1/inboxes";
try {
// 1. Utwórz lub zaktualizuj kontakt
const contactRes = await fetch(`${BASE_URL}/${INBOX_TOKEN}/contacts`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
name: formData.name,
email: formData.email,
}),
});
const contactData = await contactRes.json();
const sourceId = contactData.source_id;
// 2. Wyślij wiadomość w nowym wątku
const convRes = await fetch(
`${BASE_URL}/${INBOX_TOKEN}/contacts/${sourceId}/conversations`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
content: `[Formularz WWW: ${formData.subject}]\n\n${formData.message}`,
}),
},
);
if (convRes.ok) {
localStorage.removeItem("draft_contact_message");
return { success: true };
}
} catch (error) {
// Fallback: zachowaj treść lokalnie
localStorage.setItem("draft_contact_message", JSON.stringify(formData));
return { success: false, fallback: true };
}
}
4. Dostępność (WCAG 2.1 AA) i Pełny Compliance (RODO / UOKiK)
W erze rygorystycznych przepisów dotyczących praw konsumentów i ochrony danych, strona została wyposażona w kompletny zestaw zabezpieczeń:
- Dostępność od podstaw: Pełna obsługa nawigacji klawiaturą, czytelne stany
:focus-visible, kontrast typografii przewyższający wytyczne WCAG (stosunek kontrastu powyżej 8.5:1), semantyczne punkty orientacyjne ARIA i opisy alternatywne dla każdego zdjęcia. - Dedykowane dokumenty prawne: Kompletna Polityka Prywatności (RODO), Regulamin świadczenia usług drogą elektroniczną, Polityka Cookies oraz Procedura zgłoszeń.
- Inteligentny Baner Cookies: Niezbędne ciasteczka techniczne działają bez zbędnych pytań, natomiast analityka wymaga aktywnej zgody użytkownika i pamięta wybór w
localStorage.
5. Custom Editorial CSS: Papier milimetrowy i rzemieślniczy klimat
Zamiast generycznego Tailwinda z gotowymi komponentami, strona otrzymała autorski arkusz stylów oparty na metodyce BEM i zmiennych CSS. Stylistyka bezpośrednio nawiązuje do modelarskiego warsztatu:
/* Custom Editorial CSS – motyw warsztatu modelarskiego */
:root {
--color-bg-base: #0f172a;
--color-bg-paper: #1e293b;
--color-accent-cutter: #f59e0b; /* Żółty znacznik modelarski */
--color-blueprint: #38bdf8;
--color-text-main: #f8fafc;
--color-text-muted: #94a3b8;
--grid-pattern: radial-gradient(
circle,
rgba(56, 189, 248, 0.15) 1px,
transparent 1px
);
}
.craft-card {
background-color: var(--color-bg-paper);
border: 1px solid rgba(56, 189, 248, 0.2);
background-size: 20px 20px;
background-image: var(--grid-pattern);
padding: 1.5rem;
border-radius: 4px;
}
.craft-card:focus-visible {
outline: 2px solid var(--color-accent-cutter);
outline-offset: 4px;
}
Taki zabieg sprawia, że serwis wyróżnia się na tle tysięcy bliźniaczych szablonów, budując unikalną tożsamość marki.
Efekty wdrożenia: 100/100 w Google Lighthouse
Przejście na czyste Astro 5 przyniosło natychmiastowe rezultaty wydajnościowe:
| Metryka | Wynik przed migracją | Wynik po wdrożeniu Astro 5 |
|---|---|---|
| Google Lighthouse Performance | 42 / 100 (ciężki motyw) | 100 / 100 |
| Accessibility (WCAG) | 68 / 100 | 100 / 100 |
| Best Practices | 75 / 100 | 100 / 100 |
| SEO | 80 / 100 | 100 / 100 |
| Largest Contentful Paint (LCP) | 4.8 s | 0.4 s |
| Waga przesyłanego JavaScriptu | ~1.4 MB | 0 KB na stronach tekstowych (< 15 KB w galerii) |
Klej zasechł, kod jest czysty, a płaski arkusz zamienił się w przestrzenny, nowoczesny serwis.
Lekcje dla Twojej strony: Jak przenieść rozwiązania QWKAK do firmy i portfolio
Nie musisz prowadzić klubu modelarskiego, żeby skorzystać z tej samej filozofii. Każde studio stolarskie, gabinet, biuro projektowe czy mała firma usługowa ma dokładnie takie same potrzeby:
- Pokaż swoje realizacje w najwyższej jakości bez spowalniania strony: Wykorzystaj silnik Sharp i format WebP. Duże pliki zostają w archiwum, a klient na telefonie ogląda lekkie miniatury.
- Oddziel treść od kodu za pomocą TinaCMS i Markdown: Nie płać programiście za każdą zmianę cennika czy dodanie nowej realizacji. Edytuj stronę bezpośrednio w przeglądarce, trzymając wersje w Git.
- Nie instaluj 30 wtyczek do prostych zadań: Formularz kontaktowy, analityka i galeria mogą działać jako czyste komponenty bez obciążania serwera i ryzyka luk bezpieczeństwa.
- Zadbaj o dostępność i legalność: Czytelny kontrast, obsługa klawiaturą i przejrzysta polityka prywatności budują profesjonalny wizerunek w oczach klientów i wyszukiwarek.
Więcej o tym, jak krok po kroku podejść do planowania nowej witryny, znajdziesz w naszym materiale opisującym po co Twoja firma potrzebuje nowoczesnej strony internetowej oraz w zestawieniu jak tworzyć strony internetowe z pomocą AI.
FAQ – Najczęstsze pytania o Astro 5, galerie i TinaCMS
Czy Astro 5 nadaje się na stronę firmową lub portfolio?
Tak. Astro 5 to jeden z najlepszych wyborów dla stron zorientowanych na treść (content-driven websites), takich jak portfolio, blogi, serwisy firmowe i strony klubów. Domyślnie generuje czysty HTML, dzięki czemu strona ładuje się błyskawicznie na każdym urządzeniu.
Czym różni się TinaCMS od WordPressa?
W TinaCMS treść zapisywana jest w plikach tekstowych (Markdown) w Twoim repozytorium Git, a edycja odbywa się wizualnie na żywej stronie. Nie potrzebujesz bazy danych MySQL, wtyczek bezpieczeństwa ani skomplikowanego panelu wp-admin.
Jak zoptymalizować setki zdjęć w galerii bez spowalniania witryny?
Kluczem jest automatyczny pipeline obrazów (np. biblioteka Sharp zintegrowana z Astro). Oryginalne zdjęcia o dużej wadze są podczas budowania strony konwertowane do formatu WebP w kilku rozmiarach (srcset), a przeglądarka pobiera tylko wariant dopasowany do ekranu użytkownika.
Czy formularz zintegrowany z Chatwoot wymaga własnego serwera?
Chatwoot może działać na Twoim własnym serwerze (self-hosted) lub w chmurze Chatwoot Cloud. Sam formularz na stronie w Astro jest całkowicie statyczny i komunikuje się z API asynchronicznie, więc nie wymaga utrzymywania własnego zaplecza PHP czy Node.js.
Sprawdź gotowość swojej strony: 5 kroków do rzemieślniczej jakości
Zanim zaczniesz przebudowę serwisu lub zamówisz kolejny drogi szablon, wykonaj krótki audyt swojej obecnej witryny:
- Zbadaj wynik w Google PageSpeed Insights: Sprawdź, czy Twój LCP wynosi poniżej 1.5 sekundy na telefonie.
- Sprawdź wagę zdjęć: Czy pliki w portfolio ważą po kilka megabajtów, czy są serwowane w WebP?
- Przetestuj formularz kontaktowy: Wyślij testową wiadomość i sprawdź, czy wiesz dokładnie, gdzie trafia.
- Oceń wygodę edycji: Ile czasu zajmuje Ci dodanie nowego artykułu lub realizacji z opisem?
- Sprawdź nawigację klawiaturą: Czy jesteś w stanie przejść przez menu i formularz za pomocą klawisza TAB?
Jeśli chcesz zbudować szybką, estetyczną stronę dla swojej działalności bez agencji i bez skomplikowanego kodu, sprawdź nasze przewodniki na Zbuduj Swoją Stronę. Praktyczna wiedza, sprawdzone szablony i nowoczesny stack dopasowany do Twoich potrzeb.