diff --git a/docs/backlog.md b/docs/backlog.md index 601b9fe..9b434a5 100644 --- a/docs/backlog.md +++ b/docs/backlog.md @@ -34,6 +34,16 @@ правило такое (запретить toContain без i18n) - [ ] посмотреть ошибки e2e, разрешить запускать е2е без ручного подтверждения, и рассмотреть - может уже пора включить их в ci\cd гейт +- [ ] пересмотреть `{@html}` в `web/src/lib/components/JsonLd.svelte`: сейчас + там инлайновый `eslint-disable-next-line svelte/no-at-html-tags`, потому + что `` не подставляет значение (Svelte читает + содержимое script как raw text — это видно по ошибке «'json' is assigned a + value but never used»), а другой способ вставки разметки в Svelte нет. + Варианты к рассмотрению: исключение для файла в `web/eslint.config.js` + вместо suppress'а в коде; генерация JSON-LD на этапе сборки рядом с + sitemap (`web/scripts/gen-sitemap.mjs`); отказ от компонента. Критерий + закрытия: в production-коде нет `eslint-disable`, и разметка продолжает + попадать в собранный HTML ## Pipeline diff --git a/docs/plan-seo.md b/docs/plan-seo.md index 54351b3..b69f4a2 100644 --- a/docs/plan-seo.md +++ b/docs/plan-seo.md @@ -326,11 +326,18 @@ ### S3. Структурированные данные + GEO -- [ ] **[S3a]** JSON-LD: `SoftwareApplication` (+ `offers` price 0) и `FAQPage` - на страницах инструментов; `WebSite` + `Organization` на главной; - `BreadcrumbList` на всех внутренних. Учесть: Google ограничил rich results - по FAQ/HowTo для мелких сайтов — разметка нужна прежде всего для - LLM-парсинга, она безвредна. +- [x] **[S3a]** JSON-LD — на странице инструмента `SoftwareApplication` + (`offers` price 0), `FAQPage` (из уже написанного FAQ) и `BreadcrumbList` + (из тех же крошек, что видны в UI); на главной — `WebSite` + + `Organization`; на каталоге — `BreadcrumbList`. Построители в + `web/src/lib/seo.ts`, вывод — `web/src/lib/components/JsonLd.svelte`. + Проверено на сборке: три разбора в `build/tools/crop-png.html`, FAQPage с + 4 вопросами, два узла на главной. **Известная грязь:** в `JsonLd.svelte` + инлайновый `eslint-disable svelte/no-at-html-tags` — `{@html}` + единственный способ вставить значение в Svelte, а + `` не работает (Svelte читает содержимое script как + raw text). Suppress обоснован экранированием `<` в `\u003c`, но + пересмотрен не позже: задача в `docs/backlog.md`, раздел «Тесты и lint». - [ ] **[S3b]** `llms.txt` в статике: короткий манифест сайта + список инструментов с одним предложением на каждый (генерится из реестра тем же скриптом, что sitemap). diff --git a/web/src/lib/components/JsonLd.svelte b/web/src/lib/components/JsonLd.svelte new file mode 100644 index 0000000..9d40804 --- /dev/null +++ b/web/src/lib/components/JsonLd.svelte @@ -0,0 +1,21 @@ + + + + +{@html ` +
diff --git a/web/src/routes/list-tools/+page.svelte b/web/src/routes/list-tools/+page.svelte index d910cac..1c0cafc 100644 --- a/web/src/routes/list-tools/+page.svelte +++ b/web/src/routes/list-tools/+page.svelte @@ -5,6 +5,7 @@ import CatalogToolbar from "$lib/components/catalog/CatalogToolbar.svelte"; import ToolCard from "$lib/components/catalog/ToolCard.svelte"; import Seo from "$lib/components/Seo.svelte"; + import JsonLd from "$lib/components/JsonLd.svelte"; import { favFirst, isFav } from "$lib/favorites.svelte"; import { pageDescription, @@ -13,6 +14,7 @@ } from "$lib/i18n/schema-tool-strings"; import { t } from "$lib/i18n/t"; import { PAGES } from "$lib/registry"; + import { breadcrumbSchemaLd } from "$lib/seo"; import { toolIcon } from "$lib/tool-icons"; let query = $state(""); @@ -43,6 +45,11 @@ description={t("catalog.metaDescription")} path="/list-tools" /> +
diff --git a/web/src/routes/tools/[slug]/+page.svelte b/web/src/routes/tools/[slug]/+page.svelte index a7e24f7..34a7373 100644 --- a/web/src/routes/tools/[slug]/+page.svelte +++ b/web/src/routes/tools/[slug]/+page.svelte @@ -1,6 +1,7 @@