# storage — media na Cloudflare R2 Offload mediów (obrazy, pliki) do Cloudflare R2 zamiast lokalnego dysku. R2 jest S3-kompatybilny; plugin dostarcza `buildR2Storage`, który czyta dane z `.env` i konfiguruje adapter. > **Storage to infrastruktura, nie treść.** Dane R2 (klucze, bucket) idą do > `.env` — jak DATABASE_URI, PAYLOAD_SECRET, GRAPH_*. NIE do panelu (to sekrety > agencyjne, wiążą się przy starcie, nie zmienia ich redaktor). ## Zależność ```bash pnpm add @payloadcms/storage-s3 ``` ## Zmienne .env Patrz [R2-ENV-przyklad](../R2-ENV-przyklad.md) po pełną instrukcję skąd wziąć wartości. ```bash R2_BUCKET=nazwa-bucketa R2_ENDPOINT=https://.r2.cloudflarestorage.com R2_ACCESS_KEY_ID= R2_SECRET_ACCESS_KEY= ``` ## Wpięcie (payload.config.ts) ```ts import { buildR2Storage } from '@intecion/ipal-kit' export default buildConfig({ // ... plugins: [ ipalKit({ /* ... */ }), buildR2Storage(['media']), // slugi kolekcji upload do offloadu ], }) ``` `buildR2Storage` przyjmuje listę kolekcji upload (domyślnie `['media']`). Jeśli masz więcej kolekcji plików: `buildR2Storage(['media', 'documents'])`. ## Zachowanie (fallback) - **Wszystkie 4 zmienne** → media w R2. - **Brak zmiennych** → fallback na lokalny dysk (dev działa bez R2, zero konfiguracji). - **Część zmiennych** → ostrzeżenie w logu + fallback (częściowa konfiguracja = pewnie pomyłka). To wzorzec „degrade gracefully" — jak mailAdapter, który wraca do SMTP, gdy brak Graph. Projekt działa niezależnie od tego, czy R2 jest skonfigurowany. ## Publiczny dostęp (WAŻNE) R2 domyślnie prywatny. Upload zadziała, ale obrazy się NIE wyświetlą (403), dopóki nie skonfigurujesz publicznego odczytu: 1. Cloudflare → R2 → bucket → Settings → **Public access** → podłącz custom domain 2. Albo serwuj przez Cloudflare CDN / własną domenę Bez tego media wgrają się do R2, ale front nie pokaże obrazów. Konfiguracja domeny jest po stronie Cloudflare, nie kodu. ## Migracja istniejących mediów Jeśli projekt miał media lokalnie i przełączasz na R2 — nowe uploady idą do R2, ale STARE zostają na dysku (i znikną przy redeployu bez wolumenu). Przed przełączeniem na produkcji przenieś istniejące pliki do bucketa (np. `rclone` albo ręcznie przez R2 dashboard), inaczej stare obrazy znikną. ## Weryfikacja ```bash # po wpięciu i ustawieniu .env: pnpm dev # wgraj obraz w panelu (Media) → sprawdź w Cloudflare R2, czy plik się pojawił ``` ## Dev na lokalnym I na R2 (seedowanie podczas developmentu) Fallback (brak zmiennych → lokalny dysk) oznacza, że **dev działa w obu trybach**: - **Dev bez R2 w .env** → media na lokalnym dysku. Szybki start, zero konfiguracji. - **Dev z R2 w .env** → media w R2 już podczas developmentu. Przydatne, gdy seedujesz treść w devie i chcesz, żeby od razu lądowała w buckecie (np. wspólny bucket dev, albo test realnego flow przed produkcją). Przełączasz trybem po prostu obecnością zmiennych R2 w `.env`. Ten sam kod, `buildR2Storage` sam wykrywa. Nie musisz nic zmieniać w configu między trybami. > Jeśli seedujesz w devie do R2 — pamiętaj, że to realny bucket. Używaj osobnego > bucketa dev (nie produkcyjnego), żeby nie mieszać danych testowych z realnymi. ## Normalizacja nazw plików (automatyczna) Plik `normalizeFilenameHook` czyści nazwy wgrywanych plików — slugifikuje nazwę, zachowuje rozszerzenie: ``` "Zdjęcie jeden nad morzem.jpg" → "zdjecie-jeden-nad-morzem.jpg" "Faktura #12 (2024).PDF" → "faktura-12-2024.pdf" ``` Wpięcie w kolekcję Media (projekt): ```ts import { normalizeFilenameHook } from '@intecion/ipal-kit' export const Media: CollectionConfig = { slug: 'media', upload: { staticDir: 'media' /* ... */ }, hooks: { beforeOperation: [normalizeFilenameHook], // czyści nazwę przed zapisem }, fields: [ /* alt itd. */ ], } ``` Działa z lokalnym dyskiem i z R2/S3 (hook biegnie PRZED warstwą storage, więc czysta nazwa trafia i do bazy, i do bucketa). Dlaczego to ważne: - **URL-e mediów są czyste** — `/media/zdjecie-nad-morzem.jpg`, nie `/media/Zdjęcie%20jeden%20nad%20morzem.jpg` (spacje/diakrytyki w URL = problemy). - **Przenośność** — nazwa bez polskich znaków/spacji działa wszędzie (CDN, S3, systemy plików). - **Bez kolizji kodowania** — spacje i `#`, `()` w nazwach plików potrafią psuć ścieżki i cache. Sama funkcja `normalizeFilename(name)` też jest wyeksportowana, gdybyś potrzebował jej poza hookiem.