diff --git a/docs/backlog.md b/docs/backlog.md index e216f61..22258a1 100644 --- a/docs/backlog.md +++ b/docs/backlog.md @@ -227,7 +227,15 @@ переименований уже опубликованных страниц; клонов с canonical не делать (`docs/plan-seo.md` §5) - [ ] sitemap.xml из реестра + `Sitemap:` в robots.txt (S1b/S1c) -- [ ] Seo.svelte: canonical + OG-теги, per-tool OG-картинки (S1d/S1e) +- [ ] Seo.svelte: per-tool OG-картинки (S1e) — у каждой страницы инструмента + своя превью-карточка 1200×630 для соцсетей/мессенджеров: имя инструмента, + бренд, иконка. Сейчас все страницы ссылаются на общую + `static/og/default.png` (генерирует `pnpm --dir web gen:og` — + Playwright-скриншот HTML-карточки в `web/scripts/gen-og.mjs`). Что делать: + расширить gen-og.mjs — проход по реестру страниц, карточка с названием + инструмента в `static/og/[slug].png`, в `Seo.svelte` подставлять og-URL по + slug страницы. Критерий приёмки: у `/tools/flip-png` в og:image своя + картинка с названием инструмента, файл лежит в `static/og/` - [ ] контент страницы инструмента: seo-блок в словарях, how-to/FAQ/related (S2) - [ ] JSON-LD (SoftwareApplication/FAQPage/Breadcrumb) + llms.txt (S3) - [ ] дистрибуция: закрепить репо, alternative.to, Product Hunt, Reddit/HN (S4c) diff --git a/web/package.json b/web/package.json index 65e9f05..60a5d43 100644 --- a/web/package.json +++ b/web/package.json @@ -14,6 +14,7 @@ "test:coverage": "vitest run --coverage", "test:rules": "vitest run eslint-plugins/__tests__", "test:e2e": "playwright test", + "gen:og": "node scripts/gen-og.mjs", "format": "prettier --write . --log-level warn --cache", "format:check": "prettier --check . --log-level warn --cache", "lint": "eslint .", diff --git a/web/scripts/gen-og.mjs b/web/scripts/gen-og.mjs new file mode 100644 index 0000000..0f7c77d --- /dev/null +++ b/web/scripts/gen-og.mjs @@ -0,0 +1,56 @@ +// Генерирует static/og/default.png — дефолтная OG-карточка 1200×630. +// Запуск: pnpm --dir web gen:og. Карточка рисуется в браузере (текст, иконки), +// per-tool карточки — этим же harness позже (docs/plan-seo.md S1e). +import { mkdirSync } from "node:fs"; +import { chromium } from "playwright"; + +const W = 1200; +const H = 630; + +const IMAGE_ICON = ``; + +function cardHtml() { + return ` +
+
+
EP
+
easy-png-tools
+
PNG utilities right in your browser
+
Convert · Crop · Resize — locally, no uploads
+
+
${IMAGE_ICON}
+
+`; +} + +mkdirSync(new URL("../static/og/", import.meta.url), { recursive: true }); + +const browser = await chromium.launch(); +try { + const page = await browser.newPage({ viewport: { width: W, height: H } }); + await page.setContent(cardHtml()); + await page.screenshot({ + path: new URL("../static/og/default.png", import.meta.url).pathname.replace( + /^\/([A-Za-z]:)/, + "$1", + ), + }); + console.log(`og/default.png written (${W}x${H})`); +} finally { + await browser.close(); +} diff --git a/web/src/lib/components/Seo.svelte b/web/src/lib/components/Seo.svelte new file mode 100644 index 0000000..c32443a --- /dev/null +++ b/web/src/lib/components/Seo.svelte @@ -0,0 +1,30 @@ + + + + {title} + + + + + + + + + + + + + diff --git a/web/src/lib/seo.ts b/web/src/lib/seo.ts new file mode 100644 index 0000000..24c40f9 --- /dev/null +++ b/web/src/lib/seo.ts @@ -0,0 +1,6 @@ +/** + * Публичный URL сайта для canonical/OG (абсолютные URL, включая base-path + * GitHub Pages). После покупки домена меняется здесь — `docs/plan-seo.md` §6. + * Внимание: `$app/paths.base` для этого не подходит — он относительный. + */ +export const SITE_URL = "https://ku6epxboctuk.github.io/easy-png-tools"; diff --git a/web/src/routes/+page.svelte b/web/src/routes/+page.svelte index 2ac2ae3..8362a9c 100644 --- a/web/src/routes/+page.svelte +++ b/web/src/routes/+page.svelte @@ -4,6 +4,7 @@ import CatalogHeader from "$lib/components/catalog/CatalogHeader.svelte"; import CatalogToolbar from "$lib/components/catalog/CatalogToolbar.svelte"; import ToolCard from "$lib/components/catalog/ToolCard.svelte"; + import Seo from "$lib/components/Seo.svelte"; import { favFirst, isFav } from "$lib/favorites.svelte"; import { pageDescription, @@ -37,9 +38,7 @@ ); - - {t("home.defaultTitle")} - +
diff --git a/web/src/routes/list-tools/+page.svelte b/web/src/routes/list-tools/+page.svelte index 0efd849..9bfd823 100644 --- a/web/src/routes/list-tools/+page.svelte +++ b/web/src/routes/list-tools/+page.svelte @@ -4,6 +4,7 @@ import CatalogHeader from "$lib/components/catalog/CatalogHeader.svelte"; import CatalogToolbar from "$lib/components/catalog/CatalogToolbar.svelte"; import ToolCard from "$lib/components/catalog/ToolCard.svelte"; + import Seo from "$lib/components/Seo.svelte"; import { favFirst, isFav } from "$lib/favorites.svelte"; import { pageDescription, @@ -37,10 +38,11 @@ ); - - {t("catalog.pageTitle")} - - +
diff --git a/web/src/routes/tools/[slug]/+page.svelte b/web/src/routes/tools/[slug]/+page.svelte index 86f5a72..546bcb1 100644 --- a/web/src/routes/tools/[slug]/+page.svelte +++ b/web/src/routes/tools/[slug]/+page.svelte @@ -1,6 +1,8 @@ +{#if page} + +{/if} - easy-png-tools / {page?.title ?? t("toolPage.fallbackTitle")} + {#if !page} + easy-png-tools / {t("toolPage.fallbackTitle")} + {/if} diff --git a/web/static/og/default.png b/web/static/og/default.png new file mode 100644 index 0000000..af1cd53 Binary files /dev/null and b/web/static/og/default.png differ