@webnumseoagent/next 0.1.21 → 0.1.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/client.ts CHANGED
@@ -38,6 +38,7 @@ interface SeoFields {
38
38
  siteName?: string;
39
39
  baseUrl?: string;
40
40
  orgProfile?: { name?: string };
41
+ indexNowKey?: string; // ключ IndexNow (см. seoIndexNowKey / файл /seoagent-indexnow.txt)
41
42
  }
42
43
 
43
44
  // Подстановка переменных шаблона (RankMath-стиль). Значения подставляем ЧЕРЕЗ функции — иначе `$`
@@ -158,6 +159,15 @@ function mergeIntoMetadata(fallback: Metadata, f: SeoFields, route: string): Met
158
159
  m.description = f.description;
159
160
  }
160
161
  if (f.canonical) m.alternates = { ...(m.alternates ?? {}), canonical: f.canonical };
162
+ // Авто self-canonical: если ни сайт (fallback), ни конфиг не задали canonical — ставим ссылку на
163
+ // саму страницу. Существующий canonical НЕ трогаем (сайт может каноникалить на другой URL — дедуп).
164
+ // Путь нормализуем к ведущему «/»; при заданном baseUrl делаем абсолютным, иначе относительный —
165
+ // он тоже даёт верный self-canonical (резолвится к URL страницы / через metadataBase сайта).
166
+ if (!m.alternates?.canonical) {
167
+ const path = "/" + String(route ?? "").replace(/^\/+/, "");
168
+ const self = f.baseUrl ? f.baseUrl.replace(/\/+$/, "") + path : path;
169
+ m.alternates = { ...(m.alternates ?? {}), canonical: self };
170
+ }
161
171
  if (f.robots) {
162
172
  const r = f.robots;
163
173
  const base: Record<string, unknown> =
@@ -296,6 +306,30 @@ export async function seoJsonLd(route: string, locale?: string): Promise<unknown
296
306
  return data?.fields?.jsonLd ?? [];
297
307
  }
298
308
 
309
+ // Ключ IndexNow для файла-подтверждения. Смонтируй на сайте:
310
+ // // app/seoagent-indexnow.txt/route.ts
311
+ // import { seoIndexNowKey } from "@webnumseoagent/next";
312
+ // export const dynamic = "force-dynamic";
313
+ // export async function GET() {
314
+ // return new Response(await seoIndexNowKey(), { headers: { "content-type": "text/plain; charset=utf-8" } });
315
+ // }
316
+ // Тянем СВЕЖИМ (no-store): ключ стабилен, но так доступен сразу после генерации платформой.
317
+ export async function seoIndexNowKey(): Promise<string> {
318
+ if (!API || !SITE || !TOKEN) return "";
319
+ try {
320
+ const r = await fetch(`${API}/api/seo/${SITE}/config?route=%2F&locale=`, {
321
+ headers: { "x-seo-agent-key": TOKEN },
322
+ cache: "no-store",
323
+ signal: AbortSignal.timeout(5000),
324
+ });
325
+ if (!r.ok) return "";
326
+ const data = (await r.json()) as { fields?: SeoFields };
327
+ return data.fields?.indexNowKey ?? "";
328
+ } catch {
329
+ return "";
330
+ }
331
+ }
332
+
299
333
  // Счётчики аналитики (общесайтовые). Читаются в analytics.tsx (<SeoAnalytics/>).
