@webnumseoagent/next 0.9.1 → 0.9.2

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/bin/cli.mjs CHANGED
@@ -50,7 +50,7 @@ async function writeOurRoute(p, content) {
50
50
  ok(path.relative(root, p));
51
51
  return;
52
52
  }
53
- if (!/SeoBlogIndex|SeoBlogArticle|seoBlogPost/.test(cur)) { skip(`${path.relative(root, p)} — свой блог сайта`); return; }
53
+ if (!/SeoBlogIndex|SeoBlogArticle|seoBlogPost|seoBlogUrlPrefix/.test(cur)) { skip(`${path.relative(root, p)} — свой блог сайта`); return; }
54
54
  if (cur === content) { skip(path.relative(root, p)); return; }
55
55
  await fs.writeFile(p, content);
56
56
  ok(`${path.relative(root, p)} (обновлён)`);
@@ -755,13 +755,32 @@ async function blogPage() {
755
755
  if (localeDir) ok(`Мультиязычный сайт (${localeDir}) — блог под layout сайта: ${appDir}/${localeDir}/blog (header/footer сайта).`);
756
756
  else ok(`Блог на ${appDir}/blog (наследует корневой layout сайта).`);
757
757
 
758
- // Ставим под [locale], но на корне уже есть НАШ голый app/blog бэкапим его (иначе останется
759
- // дублирующий /blog без шапки). Трогаем только наши файлы (импортят SeoBlogIndex/seoBlogPost).
758
+ // Мультиязычный сайт: реальный блог лежит под /[locale]/blog, а голый /blog[/slug] иначе даёт 404
759
+ // (спам-бэклинки и сторонние ссылки часто без префикса локали). Ставим на корень app/blog РЕДИРЕКТ-СТАБЫ
760
+ // → 308 на каноническую /{defaultLocale}/blog[/slug] (префикс из .blogfs.json — та же каноника, что в
761
+ // sitemap). Статик "blog" бьёт динамический [locale], поэтому /blog не матчит [locale]="blog".
762
+ // writeOurRoute перезапишет НАШ прежний голый /blog (маркер SeoBlogIndex), но не тронет свой блог сайта.
760
763
  if (localeDir) {
761
- for (const f of [`blog/page.${jsxExt}`, `blog/[slug]/page.${jsxExt}`]) {
762
- const p = path.join(root, appDir, f);
763
- const cur = await fs.readFile(p, "utf8").catch(() => null);
764
- if (cur && /SeoBlogIndex|SeoBlogArticle|seoBlogPost/.test(cur)) await backupIfExists(p);
764
+ const redirImport = (file) => `import { notFound, permanentRedirect } from "next/navigation";\nimport { seoBlogUrlPrefix } from "${blogFor(file)}";`;
765
+ await writeOurRoute(
766
+ path.join(root, appDir, `blog/page.${jsxExt}`),
767
+ `${redirImport("blog/page.tsx")}\nexport const dynamic = "force-dynamic";\n\n// SEO Agent: локаль-less /blog → 308 на /{defaultLocale}/blog (иначе 404 на мультиязычном сайте).\nexport default async function BlogIndexRedirect() {\n const prefix = await seoBlogUrlPrefix();\n if (!prefix) notFound();\n permanentRedirect(\`\${prefix}/blog\`);\n}\n`,
768
+ );
769
+ // Конфликт: свой корневой blog/[иной-сегмент] → редирект-[slug] не ставим (Next запрещает два динам. имени).
770
+ let rootDynConflict = false;
771
+ const rootBlogDir = path.join(root, appDir, "blog");
772
+ if (await exists(rootBlogDir)) {
773
+ const names = await fs.readdir(rootBlogDir).catch(() => []);
774
+ rootDynConflict = names.some((n) => /^\[.*\]$/.test(n) && n !== "[slug]");
775
+ }
776
+ if (rootDynConflict) {
777
+ log(`\n \x1b[33mℹ\x1b[0m У сайта есть корневой blog/[иной-сегмент] — редирект /blog/[slug] не ставлю (конфликт динам. сегментов).`);
778
+ } else {
779
+ const rSig = ext === "ts" ? `{ params }: { params: Promise<{ slug: string }> }` : "{ params }";
780
+ await writeOurRoute(
781
+ path.join(root, appDir, `blog/[slug]/page.${jsxExt}`),
782
+ `${redirImport("blog/[slug]/page.tsx")}\nexport const dynamic = "force-dynamic";\n\n// SEO Agent: локаль-less /blog/[slug] → 308 на /{defaultLocale}/blog/[slug] (спасает бэклинки без префикса локали).\nexport default async function BlogSlugRedirect(${rSig}) {\n const { slug } = await params;\n const prefix = await seoBlogUrlPrefix();\n if (!prefix) notFound();\n permanentRedirect(\`\${prefix}/blog/\${slug}\`);\n}\n`,
783
+ );
765
784
  }
766
785
  }
767
786
 
package/blog-fs.ts CHANGED
@@ -45,7 +45,8 @@ export function fsTheme(): SeoBlogTheme | undefined {
45
45
 
46
46
  // Канонический префикс публичных URL блога. Приоритет — явный descriptor.urlPrefix (пишет платформа);
47
47
  // фолбэк для старых дескрипторов без него: <2 локалей → "" (одноязычный /blog), иначе "/<defaultLocale>".
48
- function urlPrefix(): string {
48
+ // Экспортируется для локаль-less редирект-роута (голый /blog → каноническая /{defaultLocale}/blog).
49
+ export function urlPrefix(): string {
49
50
  const d = readDescriptor();
50
51
  if (!d) return "";
51
52
  if (typeof d.urlPrefix === "string") return d.urlPrefix.replace(/\/+$/, "");
package/blog.tsx CHANGED
@@ -38,6 +38,13 @@ export async function seoBlogPost(slug: string, opts?: { locale?: string }): Pro
38
38
  return m.fsBlogPost(slug, opts?.locale ?? m.fsDefaultLocale());
39
39
  }
40
40
 
41
+ // Канонический префикс локали публичных URL блога ("" одноязычный | "/en" и т.п.). Для локаль-less
42
+ // редирект-роута мультиязычного сайта: голый /blog[/slug] → 308 на /{defaultLocale}/blog[/slug].
43
+ export async function seoBlogUrlPrefix(): Promise<string> {
44
+ const m = await blogfs();
45
+ return m.urlPrefix();
46
+ }
47
+
41
48
  // Параметры для generateStaticParams роута блога: пре-рендер ВСЕХ статей на сборке (SSG из файлов).
42
49
  // Без localeParam → [{slug}] (одноязычный /blog/[slug]). С localeParam → [{<localeParam>, slug}] для КАЖДОЙ
43
50
  // локали (мультиязычный /[locale]/blog/[slug]): иначе force-static не покроет комбинацию locale×slug → 404.
package/dist/blog-fs.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type { SeoBlogSummary, SeoBlogPostFull, SeoBlogTheme } from "./blog";
2
2
  export declare function fsTheme(): SeoBlogTheme | undefined;
3
+ export declare function urlPrefix(): string;
3
4
  export declare function fsAvailable(): boolean;
4
5
  export declare function fsDefaultLocale(): string;
5
6
  export declare function fsLocales(): string[];
package/dist/blog-fs.js CHANGED
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.fsTheme = fsTheme;
4
+ exports.urlPrefix = urlPrefix;
4
5
  exports.fsAvailable = fsAvailable;
5
6
  exports.fsDefaultLocale = fsDefaultLocale;
6
7
  exports.fsLocales = fsLocales;
@@ -52,6 +53,7 @@ function fsTheme() {
52
53
  }
53
54
  // Канонический префикс публичных URL блога. Приоритет — явный descriptor.urlPrefix (пишет платформа);
54
55
  // фолбэк для старых дескрипторов без него: <2 локалей → "" (одноязычный /blog), иначе "/<defaultLocale>".
56
+ // Экспортируется для локаль-less редирект-роута (голый /blog → каноническая /{defaultLocale}/blog).
55
57
  function urlPrefix() {
56
58
  const d = readDescriptor();
57
59
  if (!d)
package/dist/blog.d.ts CHANGED
@@ -33,6 +33,7 @@ export declare function seoBlogList(opts?: {
33
33
  export declare function seoBlogPost(slug: string, opts?: {
34
34
  locale?: string;
35
35
  }): Promise<SeoBlogPostFull | null>;
36
+ export declare function seoBlogUrlPrefix(): Promise<string>;
36
37
  export declare function seoBlogStaticParams(opts?: {
37
38
  localeParam?: string;
38
39
  }): Promise<Array<Record<string, string>>>;
package/dist/blog.js CHANGED
@@ -35,6 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.seoBlogList = seoBlogList;
37
37
  exports.seoBlogPost = seoBlogPost;
38
+ exports.seoBlogUrlPrefix = seoBlogUrlPrefix;
38
39
  exports.seoBlogStaticParams = seoBlogStaticParams;
39
40
  exports.SeoBlogIndex = SeoBlogIndex;
40
41
  exports.SeoBlogArticle = SeoBlogArticle;
@@ -52,6 +53,12 @@ async function seoBlogPost(slug, opts) {
52
53
  const m = await blogfs();
53
54
  return m.fsBlogPost(slug, opts?.locale ?? m.fsDefaultLocale());
54
55
  }
56
+ // Канонический префикс локали публичных URL блога ("" одноязычный | "/en" и т.п.). Для локаль-less
57
+ // редирект-роута мультиязычного сайта: голый /blog[/slug] → 308 на /{defaultLocale}/blog[/slug].
58
+ async function seoBlogUrlPrefix() {
59
+ const m = await blogfs();
60
+ return m.urlPrefix();
61
+ }
55
62
  // Параметры для generateStaticParams роута блога: пре-рендер ВСЕХ статей на сборке (SSG из файлов).
56
63
  // Без localeParam → [{slug}] (одноязычный /blog/[slug]). С localeParam → [{<localeParam>, slug}] для КАЖДОЙ
57
64
  // локали (мультиязычный /[locale]/blog/[slug]): иначе force-static не покроет комбинацию locale×slug → 404.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webnumseoagent/next",
3
- "version": "0.9.1",
3
+ "version": "0.9.2",
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",