# Active Context — Strava Frontend ## Task State - task_id: TASK-404 - status: success - parent_task: — - 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 транзитивно). - Создан `src/router/seo.ts` — `setupSeo(router, head: Unhead)`: SEO_MAP по name роута + DEFAULT_SEO, `head.push({title, meta:[description]})` + `router.afterEach(() => update())`. - `src/main.ts` — `import { createHead } from "unhead/client"` + `import { headSymbol } from "@unhead/vue"`; `const head = createHead(); app.provide(headSymbol, head); setupSeo(router, head);` (после `app.use(router)`). - `index.html` — `` заменён на «Cycle Rider — платформа для анализа велотренировок: ...»; добавлен `<meta name="description">` в `<head>` как fallback; блок Yandex.Metrika перенесён из `<head>` в начало `<body>` (Vite 5 parse5 запрещает `<div>/<img>` внутри `<noscript>` в `<head>` — `disallowed-content-in-noscript-in-head`). - Verified: `npm run build` exit 0 (vue-tsc 0 ошибок, vite build ✓ 6.65s; warning chunk >500kB — предсуществующий); `npm run lint` exit 0. ## ✅ CODER SUCCESS REPORT — TASK-PUBLIC-ROUTE-FIX - Изменён файл (единственный): `src/router/index.ts`: - Удалён вложенный `workout_public_item` из `children` маршрута `workouts` (бывшие строки 50–54: `path: "public/workouts/:id"`). - Добавлен top-level роут `{ name: "workout_public_item", path: "/public/workouts/:id", component: () => import("../pages/workouts/WorkoutPublicItem.vue") }` — зарегистрирован **до** catch-all `/:pathMatch(.*)*`, вне `AppLayout` (без auth-guard'а). - Поведение: URL `/public/workouts/:id` теперь доступен без авторизации (не обёрнут в `AppLayout`), совпадает с SSR-эндпоинтом, nginx-прокси `location ~ ^/public/workouts/` и sitemap. Внутренняя навигация по `name: "workout_public_item"` (см. `Feed.vue`) не тронута — имя не менялось. - `WorkoutPublicItem.vue` / `WorkoutItem.vue`: проверено — `inject("axiosAuth")` работает глобально (app.provide в `main.ts`), от AppLayout не зависит; auth-check'ов от вложенности не найдено. - `yarn lint`: exit 0 (prettier: все файлы unchanged, eslint 0 ошибок). - `yarn build`: exit 0 (vue-tsc --noEmit 0 ошибок, vite build ✓ 6.95s; warning о chunk >500kB — предсуществующий). ## ✅ CODER SUCCESS REPORT — TASK-SSR-3 - Изменённые файлы: - `server/index.ts` — добавлены `GET /sitemap.xml` и `GET /robots.txt`. - `nginx.conf` — полная замена: SSR proxy, cookie-based landing, assets cache, SPA fallback. - `Dockerfile` — multi-stage: node:20-alpine build → runtime (nginx + tsx server). - `run.sh` — `npx tsx server/index.ts & nginx -g 'daemon off;'`. - `package.json` — `tsx` → dependencies. - `src/stores/user-store.ts` — `typeof window` guard для SSR. - `src/main.ts` — `typeof window` guard перед `localStorage`. - Verified: lint 0, build 0, curl /robots.txt 200, /sitemap.xml 200, /health ok. ## ✅ CODER SUCCESS REPORT — TASK-SSR-2 - `server/index.ts` — 4 SSR route handlers + `app.disable("x-powered-by")`: `/`, `/explore`, `/public/workouts/:id`, `/routes`. - Helpers: `getAssetTagsSafe()`, `escapeHtml`, `formatDate`, `formatDuration`, `formatDistance`, `formatSpeed`, `getToken`. - Canonical base: `SSR_BASE_URL` || `https://cycle-rider.ru`. - Verified: lint 0, build 0, curl all routes OK. ## ✅ CODER SUCCESS REPORT — TASK-SSR-1 - Created `server/index.ts`, `server/template.ts`, `server/api.ts`. - `package.json`: +express, +@types/express, +tsx, +3 server scripts. - Verified: lint 0, build 0, curl /health OK. ## ✅ CODER SUCCESS REPORT — TASK-SSR-0 - `src/router/index.ts`: `dashboard` → `explore` (name + path), legacy redirect `/dashboard` → `explore`. - `NavigationRoutes.ts`, `AppLayoutNavigation.vue`, `AppLayout.vue`, `Login.vue`, `Signup.vue`, `CheckTheEmail.vue` — все ссылки `dashboard` → `explore`. - Verified: lint 0, build 0.