300
334
  export async function seoAnalytics(route = "/", locale?: string): Promise<{ ga4?: string; gtm?: string; metrica?: string }> {
301
335
  const data = await fetchJson<{ fields: SeoFields }>(
package/dist/client.d.ts CHANGED
@@ -11,6 +11,7 @@ export declare function seoSitemap(): Promise<MetadataRoute.Sitemap>;
11
11
  export declare function seoRobots(): Promise<MetadataRoute.Robots>;
12
12
  export declare function seoRobotsTxt(): Promise<string>;
13
13
  export declare function seoJsonLd(route: string, locale?: string): Promise<unknown[]>;
14
+ export declare function seoIndexNowKey(): Promise<string>;
14
15
  export declare function seoAnalytics(route?: string, locale?: string): Promise<{
15
16
  ga4?: string;
16
17
  gtm?: string;
package/dist/client.js CHANGED
@@ -18,6 +18,7 @@ exports.seoSitemap = seoSitemap;
18
18
  exports.seoRobots = seoRobots;
19
19
  exports.seoRobotsTxt = seoRobotsTxt;
20
20
  exports.seoJsonLd = seoJsonLd;
21
+ exports.seoIndexNowKey = seoIndexNowKey;
21
22
  exports.seoAnalytics = seoAnalytics;
22
23
  const API = process.env.SEOAGENT_API_BASE ?? "";
23
24
  const SITE = process.env.SEOAGENT_SITE_ID ?? "";
@@ -157,6 +158,15 @@ function mergeIntoMetadata(fallback, f, route) {
157
158
  }
158
159
  if (f.canonical)
159
160
  m.alternates = { ...(m.alternates ?? {}), canonical: f.canonical };
161
+ // Авто self-canonical: если ни сайт (fallback), ни конфиг не задали canonical — ставим ссылку на
162
+ // саму страницу. Существующий canonical НЕ трогаем (сайт может каноникалить на другой URL — дедуп).
163
+ // Путь нормализуем к ведущему «/»; при заданном baseUrl делаем абсолютным, иначе относительный —
164
+ // он тоже даёт верный self-canonical (резолвится к URL страницы / через metadataBase сайта).
165
+ if (!m.alternates?.canonical) {
166
+ const path = "/" + String(route ?? "").replace(/^\/+/, "");
167
+ const self = f.baseUrl ? f.baseUrl.replace(/\/+$/, "") + path : path;
168
+ m.alternates = { ...(m.alternates ?? {}), canonical: self };
169
+ }
160
170
  if (f.robots) {
161
171
  const r = f.robots;
162
172
  const base = typeof m.robots === "string" ? robotsFromString(m.robots)
@@ -294,6 +304,32 @@ async function seoJsonLd(route, locale) {
294
304
  const data = await fetchJson(`/config?route=${encodeURIComponent(route)}&locale=${encodeURIComponent(locale ?? "")}`);
295
305
  return data?.fields?.jsonLd ?? [];
296
306
  }
307
+ // Ключ IndexNow для файла-подтверждения. Смонтируй на сайте:
308
+ // // app/seoagent-indexnow.txt/route.ts
309
+ // import { seoIndexNowKey } from "@webnumseoagent/next";
310
+ // export const dynamic = "force-dynamic";
311
+ // export async function GET() {
312
+ // return new Response(await seoIndexNowKey(), { headers: { "content-type": "text/plain; charset=utf-8" } });
313
+ // }
314
+ // Тянем СВЕЖИМ (no-store): ключ стабилен, но так доступен сразу после генерации платформой.
315
+ async function seoIndexNowKey() {
316
+ if (!API || !SITE || !TOKEN)
317
+ return "";
318
+ try {
319
+ const r = await fetch(`${API}/api/seo/${SITE}/config?route=%2F&locale=`, {
320
+ headers: { "x-seo-agent-key": TOKEN },
321
+ cache: "no-store",
322
+ signal: AbortSignal.timeout(5000),
323
+ });
324
+ if (!r.ok)
325
+ return "";
326
+ const data = (await r.json());
327
+ return data.fields?.indexNowKey ?? "";
328
+ }
329
+ catch {
330
+ return "";
331
+ }
332
+ }
297
333
  // Счётчики аналитики (общесайтовые). Читаются в analytics.tsx (<SeoAnalytics/>).
298
334
  async function seoAnalytics(route = "/", locale) {
299
335
  const data = await fetchJson(`/config?route=${encodeURIComponent(route)}&locale=${encodeURIComponent(locale ?? "")}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webnumseoagent/next",
3
- "version": "0.1.21",
3
+ "version": "0.1.23",
4
4
  "description": "Runtime SEO adapter + installer for Next.js App Router sites (SEO Agent platform)",
5
5
  "main": "dist/client.js",
6
6
  "types": "dist/client.d.ts",