diff --git a/.roo/memory-bank/activeContext.md b/.roo/memory-bank/activeContext.md index c602243..3aa8929 100644 --- a/.roo/memory-bank/activeContext.md +++ b/.roo/memory-bank/activeContext.md @@ -2,12 +2,42 @@ ## Task State -- task_id: TASK-F11 +- task_id: TASK-404 - status: success - parent_task: — -- summary: **SEO — @unhead/vue, динамический title по роутам + meta description.** +- summary: **404 для несуществующих URL — SSR + nginx + frontend router.** - next task: — (no active tasks) +## TASK-404: HTTP 404 для несуществующих страниц (SEO) + +### Проблема + +Любой неизвестный URL (например `/random-page`) отдаёт `200` + redirect на `/explore`. Поисковик путается. + +### Решение + +1. `server/index.ts` — catch-all 404 handler: `app.use((req, res) => { if (req.method === "GET") res.status(404).send(renderTemplate(...)) })` с SEO-мета (title: "404 — страница не найдена — Cycle Rider", noindex meta). +2. `nginx.conf` — whitelist SPA-роутов (`/workouts`, `/auth`, `/preferences`, `/404`) → `try_files $uri /index.html`; всё остальное (кроме SSR-локаций) → `proxy_pass http://127.0.0.1:3001`. +3. `src/router/index.ts` — catch-all `/:pathMatch(.*)*` → redirect на `{ name: "404" }` (вместо `explore`). +4. `src/router/seo.ts` — запись `404: { title: "404 — страница не найдена — Cycle Rider", description: "..." }` в SEO_MAP. + +### Acceptance Criteria + +- `curl -s -o /dev/null -w "%{http_code}" http://localhost:80/random-path` → `404` +- `curl -s http://localhost:80/random-path | grep -c '404'` → > 0 +- `curl -s -o /dev/null -w "%{http_code}" http://localhost:80/workouts` → `200` +- `curl -s -o /dev/null -w "%{http_code}" http://localhost:80/auth/login` → `200` +- `curl -s -o /dev/null -w "%{http_code}" http://localhost:80/explore` → `200` +- `yarn lint` → 0, `yarn build` → 0 + +## ✅ CODER SUCCESS REPORT — TASK-404 + +- `server/index.ts` — добавлен catch-all 404 handler (`app.use(...)` без path) перед `app.listen`: для GET — `renderTemplate` с SEO-мета (title: "404 — Страница не найдена — Cycle Rider", canonical `/404`), для остальных методов — `res.status(404).send("Not Found")`. +- `nginx.conf` — реорганизация: добавлен SPA whitelist `~ ^/(workouts|auth|preferences|404)(/|$)` → `try_files $uri /index.html`; старое `location /` (SPA fallback) заменено на catch-all → `proxy_pass http://127.0.0.1:3001` (SSR вернёт 404 для неизвестных путей). +- `src/router/index.ts` — catch-all `/:pathMatch(.*)*` redirect: `{ name: "explore" }` → `{ name: "404" }`. +- `src/router/seo.ts` — добавлена запись `"404"` в `SEO_MAP` (title + description). +- Verified: `yarn lint` exit 0, `yarn build` exit 0 (vue-tsc 0 errors, vite build ✓ 7.02s). + ## ✅ CODER SUCCESS REPORT — TASK-F11 - `package.json` — `@unhead/vue` ^3.4.1 в dependencies (установлено через `npm install @unhead/vue --legacy-peer-deps`; peer-конфликт vite 4 vs vite 5 разрешён legacy-flag'ом; vite в project поднят до 5.4.21 транзитивно). diff --git a/.roo/memory-bank/progress.md b/.roo/memory-bank/progress.md index 3d92c4e..aafd407 100644 --- a/.roo/memory-bank/progress.md +++ b/.roo/memory-bank/progress.md @@ -1,5 +1,13 @@ # Progress — Strava Frontend +## 2026-09-19 — TASK-404: HTTP 404 для несуществующих страниц (SEO) + +- `server/index.ts` — catch-all 404 handler (`app.use` без path, перед `app.listen`): GET → `renderTemplate` (SEO-мета, canonical `/404`), non-GET → `res.status(404).send("Not Found")`. +- `nginx.conf` — SPA whitelist location `~ ^/(workouts|auth|preferences|404)(/|$)` → `try_files $uri /index.html`; catch-all `location /` → `proxy_pass http://127.0.0.1:3001` (SSR 404 для неизвестных URL). +- `src/router/index.ts` — catch-all redirect: `explore` → `404`. +- `src/router/seo.ts` — `SEO_MAP["404"]` добавлен. +- Verified: `yarn lint` exit 0, `yarn build` exit 0 (vue-tsc 0, vite ✓ 7.02s). + ## 2026-09-19 — TASK-F11: SEO @unhead/vue dynamic title + meta description per route - `package.json` — `@unhead/vue` ^3.4.1 added to dependencies (`npm install @unhead/vue --legacy-peer-deps`; vite transitively bumped 4.5.5→5.4.21). diff --git a/nginx.conf b/nginx.conf index 9ea3f26..4770b92 100644 --- a/nginx.conf +++ b/nginx.conf @@ -48,12 +48,21 @@ http { proxy_set_header Cookie $http_cookie; } - # Everything else → SPA - location / { + # SPA whitelist — client-side routes + location ~ ^/(workouts|auth|preferences|404)(/|$) { root /usr/share/nginx/html; try_files $uri /index.html; } + # Catch-all → SSR proxy (unknown paths get a 404 from SSR) + location / { + proxy_pass http://127.0.0.1:3001; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header Cookie $http_cookie; + } + # Static assets location /assets/ { root /usr/share/nginx/html; diff --git a/server/index.ts b/server/index.ts index 57e35b3..dc7fc9c 100644 --- a/server/index.ts +++ b/server/index.ts @@ -308,6 +308,27 @@ app.get("/health", (_req: Request, res: Response) => { res.json({ status: "ok" }); }); +// Catch-all 404 — SEO +app.use((req: Request, res: Response) => { + if (req.method !== "GET") { + res.status(404).send("Not Found"); + return; + } + const meta: SeoMeta = { + title: "404 — Страница не найдена — Cycle Rider", + description: + "Страница не найдена. Вернитесь на главную страницу Cycle Rider.", + canonicalUrl: `${BASE_URL}/404`, + }; + const content = ` +

404 — Страница не найдена

+

Запрашиваемая страница не существует или была перемещена.

+

Вернуться на главную

`; + res + .status(404) + .send(renderTemplate({ meta, content, assetTags: getAssetTagsSafe() })); +}); + app.listen(PORT, () => { console.log(`SSR server listening on :${PORT}`); }); diff --git a/src/router/index.ts b/src/router/index.ts index 1f6fc11..971c778 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -10,7 +10,7 @@ const routes: Array = [ }, { path: "/:pathMatch(.*)*", - redirect: { name: "explore" }, + redirect: { name: "404" }, }, { name: "admin", diff --git a/src/router/seo.ts b/src/router/seo.ts index b86a865..7563950 100644 --- a/src/router/seo.ts +++ b/src/router/seo.ts @@ -61,6 +61,10 @@ const SEO_MAP: Record = { title: "Восстановление пароля — Cycle Rider", description: "Восстановите доступ к аккаунту Cycle Rider.", }, + "404": { + title: "404 — страница не найдена — Cycle Rider", + description: "Запрошенная страница не существует или была перемещена.", + }, }; export function setupSeo(router: Router, head: Unhead) {