Przejdź do treści głównej
Powrót do bloga
Jak zbudowaliśmy nowoczesny, rzemieślniczy serwis dla klubu modelarskiego w Astro 5 – Case Study QWKAK

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.

10 min czytania
AstroQWKAKTinaCMSgalerie zdjęćChatwootstrona internetowaLighthouse

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:

  1. 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.
  2. 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.
  3. 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.
  4. Prywatność i niezależność: Brak inwazyjnych zewnętrznych trackerów, własna analityka oraz formularz zintegrowany z dedykowaną instancją Chatwoot.
  5. 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:

WarstwaTechnologiaDlaczego właśnie to?
Core FrameworkAstro 5.xArchitektura wysp (Islands Architecture), genialny silnik statyczny (SSG), zerowy JavaScript na stronach artykułów i relacji.
Optymalizacja MediówSharp + astro:assetsNajszybszy silnik przetwarzania obrazów w Node.js, automatyczna konwersja do WebP i generowanie responsywnych wariantów w locie podczas budowania.
System Galeriiastro-galleryZestaw komponentów (Justified, Masonry, Karuzela 3D, Slideshow) z wbudowanym lightboxem i obsługą metadanych EXIF.
Zarządzanie TreściąAstro Content Layer + TinaCMSArtykuły i relacje zapisywane jako pliki Markdown/MDX ze ścisłą walidacją typów przez Zod i edycją wizualną na żywej stronie.
Komunikacja & CRMChatwoot API (Self-hosted)Otwarty system obsługi wiadomości połączony przez asynchroniczne Client API bez ciężkich zewnętrznych ramek iframe.
Styling & DesignCustom Editorial CSS (BEM/Craft)Unikalny, autorski motyw graficzny nawiązujący do papieru milimetrowego, maty modelarskiej, granatu i żółtych znaczników technicznych.
AnalitykaSelf-hosted AnalyticsLekki 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):

  1. Parser rozpoznaje znacznik w locie: Wyciąga nazwę katalogu (syrenka_2024) oraz parametry układu (masonry, kolumny=4).
  2. Wyszukuje odpowiedni folder zdjęć: Odczytuje listę plików graficznych w wysokiej rozdzielczości.
  3. Przetwarza zdjęcia przez bibliotekę Sharp: Generuje warianty WebP o różnych szerokościach (np. 400px, 800px, 1600px) i buduje atrybut srcset.
  4. 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:

  1. Bezpośrednie zapytanie do API: Po kliknięciu „Wyślij” przeglądarka wysyła asynchroniczne żądanie fetch bezpośrednio do publicznego endpointu skrzynki Chatwoot (/public/api/v1/inboxes/{inbox_identifier}/contacts).
  2. Tworzenie profilu kontaktu: Chatwoot automatycznie tworzy lub aktualizuje profil rozmówcy (imię, e-mail).
  3. 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.
  4. 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:

MetrykaWynik przed migracjąWynik po wdrożeniu Astro 5
Google Lighthouse Performance42 / 100 (ciężki motyw)100 / 100
Accessibility (WCAG)68 / 100100 / 100
Best Practices75 / 100100 / 100
SEO80 / 100100 / 100
Largest Contentful Paint (LCP)4.8 s0.4 s
Waga przesyłanego JavaScriptu~1.4 MB0 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:

  1. 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.
  2. 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.
  3. 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.
  4. 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:

  1. Zbadaj wynik w Google PageSpeed Insights: Sprawdź, czy Twój LCP wynosi poniżej 1.5 sekundy na telefonie.
  2. Sprawdź wagę zdjęć: Czy pliki w portfolio ważą po kilka megabajtów, czy są serwowane w WebP?
  3. Przetestuj formularz kontaktowy: Wyślij testową wiadomość i sprawdź, czy wiesz dokładnie, gdzie trafia.
  4. Oceń wygodę edycji: Ile czasu zajmuje Ci dodanie nowego artykułu lub realizacji z opisem?
  5. 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.