404
Gitea Actions Demo / build_and_push (push) Successful in 1m11s
Details
Gitea Actions Demo / build_and_push (push) Successful in 1m11s
Details
This commit is contained in:
parent
9aa73b936b
commit
6e4f53caac
|
|
@ -2,12 +2,42 @@
|
||||||
|
|
||||||
## Task State
|
## Task State
|
||||||
|
|
||||||
- task_id: TASK-F11
|
- task_id: TASK-404
|
||||||
- status: success
|
- status: success
|
||||||
- parent_task: —
|
- parent_task: —
|
||||||
- summary: **SEO — @unhead/vue, динамический title по роутам + meta description.**
|
- summary: **404 для несуществующих URL — SSR + nginx + frontend router.**
|
||||||
- next task: — (no active tasks)
|
- 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
|
## ✅ 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 транзитивно).
|
- `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 транзитивно).
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,13 @@
|
||||||
# Progress — Strava Frontend
|
# 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
|
## 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).
|
- `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).
|
||||||
|
|
|
||||||
13
nginx.conf
13
nginx.conf
|
|
@ -48,12 +48,21 @@ http {
|
||||||
proxy_set_header Cookie $http_cookie;
|
proxy_set_header Cookie $http_cookie;
|
||||||
}
|
}
|
||||||
|
|
||||||
# Everything else → SPA
|
# SPA whitelist — client-side routes
|
||||||
location / {
|
location ~ ^/(workouts|auth|preferences|404)(/|$) {
|
||||||
root /usr/share/nginx/html;
|
root /usr/share/nginx/html;
|
||||||
try_files $uri /index.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
|
# Static assets
|
||||||
location /assets/ {
|
location /assets/ {
|
||||||
root /usr/share/nginx/html;
|
root /usr/share/nginx/html;
|
||||||
|
|
|
||||||
|
|
@ -308,6 +308,27 @@ app.get("/health", (_req: Request, res: Response) => {
|
||||||
res.json({ status: "ok" });
|
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 = `
|
||||||
|
<h1>404 — Страница не найдена</h1>
|
||||||
|
<p>Запрашиваемая страница не существует или была перемещена.</p>
|
||||||
|
<p><a href="/">Вернуться на главную</a></p>`;
|
||||||
|
res
|
||||||
|
.status(404)
|
||||||
|
.send(renderTemplate({ meta, content, assetTags: getAssetTagsSafe() }));
|
||||||
|
});
|
||||||
|
|
||||||
app.listen(PORT, () => {
|
app.listen(PORT, () => {
|
||||||
console.log(`SSR server listening on :${PORT}`);
|
console.log(`SSR server listening on :${PORT}`);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ const routes: Array<RouteRecordRaw> = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/:pathMatch(.*)*",
|
path: "/:pathMatch(.*)*",
|
||||||
redirect: { name: "explore" },
|
redirect: { name: "404" },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "admin",
|
name: "admin",
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,10 @@ const SEO_MAP: Record<string, RouteSeo> = {
|
||||||
title: "Восстановление пароля — Cycle Rider",
|
title: "Восстановление пароля — Cycle Rider",
|
||||||
description: "Восстановите доступ к аккаунту Cycle Rider.",
|
description: "Восстановите доступ к аккаунту Cycle Rider.",
|
||||||
},
|
},
|
||||||
|
"404": {
|
||||||
|
title: "404 — страница не найдена — Cycle Rider",
|
||||||
|
description: "Запрошенная страница не существует или была перемещена.",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export function setupSeo(router: Router, head: Unhead) {
|
export function setupSeo(router: Router, head: Unhead) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue