Showcase: jak zbudowaliśmy agency-site jako demo
agency-site to fork-and-sync naszego głównego site'a - działa jako pełne demo. Pokazuję jak to się utrzymuje bez bólu i dlaczego nie jest to po prostu storybook.
agency-site to fork-and-sync naszego głównego site'a - działa jako pełne demo. Pokazuję jak to się utrzymuje bez bólu i dlaczego nie jest to po prostu storybook.
Często mam ten sam dialog na rozmowie sprzedażowej:
Klient: „A czy macie jakiś realny przykład live? Nie portfolio z screenshotami."
Ja: „Tak, agency-site. To pełna kopia naszego site'a, z innym brandem i innym contentem."
apps/showcase-agency-site to nie storybook ani isolated demo. To pełnoprawna,
zdeployowana aplikacja, która korzysta z 95% tego samego kodu co strona główna,
ale z innym tematem brandingowym, copy, contentem. Tutaj opisuję, jak to jest
zorganizowane.
Pierwszą myślą było zrobić wspólny package i import z obu apek:
packages/site-template/ ← shared
apps/site/ ← imports
apps/showcase-agency/ ← imports
To było złe. Powód: jak za 3 miesiące będę chciał zmienić hero section na site'cie głównym, ale nie na agency-site (bo agency-site jest snapshotem demo dla klientów na konkretnej dacie), to wspólny package zmusza mnie do wprowadzania prop-driven konfiguracji, której nikt nie potrzebuje poza tymi dwiema apkami.
Rozwiązanie: fork katalogowy.
# apps/showcase-agency-site/ jest forkiem apps/site/
# Synchronizacja przez dedykowany skrypt:
./scripts/sync-agency-site-backend.shSkrypt:
apps/site/backend/ → apps/showcase-agency-site/backend/apps/site/admin-frontend/ → apps/showcase-agency-site/admin-frontend/apps/site/app/ - frontend dywergencji jest dozwolony.messages/ - copy jest forkiem, nie syncem.Dzięki temu backend + admin są zawsze identyczne, a frontend może żyć własnym życiem.
ESLint + no-restricted-imports:
// apps/showcase-agency-site/eslint.config.mjs
'no-restricted-imports': ['error', {
patterns: [{
group: ['../../../site/**', '@itsolutions/site/**'],
message: 'Cross-app imports forbidden. Use packages/ for shared code.'
}]
}]Plus dependency-cruiser w CI:
pnpm depcheck
# → exit 1 jeśli ktoś zaimportował z innej apkiPróba przemycenia importu z apps/site/ do apps/showcase-agency-site/
fail-uje build w CI. To boli - i o to chodzi. Jak coś ma być wspólne,
ląduje w packages/.
Oba stacki żyją na tym samym VPS-ie:
itsolutionsdz.pl → apps/site → Caddy port 80/443
demo-agency-site.itsolutionsdz.pl → apps/showcase-agency-site → Caddy port 8082/8443
Każdy stack ma swoje docker-compose.prod.yml. Każdy obsługuje własną
bazę danych. Każdy ma osobny Let's Encrypt cert. To nie jest multi-tenant -
to dwie różne aplikacje, które dzielą tylko sprzęt.
demo-agency-site.itsolutionsdz.pl/admin z testowymi
credentialami (publikowanymi w katalogu showcase). Klient widzi,
jak będzie wyglądał jego panel.“Najlepszy showcase to kompletna aplikacja zbudowana dla realnego przypadku, której kod publikujesz jako demo - nie storybook ze stockowym contentem.
”
Klienci widzą, jak będzie wyglądać ich projekt. Ty zachowujesz jedno repo, jedną architekturę i jeden flow deployu.
I tak - agency-site dostaje update-y co kilka tygodni, kiedy główna strona dostaje rebrand albo nowy feature. To utrzymanie jest tańsze niż utrzymanie trzeciej aplikacji ze stockowym contentem.