rebrand: кэш-бастинг логотипа (старое лого залипало в SW/кэше)
Some checks are pending
Auto-tag / Create date-tag (push) Waiting to run
CI / Backend (.NET 8) (push) Waiting to run
CI / Web (React + Vite) (push) Waiting to run
CI / POS (WPF, Windows) (push) Waiting to run
Docker Public / Build + push Public (test + latest) (push) Waiting to run
Docker Public / Deploy :test → test.food-market.kz (push) Blocked by required conditions
Docker Web / Build + push Web (push) Waiting to run
Docker Web / Deploy Web on stage (push) Blocked by required conditions
Some checks are pending
Auto-tag / Create date-tag (push) Waiting to run
CI / Backend (.NET 8) (push) Waiting to run
CI / Web (React + Vite) (push) Waiting to run
CI / POS (WPF, Windows) (push) Waiting to run
Docker Public / Build + push Public (test + latest) (push) Waiting to run
Docker Public / Deploy :test → test.food-market.kz (push) Blocked by required conditions
Docker Web / Build + push Web (push) Waiting to run
Docker Web / Deploy Web on stage (push) Blocked by required conditions
Причина: 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 <noreply@anthropic.com>
This commit is contained in:
parent
e5bbd4a047
commit
466e546ae2
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) — отдельной задачей.
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
---
|
||||
<img src={src} alt="ALL MARKET" class={`h-8 w-auto select-none ${className}`} draggable="false" />
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ const ymId = import.meta.env.PUBLIC_YM_ID ?? 'REPLACE_ME'
|
|||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg?v=am3" />
|
||||
<title>{title} · ALL MARKET</title>
|
||||
<meta name="description" content={description} />
|
||||
<link rel="canonical" href={canonical} />
|
||||
|
|
|
|||
|
|
@ -2,17 +2,19 @@
|
|||
<html lang="ru-KZ">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32.png" />
|
||||
<!-- ?v=am3 — версия бренд-ассетов для обхода кэша (см. sw.js ASSET_VERSION).
|
||||
При смене логотипа bump ?v здесь, в manifest, в Logo.tsx и CACHE_VERSION в sw.js. -->
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg?v=am3" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32.png?v=am3" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
|
||||
<title>ALL MARKET</title>
|
||||
<meta name="theme-color" content="#00B207" />
|
||||
<!-- PWA: manifest + apple-touch-meta. SW регистрируется в main.tsx. -->
|
||||
<link rel="manifest" href="/manifest.webmanifest" />
|
||||
<link rel="manifest" href="/manifest.webmanifest?v=am3" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
|
||||
<meta name="apple-mobile-web-app-title" content="ALL MARKET" />
|
||||
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
|
||||
<link rel="apple-touch-icon" href="/apple-touch-icon.png?v=am3" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
|
|
|||
|
|
@ -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" },
|
||||
|
|
|
|||
|
|
@ -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=<ASSET_VERSION> в ссылках (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())
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<img
|
||||
src={src}
|
||||
|
|
|
|||
Loading…
Reference in a new issue