From 466e546ae2772d49e382e7d9950250a221e2b21f Mon Sep 17 00:00:00 2001 From: nns Date: Thu, 8 Oct 2026 19:47:41 +0500 Subject: [PATCH] =?UTF-8?q?rebrand:=20=D0=BA=D1=8D=D1=88-=D0=B1=D0=B0?= =?UTF-8?q?=D1=81=D1=82=D0=B8=D0=BD=D0=B3=20=D0=BB=D0=BE=D0=B3=D0=BE=D1=82?= =?UTF-8?q?=D0=B8=D0=BF=D0=B0=20(=D1=81=D1=82=D0=B0=D1=80=D0=BE=D0=B5=20?= =?UTF-8?q?=D0=BB=D0=BE=D0=B3=D0=BE=20=D0=B7=D0=B0=D0=BB=D0=B8=D0=BF=D0=B0?= =?UTF-8?q?=D0=BB=D0=BE=20=D0=B2=20SW/=D0=BA=D1=8D=D1=88=D0=B5)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Причина: SW веб-админки прекэшировал /logo.svg и отдавал stale, CACHE_VERSION не менялся при ребрендинге; публичный сайт отдавал лого/favicon без Cache-Control. - sw.js: CACHE_VERSION fm-v2 → am-v3 (+ASSET_VERSION am3); activate чистит старые кэши, skipWaiting+clients.claim; прекэш бренд-ассетов версионирован; бренд в комментариях → ALL MARKET. - ?v=am3 на нехэшированных бренд-ассетах: index.html, manifest, Logo.tsx, публичный Logo.astro + BaseLayout favicon. - nginx web: index.html + бренд-ассеты + manifest → no-cache; /assets/* → immutable. - nginx public: html + лого/favicon → no-cache; /_astro/* → immutable. Проверено Playwright: старый клиент (SW fm-v2 + лого Food Market) → после выкладки + 1 перезагрузки кэш am-v3, fm-v2 удалён, лого ALL MARKET. Заметка на будущее про очистку API_CACHE при logout — в прогресс-доке. Co-Authored-By: Claude Opus 5.5 --- deploy/nginx.conf | 20 +++++++++++++++++-- docs/rebrand-all-market-progress.md | 15 ++++++++++++++ src/food-market.public/deploy/nginx.conf | 18 ++++++++++++++++- .../src/components/Logo.astro | 3 ++- .../src/layouts/BaseLayout.astro | 2 +- src/food-market.web/index.html | 10 ++++++---- .../public/manifest.webmanifest | 6 +++--- src/food-market.web/public/sw.js | 19 ++++++++++++++---- src/food-market.web/src/components/Logo.tsx | 4 +++- 9 files changed, 80 insertions(+), 17 deletions(-) diff --git a/deploy/nginx.conf b/deploy/nginx.conf index f8108f1..cd6f3ed 100644 --- a/deploy/nginx.conf +++ b/deploy/nginx.conf @@ -134,15 +134,31 @@ server { } location = /manifest.webmanifest { types { } default_type application/manifest+json; - add_header Cache-Control "public, max-age=3600"; + # no-cache: при ребрендинге manifest (name/иконки) должен обновляться сразу. + add_header Cache-Control "no-cache, must-revalidate"; try_files /manifest.webmanifest =404; } location = /offline.html { try_files /offline.html =404; } - # SPA fallback — all other routes return index.html + # Хэшированные ассеты Vite (имя содержит контент-хэш) — иммутабельно на год. + location /assets/ { + add_header Cache-Control "public, max-age=31536000, immutable"; + try_files $uri =404; + } + + # Нехэшированные бренд-ассеты (лого/иконки/favicon) — no-cache + ETag, чтобы + # смена логотипа не упиралась в кэш браузера (ETag даёт 304, если не менялось). + location ~* ^/(logo|logo-light|logo-bg|logo-fg|favicon|favicon-32|apple-touch-icon|icon-192|icon-512)\.(svg|png)$ { + add_header Cache-Control "no-cache, must-revalidate"; + try_files $uri =404; + } + + # index.html (SPA-оболочка) — no-cache: всегда свежая, подтягивает новые + # хэшированные бандлы и версии бренд-ассетов. SPA fallback для всех маршрутов. location / { + add_header Cache-Control "no-cache, must-revalidate"; try_files $uri $uri/ /index.html; } } diff --git a/docs/rebrand-all-market-progress.md b/docs/rebrand-all-market-progress.md index 16524c4..b78dfd3 100644 --- a/docs/rebrand-all-market-progress.md +++ b/docs/rebrand-all-market-progress.md @@ -88,3 +88,18 @@ API и web (0 ошибок); лого вернулось на Food Market (dist/ - 08.10 — ревью Mac-Claude: безопасность входа (префилл/подсказка/сидер/ротация пароля стенда), тост 404 (StageBanner/QuickLoginBlock → fetch), касса (лого крупнее + разделитель, бейдж внутри). - 08.10 — deploy-stage OK; menu-walk 3/3; full-chain 16/16 ЗЕЛЁНЫЙ; откат проверен в worktree. +- 08.10 — **кэш-бастинг лого** (у владельца в браузере оставалось старое лого из SW/кэша): + - `sw.js`: `CACHE_VERSION fm-v2 → am-v3` + `ASSET_VERSION=am3`; activate удаляет старые кэши, + skipWaiting+clients.claim есть; прекэш бренд-ассетов версионирован; бренд в комментариях → ALL MARKET. + - `?v=am3` на нехэшированных бренд-ассетах во всех ссылках: `index.html`, `manifest.webmanifest`, + `Logo.tsx`, публичный `Logo.astro` + `BaseLayout.astro` favicon. + - nginx web (`deploy/nginx.conf`): `index.html` + бренд-ассеты + `manifest` → `no-cache`; `/assets/*` → immutable. + - nginx public (`src/food-market.public/deploy/nginx.conf`): html + лого/favicon → `no-cache`; `/_astro/*` → immutable. + - Проверка Playwright: старый клиент (SW fm-v2 + лого Food Market) → после выкладки + 1 перезагрузки + кэш `am-v3`, `fm-v2` удалён, лого ALL MARKET. PASS. + - При будущей смене логотипа: bump `CACHE_VERSION` (sw.js) + `?v=` в ссылках (index.html, manifest, Logo.tsx/.astro). + +### Заметка на будущее (НЕ в этой задаче) +SW кэширует ответы `GET /api/*` (`API_CACHE`). На ОБЩЕМ компьютере после выхода из аккаунта данные +прошлого пользователя могут остаться в кэше. Предложение: при logout чистить `API_CACHE` +(`caches.delete('am-v3-api')` или через `postMessage` к SW) — отдельной задачей. diff --git a/src/food-market.public/deploy/nginx.conf b/src/food-market.public/deploy/nginx.conf index 838a030..2de6bf9 100644 --- a/src/food-market.public/deploy/nginx.conf +++ b/src/food-market.public/deploy/nginx.conf @@ -23,18 +23,34 @@ server { gzip_types text/css application/javascript application/json image/svg+xml text/plain; gzip_min_length 1000; + # Хэшированные ассеты Astro (/_astro/*) — иммутабельно на год. + location /_astro/ { + expires 1y; + add_header Cache-Control "public, immutable"; + try_files $uri =404; + } + # Легаси-путь на случай, если что-то лежит в /assets/ с хэшем. location ~* ^/assets/.*\.(js|css|woff2?|svg|png|jpg|webp)$ { expires 1y; add_header Cache-Control "public, immutable"; try_files $uri =404; } + # Нехэшированные бренд-ассеты (лого/иконки/favicon) — no-cache + ETag, чтобы + # смена логотипа не упиралась в эвристический кэш браузера по Last-Modified. + location ~* ^/(logo|logo-light|favicon)\.(svg|png)$ { + add_header Cache-Control "no-cache, must-revalidate"; + try_files $uri =404; + } + # Старый URL — постоянный редирект на новую страницу импорта. location = /migration-from-other-system { return 301 /import/; } location = /migration-from-other-system/ { return 301 /import/; } - # Pretty URLs: /pricing → /pricing/index.html или /pricing.html + # Pretty URLs: /pricing → /pricing/index.html или /pricing.html. + # HTML — no-cache: страницы со ссылками на лого/ассеты должны быть свежими. location / { + add_header Cache-Control "no-cache, must-revalidate"; try_files $uri $uri/ $uri.html =404; } } diff --git a/src/food-market.public/src/components/Logo.astro b/src/food-market.public/src/components/Logo.astro index 1243eed..2a3369d 100644 --- a/src/food-market.public/src/components/Logo.astro +++ b/src/food-market.public/src/components/Logo.astro @@ -1,6 +1,7 @@ --- interface Props { variant?: 'light' | 'dark'; class?: string } const { variant = 'light', class: className = '' } = Astro.props -const src = variant === 'dark' ? '/logo-light.svg' : '/logo.svg' +// ?v=am3 — версия ассета для обхода кэша при ребрендинге. +const src = variant === 'dark' ? '/logo-light.svg?v=am3' : '/logo.svg?v=am3' --- ALL MARKET diff --git a/src/food-market.public/src/layouts/BaseLayout.astro b/src/food-market.public/src/layouts/BaseLayout.astro index e1cd732..351dbf4 100644 --- a/src/food-market.public/src/layouts/BaseLayout.astro +++ b/src/food-market.public/src/layouts/BaseLayout.astro @@ -27,7 +27,7 @@ const ymId = import.meta.env.PUBLIC_YM_ID ?? 'REPLACE_ME' - + {title} · ALL MARKET diff --git a/src/food-market.web/index.html b/src/food-market.web/index.html index 08ee6fb..3432b1b 100644 --- a/src/food-market.web/index.html +++ b/src/food-market.web/index.html @@ -2,17 +2,19 @@ - - + + + ALL MARKET - + - +
diff --git a/src/food-market.web/public/manifest.webmanifest b/src/food-market.web/public/manifest.webmanifest index 88ddbcf..fce973d 100644 --- a/src/food-market.web/public/manifest.webmanifest +++ b/src/food-market.web/public/manifest.webmanifest @@ -11,9 +11,9 @@ "theme_color": "#00B207", "lang": "ru-KZ", "icons": [ - { "src": "/favicon.svg", "sizes": "any", "type": "image/svg+xml", "purpose": "any" }, - { "src": "/icon-192.png", "sizes": "192x192", "type": "image/png", "purpose": "any maskable" }, - { "src": "/icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "any maskable" } + { "src": "/favicon.svg?v=am3", "sizes": "any", "type": "image/svg+xml", "purpose": "any" }, + { "src": "/icon-192.png?v=am3", "sizes": "192x192", "type": "image/png", "purpose": "any maskable" }, + { "src": "/icon-512.png?v=am3", "sizes": "512x512", "type": "image/png", "purpose": "any maskable" } ], "shortcuts": [ { "name": "Дашборд", "url": "/dashboard" }, diff --git a/src/food-market.web/public/sw.js b/src/food-market.web/public/sw.js index e23f591..216c3d5 100644 --- a/src/food-market.web/public/sw.js +++ b/src/food-market.web/public/sw.js @@ -1,4 +1,4 @@ -/* Food Market — service worker (read-only owner PWA). +/* ALL MARKET — service worker (read-only owner PWA). * * Стратегии: * - Навигация (mode=navigate): network-first с offline-fallback на /offline.html. @@ -9,8 +9,13 @@ * (через api interceptor). * * Версия кэша инкрементируется при изменении SW — старые удаляются на activate. + * ВАЖНО при смене логотипа/иконок: bump CACHE_VERSION И ASSET_VERSION (ниже), + * и ?v= в ссылках (index.html, manifest, Logo.tsx). Иначе у тех, + * кто уже открывал админку, останется старое лого из прекэша SW. + * 2026-10: ребрендинг Food Market → ALL MARKET — fm-v2 → am-v3. */ -const CACHE_VERSION = 'fm-v2'; +const CACHE_VERSION = 'am-v3'; +const ASSET_VERSION = 'am3'; // ?v=am3 на нехэшированных бренд-ассетах const STATIC_CACHE = `${CACHE_VERSION}-static`; const API_CACHE = `${CACHE_VERSION}-api`; const OFFLINE_URL = '/offline.html'; @@ -18,8 +23,14 @@ const OFFLINE_URL = '/offline.html'; self.addEventListener('install', (event) => { event.waitUntil( caches.open(STATIC_CACHE).then((cache) => - // Прекеш offline-page чтобы fallback работал даже после reboot'a. - cache.addAll(['/offline.html', '/favicon.svg', '/logo.svg', '/manifest.webmanifest']) + // Прекеш offline-page + бренд-ассеты (версионированные — совпадают со + // ссылками на странице), чтобы fallback и лого работали даже офлайн. + cache.addAll([ + '/offline.html', + `/favicon.svg?v=${ASSET_VERSION}`, + `/logo.svg?v=${ASSET_VERSION}`, + `/manifest.webmanifest?v=${ASSET_VERSION}`, + ]) ).then(() => self.skipWaiting()) ); }); diff --git a/src/food-market.web/src/components/Logo.tsx b/src/food-market.web/src/components/Logo.tsx index e6cb4b4..d36725c 100644 --- a/src/food-market.web/src/components/Logo.tsx +++ b/src/food-market.web/src/components/Logo.tsx @@ -9,8 +9,10 @@ interface Props { className?: string; variant?: 'light' | 'dark' } * * Готовые файлы бренда из public/ (logo.svg — color, logo-light.svg — white). * Не перерисовывать и не перекрашивать — только менять размер. */ +// ?v=am3 — версия ассета для обхода кэша при ребрендинге (см. sw.js ASSET_VERSION). +const V = 'am3' export function Logo({ className, variant = 'light' }: Props) { - const src = variant === 'dark' ? '/logo-light.svg' : '/logo.svg' + const src = variant === 'dark' ? `/logo-light.svg?v=${V}` : `/logo.svg?v=${V}` return (