@webnumseoagent/next 0.7.0 → 0.7.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 +17 -9
- package/blog-fs.ts +7 -0
- package/blog.tsx +12 -4
- package/dist/blog-fs.d.ts +1 -0
- package/dist/blog-fs.js +7 -0
- package/dist/blog.d.ts +2 -4
- package/dist/blog.js +13 -3
- package/package.json +1 -1
package/bin/cli.mjs
CHANGED
|
@@ -406,7 +406,7 @@ async function init() {
|
|
|
406
406
|
// generateMetadata: у блог-роута свой generateMetadata (seoBlogPost), его НЕ оборачиваем seoMeta.
|
|
407
407
|
if (!args.includes("--no-blog")) {
|
|
408
408
|
const jsxExt = ext === "ts" ? "tsx" : "jsx";
|
|
409
|
-
const mdParam = ext === "ts" ? "{ params }: { params: { slug: string } }" : "{ params }";
|
|
409
|
+
const mdParam = ext === "ts" ? "{ params }: { params: Promise<{ slug: string }> }" : "{ params }";
|
|
410
410
|
const madeIndex = await ensureFile(
|
|
411
411
|
path.join(root, appDir, `blog/page.${jsxExt}`),
|
|
412
412
|
`import { SeoBlogIndex } from "${blogFor("blog/page.tsx")}";\nexport const dynamic = "force-static";\nexport default function BlogPage() {\n return <SeoBlogIndex title="Блог" />;\n}\n`,
|
|
@@ -426,7 +426,7 @@ async function init() {
|
|
|
426
426
|
} else {
|
|
427
427
|
madeArticle = await ensureFile(
|
|
428
428
|
path.join(root, appDir, `blog/[slug]/page.${jsxExt}`),
|
|
429
|
-
`import { notFound } from "next/navigation";\nimport { seoBlogPost, seoBlogStaticParams, SeoBlogArticle } from "${blogFor("blog/[slug]/page.tsx")}";\nexport const dynamic = "force-static";\nexport const dynamicParams = false;\n\n// Полностью статический рендер: все статьи пре-рендерятся на сборке из файлов content/blog. Контент\n// меняется git-коммитом → редеплой. dynamicParams=false: неизвестный слаг → 404 (без рантайм-чтения).\nexport async function generateStaticParams() {\n return seoBlogStaticParams();\n}\n\nexport async function generateMetadata(${mdParam}) {\n const post = await seoBlogPost(
|
|
429
|
+
`import { notFound } from "next/navigation";\nimport { seoBlogPost, seoBlogStaticParams, SeoBlogArticle } from "${blogFor("blog/[slug]/page.tsx")}";\nexport const dynamic = "force-static";\nexport const dynamicParams = false;\n\n// Полностью статический рендер: все статьи пре-рендерятся на сборке из файлов content/blog. Контент\n// меняется git-коммитом → редеплой. dynamicParams=false: неизвестный слаг → 404 (без рантайм-чтения).\nexport async function generateStaticParams() {\n return seoBlogStaticParams();\n}\n\nexport async function generateMetadata(${mdParam}) {\n const { slug } = await params;\n const post = await seoBlogPost(slug);\n if (!post) return {};\n return { title: post.title, description: post.excerpt, openGraph: { images: post.cover ? [post.cover] : [] } };\n}\n\nexport default async function BlogPostPage(${mdParam}) {\n const { slug } = await params;\n const post = await seoBlogPost(slug);\n if (!post) notFound();\n return <SeoBlogArticle post={post} />;\n}\n`,
|
|
430
430
|
);
|
|
431
431
|
}
|
|
432
432
|
if (madeIndex || madeArticle) {
|
|
@@ -694,27 +694,35 @@ async function blogPage() {
|
|
|
694
694
|
// index-роут (список): в [locale] прокидываем locale + basePath. СТАТИЧЕСКИЙ (Фаза 1 git-native):
|
|
695
695
|
// без searchParams (иначе роут стал бы динамическим и в рантайме Vercel не увидел бы файлы content/blog).
|
|
696
696
|
const idxSig = param
|
|
697
|
-
? (ext === "ts" ? `{ params }: { params: { ${param}: string } }` : "{ params }")
|
|
697
|
+
? (ext === "ts" ? `{ params }: { params: Promise<{ ${param}: string }> }` : "{ params }")
|
|
698
698
|
: "";
|
|
699
|
+
// Next 15: params — Promise, читаем через await (в Next 14 await над объектом тоже безвреден).
|
|
700
|
+
// param==="locale" — деструктурим без переименования (иначе eslint no-useless-rename).
|
|
701
|
+
const idxAwait = param ? ` const { ${param === "locale" ? "locale" : `${param}: locale`} } = await params;\n` : "";
|
|
699
702
|
const idxBody = param
|
|
700
|
-
? `<SeoBlogIndex locale={
|
|
703
|
+
? `<SeoBlogIndex locale={locale} basePath={\`/\${locale}/blog\`} />`
|
|
701
704
|
: `<SeoBlogIndex />`;
|
|
702
705
|
await writeOurRoute(
|
|
703
706
|
path.join(root, appDir, under, `blog/page.${jsxExt}`),
|
|
704
|
-
`import { SeoBlogIndex } from "${blogFor(`${under}blog/page.tsx`)}";\nexport const dynamic = "force-static";\nexport default function BlogPage(${idxSig}) {\n return ${idxBody};\n}\n`,
|
|
707
|
+
`import { SeoBlogIndex } from "${blogFor(`${under}blog/page.tsx`)}";\nexport const dynamic = "force-static";\nexport default async function BlogPage(${idxSig}) {\n${idxAwait} return ${idxBody};\n}\n`,
|
|
705
708
|
);
|
|
706
709
|
|
|
707
710
|
if (blogDynConflict) {
|
|
708
711
|
log(`\n \x1b[1mУ сайта уже есть blog/[иной-сегмент]\x1b[0m — роут статьи /blog/[slug] не создаю. Подключи вручную: \x1b[36mimport { seoBlogPost, SeoBlogArticle } from "${blogFor(`${under}blog/[slug]/page.tsx`)}";\x1b[0m`);
|
|
709
712
|
} else {
|
|
710
713
|
const artSig = ext === "ts"
|
|
711
|
-
? (param ? `{ params }: { params: { ${param}: string; slug: string } }` : `{ params }: { params: { slug: string } }`)
|
|
714
|
+
? (param ? `{ params }: { params: Promise<{ ${param}: string; slug: string }> }` : `{ params }: { params: Promise<{ slug: string }> }`)
|
|
712
715
|
: "{ params }";
|
|
713
|
-
|
|
714
|
-
const
|
|
716
|
+
// Next 15: params — Promise, читаем через await. Локаль переименовываем в locale (кроме param==="locale").
|
|
717
|
+
const artAwait = param ? ` const { slug, ${param === "locale" ? "locale" : `${param}: locale`} } = await params;\n` : ` const { slug } = await params;\n`;
|
|
718
|
+
const localeArg = param ? `, { locale }` : "";
|
|
719
|
+
const localeProp = param ? ` locale={locale}` : "";
|
|
720
|
+
// Мультиязычный роут /[locale]/blog/[slug]: пре-рендерим КАЖДУЮ статью под КАЖДУЮ локаль (передаём имя
|
|
721
|
+
// сегмента локали) — иначе force-static не покроет комбинацию locale×slug и статьи отдадут 404.
|
|
722
|
+
const gsp = param ? `seoBlogStaticParams({ localeParam: "${param}" })` : `seoBlogStaticParams()`;
|
|
715
723
|
await writeOurRoute(
|
|
716
724
|
path.join(root, appDir, under, `blog/[slug]/page.${jsxExt}`),
|
|
717
|
-
`import { notFound } from "next/navigation";\nimport { seoBlogPost, seoBlogStaticParams, SeoBlogArticle } from "${blogFor(`${under}blog/[slug]/page.tsx`)}";\nexport const dynamic = "force-static";\nexport const dynamicParams = false;\n\n// Полностью статический рендер: все статьи пре-рендерятся на сборке из файлов content/blog. Контент\n// меняется git-коммитом → редеплой. dynamicParams=false: неизвестный слаг → 404 (без рантайм-чтения).\nexport async function generateStaticParams() {\n return
|
|
725
|
+
`import { notFound } from "next/navigation";\nimport { seoBlogPost, seoBlogStaticParams, SeoBlogArticle } from "${blogFor(`${under}blog/[slug]/page.tsx`)}";\nexport const dynamic = "force-static";\nexport const dynamicParams = false;\n\n// Полностью статический рендер: все статьи пре-рендерятся на сборке из файлов content/blog. Контент\n// меняется git-коммитом → редеплой. dynamicParams=false: неизвестный слаг → 404 (без рантайм-чтения).\nexport async function generateStaticParams() {\n return ${gsp};\n}\n\nexport async function generateMetadata(${artSig}) {\n${artAwait} const post = await seoBlogPost(slug${localeArg});\n if (!post) return {};\n return { title: post.title, description: post.excerpt, openGraph: { images: post.cover ? [post.cover] : [] } };\n}\n\nexport default async function BlogPostPage(${artSig}) {\n${artAwait} const post = await seoBlogPost(slug${localeArg});\n if (!post) notFound();\n return <SeoBlogArticle post={post}${localeProp} />;\n}\n`,
|
|
718
726
|
);
|
|
719
727
|
}
|
|
720
728
|
|
package/blog-fs.ts
CHANGED
|
@@ -65,6 +65,13 @@ export function fsDefaultLocale(): string {
|
|
|
65
65
|
return typeof d === "string" ? d : ""; // битый дескриптор мог дать не-строку → не роняем split ниже
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
// Локали доставки блога из дескриптора (для generateStaticParams мультиязычного роута /[locale]/blog/[slug]:
|
|
69
|
+
// каждую статью надо пре-рендерить под каждую локаль, иначе force-static отдаст 404). Пусто — одноязычный.
|
|
70
|
+
export function fsLocales(): string[] {
|
|
71
|
+
const l = readDescriptor()?.locales;
|
|
72
|
+
return Array.isArray(l) ? l.map((x) => String(x).trim()).filter(Boolean) : [];
|
|
73
|
+
}
|
|
74
|
+
|
|
68
75
|
// Собрать meta.json всех статей (glob по папкам). Пропускает битые/незнакомые версии/черновики.
|
|
69
76
|
function listMetas(): { dir: string; meta: Meta }[] {
|
|
70
77
|
const r = root();
|
package/blog.tsx
CHANGED
|
@@ -33,10 +33,18 @@ export async function seoBlogPost(slug: string, opts?: { locale?: string }): Pro
|
|
|
33
33
|
return m.fsBlogPost(slug, opts?.locale ?? m.fsDefaultLocale());
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
// Параметры для generateStaticParams роута
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
// Параметры для generateStaticParams роута блога: пре-рендер ВСЕХ статей на сборке (SSG из файлов).
|
|
37
|
+
// Без localeParam → [{slug}] (одноязычный /blog/[slug]). С localeParam → [{<localeParam>, slug}] для КАЖДОЙ
|
|
38
|
+
// локали (мультиязычный /[locale]/blog/[slug]): иначе force-static не покроет комбинацию locale×slug → 404.
|
|
39
|
+
export async function seoBlogStaticParams(opts?: { localeParam?: string }): Promise<Array<Record<string, string>>> {
|
|
40
|
+
const list = await seoBlogList({ limit: 100000 });
|
|
41
|
+
const slugs = list.map((p) => ({ slug: p.slug }));
|
|
42
|
+
const lp = opts?.localeParam;
|
|
43
|
+
if (!lp) return slugs;
|
|
44
|
+
const m = await blogfs();
|
|
45
|
+
const locales = m.fsLocales();
|
|
46
|
+
if (!locales.length) return slugs;
|
|
47
|
+
return locales.flatMap((loc) => slugs.map((s) => ({ [lp]: loc, slug: s.slug })));
|
|
40
48
|
}
|
|
41
49
|
|
|
42
50
|
// ── Компоненты (серверные) ──────────────────────────────────────────────────────────────────
|
package/dist/blog-fs.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { SeoBlogSummary, SeoBlogPostFull, SeoBlogTheme } from "./blog";
|
|
|
2
2
|
export declare function fsTheme(): SeoBlogTheme | undefined;
|
|
3
3
|
export declare function fsAvailable(): boolean;
|
|
4
4
|
export declare function fsDefaultLocale(): string;
|
|
5
|
+
export declare function fsLocales(): string[];
|
|
5
6
|
export declare function fsBlogList(locale: string, limit: number): SeoBlogSummary[];
|
|
6
7
|
export declare function fsBlogPost(slug: string, locale: string): SeoBlogPostFull | null;
|
|
7
8
|
export declare function fsDirExists(): boolean;
|
package/dist/blog-fs.js
CHANGED
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.fsTheme = fsTheme;
|
|
4
4
|
exports.fsAvailable = fsAvailable;
|
|
5
5
|
exports.fsDefaultLocale = fsDefaultLocale;
|
|
6
|
+
exports.fsLocales = fsLocales;
|
|
6
7
|
exports.fsBlogList = fsBlogList;
|
|
7
8
|
exports.fsBlogPost = fsBlogPost;
|
|
8
9
|
exports.fsDirExists = fsDirExists;
|
|
@@ -71,6 +72,12 @@ function fsDefaultLocale() {
|
|
|
71
72
|
const d = readDescriptor()?.defaultLocale;
|
|
72
73
|
return typeof d === "string" ? d : ""; // битый дескриптор мог дать не-строку → не роняем split ниже
|
|
73
74
|
}
|
|
75
|
+
// Локали доставки блога из дескриптора (для generateStaticParams мультиязычного роута /[locale]/blog/[slug]:
|
|
76
|
+
// каждую статью надо пре-рендерить под каждую локаль, иначе force-static отдаст 404). Пусто — одноязычный.
|
|
77
|
+
function fsLocales() {
|
|
78
|
+
const l = readDescriptor()?.locales;
|
|
79
|
+
return Array.isArray(l) ? l.map((x) => String(x).trim()).filter(Boolean) : [];
|
|
80
|
+
}
|
|
74
81
|
// Собрать meta.json всех статей (glob по папкам). Пропускает битые/незнакомые версии/черновики.
|
|
75
82
|
function listMetas() {
|
|
76
83
|
const r = root();
|
package/dist/blog.d.ts
CHANGED
|
@@ -25,10 +25,8 @@ export declare function seoBlogPost(slug: string, opts?: {
|
|
|
25
25
|
locale?: string;
|
|
26
26
|
}): Promise<SeoBlogPostFull | null>;
|
|
27
27
|
export declare function seoBlogStaticParams(opts?: {
|
|
28
|
-
|
|
29
|
-
}): Promise<
|
|
30
|
-
slug: string;
|
|
31
|
-
}[]>;
|
|
28
|
+
localeParam?: string;
|
|
29
|
+
}): Promise<Array<Record<string, string>>>;
|
|
32
30
|
export declare function SeoBlogIndex(props: {
|
|
33
31
|
locale?: string;
|
|
34
32
|
basePath?: string;
|
package/dist/blog.js
CHANGED
|
@@ -50,10 +50,20 @@ async function seoBlogPost(slug, opts) {
|
|
|
50
50
|
const m = await blogfs();
|
|
51
51
|
return m.fsBlogPost(slug, opts?.locale ?? m.fsDefaultLocale());
|
|
52
52
|
}
|
|
53
|
-
// Параметры для generateStaticParams роута
|
|
53
|
+
// Параметры для generateStaticParams роута блога: пре-рендер ВСЕХ статей на сборке (SSG из файлов).
|
|
54
|
+
// Без localeParam → [{slug}] (одноязычный /blog/[slug]). С localeParam → [{<localeParam>, slug}] для КАЖДОЙ
|
|
55
|
+
// локали (мультиязычный /[locale]/blog/[slug]): иначе force-static не покроет комбинацию locale×slug → 404.
|
|
54
56
|
async function seoBlogStaticParams(opts) {
|
|
55
|
-
const list = await seoBlogList({
|
|
56
|
-
|
|
57
|
+
const list = await seoBlogList({ limit: 100000 });
|
|
58
|
+
const slugs = list.map((p) => ({ slug: p.slug }));
|
|
59
|
+
const lp = opts?.localeParam;
|
|
60
|
+
if (!lp)
|
|
61
|
+
return slugs;
|
|
62
|
+
const m = await blogfs();
|
|
63
|
+
const locales = m.fsLocales();
|
|
64
|
+
if (!locales.length)
|
|
65
|
+
return slugs;
|
|
66
|
+
return locales.flatMap((loc) => slugs.map((s) => ({ [lp]: loc, slug: s.slug })));
|
|
57
67
|
}
|
|
58
68
|
// ── Компоненты (серверные) ──────────────────────────────────────────────────────────────────
|
|
59
69
|
// Цвета/рамки/скругления — через СОБСТВЕННЫЕ переменные пакета (--sa-*) с нейтральным дефолтом.
|
package/package.json
CHANGED