@sonordev/site-kit 1.2.7
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/README.md +376 -0
- package/dist/SetupWizard-Cki06kB0.d.mts +12 -0
- package/dist/SetupWizard-Cki06kB0.d.ts +12 -0
- package/dist/analytics/index.d.mts +93 -0
- package/dist/analytics/index.d.ts +93 -0
- package/dist/analytics/index.js +89 -0
- package/dist/analytics/index.js.map +1 -0
- package/dist/analytics/index.mjs +71 -0
- package/dist/analytics/index.mjs.map +1 -0
- package/dist/api-CWtoFJCO.d.mts +137 -0
- package/dist/api-CWtoFJCO.d.ts +137 -0
- package/dist/blog/index.d.mts +305 -0
- package/dist/blog/index.d.ts +305 -0
- package/dist/blog/index.js +1578 -0
- package/dist/blog/index.js.map +1 -0
- package/dist/blog/index.mjs +1562 -0
- package/dist/blog/index.mjs.map +1 -0
- package/dist/blog/server.d.mts +229 -0
- package/dist/blog/server.d.ts +229 -0
- package/dist/blog/server.js +692 -0
- package/dist/blog/server.js.map +1 -0
- package/dist/blog/server.mjs +666 -0
- package/dist/blog/server.mjs.map +1 -0
- package/dist/chunk-24277A3Q.mjs +968 -0
- package/dist/chunk-24277A3Q.mjs.map +1 -0
- package/dist/chunk-373TK6TZ.js +321 -0
- package/dist/chunk-373TK6TZ.js.map +1 -0
- package/dist/chunk-3MYZS6PD.js +30 -0
- package/dist/chunk-3MYZS6PD.js.map +1 -0
- package/dist/chunk-43GBM4SX.js +283 -0
- package/dist/chunk-43GBM4SX.js.map +1 -0
- package/dist/chunk-4XPGGLVP.mjs +53 -0
- package/dist/chunk-4XPGGLVP.mjs.map +1 -0
- package/dist/chunk-622GAQP5.js +2008 -0
- package/dist/chunk-622GAQP5.js.map +1 -0
- package/dist/chunk-6BIPAKL4.mjs +28 -0
- package/dist/chunk-6BIPAKL4.mjs.map +1 -0
- package/dist/chunk-6ZCISNAB.mjs +343 -0
- package/dist/chunk-6ZCISNAB.mjs.map +1 -0
- package/dist/chunk-72MQFHYJ.js +1429 -0
- package/dist/chunk-72MQFHYJ.js.map +1 -0
- package/dist/chunk-7557OTHW.js +62 -0
- package/dist/chunk-7557OTHW.js.map +1 -0
- package/dist/chunk-7FUV73JZ.js +981 -0
- package/dist/chunk-7FUV73JZ.js.map +1 -0
- package/dist/chunk-7RF6PVHA.mjs +324 -0
- package/dist/chunk-7RF6PVHA.mjs.map +1 -0
- package/dist/chunk-7RYCHO6D.mjs +134 -0
- package/dist/chunk-7RYCHO6D.mjs.map +1 -0
- package/dist/chunk-7UKPRW25.mjs +1999 -0
- package/dist/chunk-7UKPRW25.mjs.map +1 -0
- package/dist/chunk-7URAOG2M.js +14864 -0
- package/dist/chunk-7URAOG2M.js.map +1 -0
- package/dist/chunk-AFAO3TGS.mjs +810 -0
- package/dist/chunk-AFAO3TGS.mjs.map +1 -0
- package/dist/chunk-BYLIU6XG.js +343 -0
- package/dist/chunk-BYLIU6XG.js.map +1 -0
- package/dist/chunk-D63MUKZ6.mjs +4423 -0
- package/dist/chunk-D63MUKZ6.mjs.map +1 -0
- package/dist/chunk-DDKW2FNA.js +390 -0
- package/dist/chunk-DDKW2FNA.js.map +1 -0
- package/dist/chunk-DQYMKR27.mjs +341 -0
- package/dist/chunk-DQYMKR27.mjs.map +1 -0
- package/dist/chunk-DW5UJKHH.js +221 -0
- package/dist/chunk-DW5UJKHH.js.map +1 -0
- package/dist/chunk-EEZCR6E6.js +50 -0
- package/dist/chunk-EEZCR6E6.js.map +1 -0
- package/dist/chunk-GCJXQ4AG.mjs +59 -0
- package/dist/chunk-GCJXQ4AG.mjs.map +1 -0
- package/dist/chunk-JGNQK2G6.mjs +14845 -0
- package/dist/chunk-JGNQK2G6.mjs.map +1 -0
- package/dist/chunk-JTLOJLWQ.mjs +563 -0
- package/dist/chunk-JTLOJLWQ.mjs.map +1 -0
- package/dist/chunk-K23A4G76.mjs +202 -0
- package/dist/chunk-K23A4G76.mjs.map +1 -0
- package/dist/chunk-KKU3K7RG.js +336 -0
- package/dist/chunk-KKU3K7RG.js.map +1 -0
- package/dist/chunk-KUGMH4ZF.js +571 -0
- package/dist/chunk-KUGMH4ZF.js.map +1 -0
- package/dist/chunk-LBVWVP72.js +110 -0
- package/dist/chunk-LBVWVP72.js.map +1 -0
- package/dist/chunk-LIVWLY2P.js +138 -0
- package/dist/chunk-LIVWLY2P.js.map +1 -0
- package/dist/chunk-M2T6R7BA.mjs +1003 -0
- package/dist/chunk-M2T6R7BA.mjs.map +1 -0
- package/dist/chunk-MV3QN7PW.mjs +47 -0
- package/dist/chunk-MV3QN7PW.mjs.map +1 -0
- package/dist/chunk-OB7E654K.js +72 -0
- package/dist/chunk-OB7E654K.js.map +1 -0
- package/dist/chunk-OIIKTGRL.mjs +380 -0
- package/dist/chunk-OIIKTGRL.mjs.map +1 -0
- package/dist/chunk-P3UWIUJS.mjs +1427 -0
- package/dist/chunk-P3UWIUJS.mjs.map +1 -0
- package/dist/chunk-PKN27UMH.mjs +136 -0
- package/dist/chunk-PKN27UMH.mjs.map +1 -0
- package/dist/chunk-QXV4667R.mjs +105 -0
- package/dist/chunk-QXV4667R.mjs.map +1 -0
- package/dist/chunk-S7FRYNSU.mjs +315 -0
- package/dist/chunk-S7FRYNSU.mjs.map +1 -0
- package/dist/chunk-TFLQX7K7.mjs +68 -0
- package/dist/chunk-TFLQX7K7.mjs.map +1 -0
- package/dist/chunk-UWE5PCYJ.mjs +279 -0
- package/dist/chunk-UWE5PCYJ.mjs.map +1 -0
- package/dist/chunk-UYFDNX2F.js +4469 -0
- package/dist/chunk-UYFDNX2F.js.map +1 -0
- package/dist/chunk-W4PALSGM.js +350 -0
- package/dist/chunk-W4PALSGM.js.map +1 -0
- package/dist/chunk-WECQ6KOB.js +1008 -0
- package/dist/chunk-WECQ6KOB.js.map +1 -0
- package/dist/chunk-XQQWI6WB.js +814 -0
- package/dist/chunk-XQQWI6WB.js.map +1 -0
- package/dist/chunk-XZJOZJB6.js +140 -0
- package/dist/chunk-XZJOZJB6.js.map +1 -0
- package/dist/chunk-ZSMWDLMK.js +63 -0
- package/dist/chunk-ZSMWDLMK.js.map +1 -0
- package/dist/cli/index.js +37243 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/index.mjs +37209 -0
- package/dist/cli/index.mjs.map +1 -0
- package/dist/commerce/index.d.mts +170 -0
- package/dist/commerce/index.d.ts +170 -0
- package/dist/commerce/index.js +174 -0
- package/dist/commerce/index.js.map +1 -0
- package/dist/commerce/index.mjs +5 -0
- package/dist/commerce/index.mjs.map +1 -0
- package/dist/commerce/server.d.mts +107 -0
- package/dist/commerce/server.d.ts +107 -0
- package/dist/commerce/server.js +187 -0
- package/dist/commerce/server.js.map +1 -0
- package/dist/commerce/server.mjs +177 -0
- package/dist/commerce/server.mjs.map +1 -0
- package/dist/config/index.d.mts +43 -0
- package/dist/config/index.d.ts +43 -0
- package/dist/config/index.js +66 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/index.mjs +64 -0
- package/dist/config/index.mjs.map +1 -0
- package/dist/engage/index.d.mts +33 -0
- package/dist/engage/index.d.ts +33 -0
- package/dist/engage/index.js +22 -0
- package/dist/engage/index.js.map +1 -0
- package/dist/engage/index.mjs +5 -0
- package/dist/engage/index.mjs.map +1 -0
- package/dist/forms/index.d.mts +437 -0
- package/dist/forms/index.d.ts +437 -0
- package/dist/forms/index.js +1168 -0
- package/dist/forms/index.js.map +1 -0
- package/dist/forms/index.mjs +1142 -0
- package/dist/forms/index.mjs.map +1 -0
- package/dist/generators-2XKQMPKH.mjs +4 -0
- package/dist/generators-2XKQMPKH.mjs.map +1 -0
- package/dist/generators-DTMO36DV.js +33 -0
- package/dist/generators-DTMO36DV.js.map +1 -0
- package/dist/images/index.d.mts +4 -0
- package/dist/images/index.d.ts +4 -0
- package/dist/images/index.js +46 -0
- package/dist/images/index.js.map +1 -0
- package/dist/images/index.mjs +5 -0
- package/dist/images/index.mjs.map +1 -0
- package/dist/images/server.d.mts +69 -0
- package/dist/images/server.d.ts +69 -0
- package/dist/images/server.js +21 -0
- package/dist/images/server.js.map +1 -0
- package/dist/images/server.mjs +4 -0
- package/dist/images/server.mjs.map +1 -0
- package/dist/index.d.mts +846 -0
- package/dist/index.d.ts +846 -0
- package/dist/index.js +2623 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +2416 -0
- package/dist/index.mjs.map +1 -0
- package/dist/layout/index.d.mts +53 -0
- package/dist/layout/index.d.ts +53 -0
- package/dist/layout/index.js +187 -0
- package/dist/layout/index.js.map +1 -0
- package/dist/layout/index.mjs +185 -0
- package/dist/layout/index.mjs.map +1 -0
- package/dist/llms/index.d.mts +448 -0
- package/dist/llms/index.d.ts +448 -0
- package/dist/llms/index.js +581 -0
- package/dist/llms/index.js.map +1 -0
- package/dist/llms/index.mjs +529 -0
- package/dist/llms/index.mjs.map +1 -0
- package/dist/manifest/index.d.mts +62 -0
- package/dist/manifest/index.d.ts +62 -0
- package/dist/manifest/index.js +85 -0
- package/dist/manifest/index.js.map +1 -0
- package/dist/manifest/index.mjs +83 -0
- package/dist/manifest/index.mjs.map +1 -0
- package/dist/middleware/index.d.mts +63 -0
- package/dist/middleware/index.d.ts +63 -0
- package/dist/middleware/index.js +54 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/middleware/index.mjs +51 -0
- package/dist/middleware/index.mjs.map +1 -0
- package/dist/migrator-2MQHOFDQ.mjs +4 -0
- package/dist/migrator-2MQHOFDQ.mjs.map +1 -0
- package/dist/migrator-THJCF6MZ.js +37 -0
- package/dist/migrator-THJCF6MZ.js.map +1 -0
- package/dist/redirects/index.d.mts +78 -0
- package/dist/redirects/index.d.ts +78 -0
- package/dist/redirects/index.js +26 -0
- package/dist/redirects/index.js.map +1 -0
- package/dist/redirects/index.mjs +5 -0
- package/dist/redirects/index.mjs.map +1 -0
- package/dist/reputation/index.d.mts +57 -0
- package/dist/reputation/index.d.ts +57 -0
- package/dist/reputation/index.js +21 -0
- package/dist/reputation/index.js.map +1 -0
- package/dist/reputation/index.mjs +4 -0
- package/dist/reputation/index.mjs.map +1 -0
- package/dist/robots/index.d.mts +38 -0
- package/dist/robots/index.d.ts +38 -0
- package/dist/robots/index.js +52 -0
- package/dist/robots/index.js.map +1 -0
- package/dist/robots/index.mjs +50 -0
- package/dist/robots/index.mjs.map +1 -0
- package/dist/routing-B5XS-6_W.d.mts +118 -0
- package/dist/routing-DZYzyDHw.d.ts +118 -0
- package/dist/scanner-GAF5PO5F.js +53 -0
- package/dist/scanner-GAF5PO5F.js.map +1 -0
- package/dist/scanner-LKJKW7IT.mjs +4 -0
- package/dist/scanner-LKJKW7IT.mjs.map +1 -0
- package/dist/securityHeaders-nwZ6nP4g.d.mts +24 -0
- package/dist/securityHeaders-nwZ6nP4g.d.ts +24 -0
- package/dist/seo/index.d.mts +600 -0
- package/dist/seo/index.d.ts +600 -0
- package/dist/seo/index.js +883 -0
- package/dist/seo/index.js.map +1 -0
- package/dist/seo/index.mjs +773 -0
- package/dist/seo/index.mjs.map +1 -0
- package/dist/seo/register-sitemap-cli.js +151 -0
- package/dist/seo/register-sitemap-cli.js.map +1 -0
- package/dist/seo/register-sitemap-cli.mjs +144 -0
- package/dist/seo/register-sitemap-cli.mjs.map +1 -0
- package/dist/seo/server.d.mts +107 -0
- package/dist/seo/server.d.ts +107 -0
- package/dist/seo/server.js +207 -0
- package/dist/seo/server.js.map +1 -0
- package/dist/seo/server.mjs +186 -0
- package/dist/seo/server.mjs.map +1 -0
- package/dist/server-api-EWXKOQZA.mjs +4 -0
- package/dist/server-api-EWXKOQZA.mjs.map +1 -0
- package/dist/server-api-GJPNRYUP.js +81 -0
- package/dist/server-api-GJPNRYUP.js.map +1 -0
- package/dist/setup/client.d.mts +60 -0
- package/dist/setup/client.d.ts +60 -0
- package/dist/setup/client.js +31 -0
- package/dist/setup/client.js.map +1 -0
- package/dist/setup/client.mjs +6 -0
- package/dist/setup/client.mjs.map +1 -0
- package/dist/setup/index.d.mts +5 -0
- package/dist/setup/index.d.ts +5 -0
- package/dist/setup/index.js +35 -0
- package/dist/setup/index.js.map +1 -0
- package/dist/setup/index.mjs +6 -0
- package/dist/setup/index.mjs.map +1 -0
- package/dist/setup/server.d.mts +14 -0
- package/dist/setup/server.d.ts +14 -0
- package/dist/setup/server.js +13 -0
- package/dist/setup/server.js.map +1 -0
- package/dist/setup/server.mjs +4 -0
- package/dist/setup/server.mjs.map +1 -0
- package/dist/site-config/index.d.mts +24 -0
- package/dist/site-config/index.d.ts +24 -0
- package/dist/site-config/index.js +17 -0
- package/dist/site-config/index.js.map +1 -0
- package/dist/site-config/index.mjs +4 -0
- package/dist/site-config/index.mjs.map +1 -0
- package/dist/sitemap/index.d.mts +96 -0
- package/dist/sitemap/index.d.ts +96 -0
- package/dist/sitemap/index.js +288 -0
- package/dist/sitemap/index.js.map +1 -0
- package/dist/sitemap/index.mjs +285 -0
- package/dist/sitemap/index.mjs.map +1 -0
- package/dist/socket-loader-J26QHHOB.js +16 -0
- package/dist/socket-loader-J26QHHOB.js.map +1 -0
- package/dist/socket-loader-R7S2YJ2J.mjs +14 -0
- package/dist/socket-loader-R7S2YJ2J.mjs.map +1 -0
- package/dist/types-0dmq3k20.d.mts +168 -0
- package/dist/types-0dmq3k20.d.ts +168 -0
- package/dist/types-Blb2QNkV.d.mts +263 -0
- package/dist/types-Blb2QNkV.d.ts +263 -0
- package/dist/types-BnCwwUX3.d.mts +250 -0
- package/dist/types-BnCwwUX3.d.ts +250 -0
- package/dist/types-CGlnp43R.d.mts +312 -0
- package/dist/types-CGlnp43R.d.ts +312 -0
- package/dist/types-D08004rU.d.mts +179 -0
- package/dist/types-D08004rU.d.ts +179 -0
- package/dist/types-DNSYU7qI.d.mts +127 -0
- package/dist/types-DNSYU7qI.d.ts +127 -0
- package/dist/types-KZP_VWZp.d.mts +266 -0
- package/dist/types-KZP_VWZp.d.ts +266 -0
- package/dist/useEventModal-BVTx69XE.d.mts +274 -0
- package/dist/useEventModal-Dx1dItTJ.d.ts +274 -0
- package/dist/web-vitals-444RLW3B.js +252 -0
- package/dist/web-vitals-444RLW3B.js.map +1 -0
- package/dist/web-vitals-KPICZIEF.mjs +241 -0
- package/dist/web-vitals-KPICZIEF.mjs.map +1 -0
- package/package.json +192 -0
|
@@ -0,0 +1,666 @@
|
|
|
1
|
+
import '../chunk-4XPGGLVP.mjs';
|
|
2
|
+
|
|
3
|
+
// src/blog/server.ts
|
|
4
|
+
function getConfig() {
|
|
5
|
+
return {
|
|
6
|
+
apiUrl: process.env.NEXT_PUBLIC_UPTRADE_API_URL || "https://api.uptrademedia.com",
|
|
7
|
+
apiKey: process.env.NEXT_PUBLIC_UPTRADE_API_KEY || ""
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
var SEO_LIMITS = {
|
|
11
|
+
title: { min: 30, max: 60, recommended: 55 },
|
|
12
|
+
metaDescription: { min: 120, max: 160, recommended: 155 },
|
|
13
|
+
excerpt: { min: 100, max: 300, recommended: 200 },
|
|
14
|
+
slug: { max: 75 },
|
|
15
|
+
focusKeyphrase: { min: 2, max: 4, words: true }
|
|
16
|
+
// 2-4 words
|
|
17
|
+
};
|
|
18
|
+
function validateSeoTitle(title, focusKeyphrase) {
|
|
19
|
+
const length = title.length;
|
|
20
|
+
let status = "good";
|
|
21
|
+
let message = "Title length is optimal";
|
|
22
|
+
if (length > SEO_LIMITS.title.max) {
|
|
23
|
+
status = "error";
|
|
24
|
+
message = `Title is ${length - SEO_LIMITS.title.max} characters too long (max ${SEO_LIMITS.title.max})`;
|
|
25
|
+
} else if (length < SEO_LIMITS.title.min) {
|
|
26
|
+
status = "warning";
|
|
27
|
+
message = `Title is short (${length}/${SEO_LIMITS.title.min} min)`;
|
|
28
|
+
} else if (focusKeyphrase && !title.toLowerCase().includes(focusKeyphrase.toLowerCase())) {
|
|
29
|
+
status = "warning";
|
|
30
|
+
message = "Focus keyphrase not found in title";
|
|
31
|
+
} else if (focusKeyphrase && !title.toLowerCase().startsWith(focusKeyphrase.toLowerCase())) {
|
|
32
|
+
status = "warning";
|
|
33
|
+
message = "Title should start with focus keyphrase for best results";
|
|
34
|
+
}
|
|
35
|
+
return { field: "title", value: title, length, limit: SEO_LIMITS.title, status, message };
|
|
36
|
+
}
|
|
37
|
+
function validateMetaDescription(description, focusKeyphrase) {
|
|
38
|
+
const length = description.length;
|
|
39
|
+
let status = "good";
|
|
40
|
+
let message = "Meta description is optimal";
|
|
41
|
+
if (length > SEO_LIMITS.metaDescription.max) {
|
|
42
|
+
status = "error";
|
|
43
|
+
message = `Description will be truncated (${length}/${SEO_LIMITS.metaDescription.max})`;
|
|
44
|
+
} else if (length < SEO_LIMITS.metaDescription.min) {
|
|
45
|
+
status = "warning";
|
|
46
|
+
message = `Description is short (${length}/${SEO_LIMITS.metaDescription.min} min)`;
|
|
47
|
+
} else if (focusKeyphrase && !description.toLowerCase().includes(focusKeyphrase.toLowerCase())) {
|
|
48
|
+
status = "warning";
|
|
49
|
+
message = "Focus keyphrase not found in description";
|
|
50
|
+
}
|
|
51
|
+
return { field: "meta_description", value: description, length, limit: SEO_LIMITS.metaDescription, status, message };
|
|
52
|
+
}
|
|
53
|
+
function validateBlogPostSeo(post) {
|
|
54
|
+
const results = [];
|
|
55
|
+
const focusKeyphrase = post.focus_keyphrase;
|
|
56
|
+
if (post.meta_title || post.title) {
|
|
57
|
+
results.push(validateSeoTitle(post.meta_title || post.title || "", focusKeyphrase));
|
|
58
|
+
}
|
|
59
|
+
if (post.meta_description) {
|
|
60
|
+
results.push(validateMetaDescription(post.meta_description, focusKeyphrase));
|
|
61
|
+
}
|
|
62
|
+
if (post.title && focusKeyphrase) {
|
|
63
|
+
const titleLower = post.title.toLowerCase();
|
|
64
|
+
const keyLower = focusKeyphrase.toLowerCase();
|
|
65
|
+
if (!titleLower.includes(keyLower)) {
|
|
66
|
+
results.push({
|
|
67
|
+
field: "h1_alignment",
|
|
68
|
+
value: post.title,
|
|
69
|
+
length: post.title.length,
|
|
70
|
+
limit: { max: 100 },
|
|
71
|
+
status: "warning",
|
|
72
|
+
message: "H1 (title) should include the focus keyphrase"
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (post.word_count !== void 0) {
|
|
77
|
+
let wordStatus = "good";
|
|
78
|
+
let wordMessage = "Content length is optimal for SEO";
|
|
79
|
+
if (post.word_count < 600) {
|
|
80
|
+
wordStatus = "error";
|
|
81
|
+
wordMessage = `Content is thin (${post.word_count} words). Aim for 1200+ words.`;
|
|
82
|
+
} else if (post.word_count < 1200) {
|
|
83
|
+
wordStatus = "warning";
|
|
84
|
+
wordMessage = `Content is short (${post.word_count} words). Long-form (1200-2500) ranks better.`;
|
|
85
|
+
} else if (post.word_count > 2500) {
|
|
86
|
+
wordMessage = `Comprehensive content (${post.word_count} words). Consider splitting into a topic cluster.`;
|
|
87
|
+
}
|
|
88
|
+
results.push({
|
|
89
|
+
field: "word_count",
|
|
90
|
+
value: String(post.word_count),
|
|
91
|
+
length: post.word_count,
|
|
92
|
+
limit: { min: 1200, max: 2500, recommended: 1800 },
|
|
93
|
+
status: wordStatus,
|
|
94
|
+
message: wordMessage
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
return results;
|
|
98
|
+
}
|
|
99
|
+
async function getBlogPost(slug) {
|
|
100
|
+
const { apiUrl, apiKey } = getConfig();
|
|
101
|
+
if (!apiKey) {
|
|
102
|
+
console.warn("[Blog] No API key configured");
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
try {
|
|
106
|
+
const response = await fetch(`${apiUrl}/public/blog/posts/${slug}`, {
|
|
107
|
+
headers: { "x-api-key": apiKey },
|
|
108
|
+
next: { revalidate: 60 }
|
|
109
|
+
});
|
|
110
|
+
if (!response.ok) return null;
|
|
111
|
+
const data = await response.json();
|
|
112
|
+
return data.post || null;
|
|
113
|
+
} catch (error) {
|
|
114
|
+
console.error("[Blog] Error fetching post:", error);
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
async function getAllBlogSlugs() {
|
|
119
|
+
const { apiUrl, apiKey } = getConfig();
|
|
120
|
+
if (!apiKey) {
|
|
121
|
+
console.warn("[Blog] No API key configured");
|
|
122
|
+
return [];
|
|
123
|
+
}
|
|
124
|
+
try {
|
|
125
|
+
const response = await fetch(`${apiUrl}/public/blog/slugs`, {
|
|
126
|
+
headers: { "x-api-key": apiKey },
|
|
127
|
+
next: { revalidate: 300 }
|
|
128
|
+
});
|
|
129
|
+
if (!response.ok) return [];
|
|
130
|
+
const data = await response.json();
|
|
131
|
+
return data.slugs || [];
|
|
132
|
+
} catch (error) {
|
|
133
|
+
console.error("[Blog] Error fetching slugs:", error);
|
|
134
|
+
return [];
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
async function getBlogCategories() {
|
|
138
|
+
const { apiUrl, apiKey } = getConfig();
|
|
139
|
+
if (!apiKey) return [];
|
|
140
|
+
try {
|
|
141
|
+
const response = await fetch(`${apiUrl}/public/blog/categories`, {
|
|
142
|
+
headers: { "x-api-key": apiKey },
|
|
143
|
+
next: { revalidate: 300 }
|
|
144
|
+
});
|
|
145
|
+
if (!response.ok) return [];
|
|
146
|
+
const data = await response.json();
|
|
147
|
+
return data.categories || [];
|
|
148
|
+
} catch (error) {
|
|
149
|
+
console.error("[Blog] Error fetching categories:", error);
|
|
150
|
+
return [];
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
async function generateBlogPostMetadata(slug, options = {}) {
|
|
154
|
+
const post = await getBlogPost(slug);
|
|
155
|
+
const { siteName, siteUrl = "", defaultImage, twitterHandle } = options;
|
|
156
|
+
if (!post) {
|
|
157
|
+
return {
|
|
158
|
+
title: "Post Not Found",
|
|
159
|
+
description: "The requested blog post could not be found."
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
const title = post.meta_title || post.title;
|
|
163
|
+
const description = post.meta_description || post.excerpt || "";
|
|
164
|
+
const image = post.og_image || post.featured_image || defaultImage;
|
|
165
|
+
const url = `${siteUrl}/blog/${post.slug}`;
|
|
166
|
+
return {
|
|
167
|
+
title,
|
|
168
|
+
description,
|
|
169
|
+
openGraph: {
|
|
170
|
+
title: post.og_title || title,
|
|
171
|
+
description: post.og_description || description,
|
|
172
|
+
url,
|
|
173
|
+
siteName,
|
|
174
|
+
type: "article",
|
|
175
|
+
publishedTime: post.published_at,
|
|
176
|
+
authors: post.author ? [typeof post.author === "string" ? post.author : post.author.name] : void 0,
|
|
177
|
+
images: image ? [
|
|
178
|
+
{
|
|
179
|
+
url: image,
|
|
180
|
+
width: post.featured_image_width || 1200,
|
|
181
|
+
height: post.featured_image_height || 630,
|
|
182
|
+
alt: post.featured_image_alt || post.title
|
|
183
|
+
}
|
|
184
|
+
] : void 0
|
|
185
|
+
},
|
|
186
|
+
twitter: {
|
|
187
|
+
card: "summary_large_image",
|
|
188
|
+
title,
|
|
189
|
+
description,
|
|
190
|
+
images: image ? [image] : void 0,
|
|
191
|
+
creator: twitterHandle
|
|
192
|
+
},
|
|
193
|
+
alternates: {
|
|
194
|
+
canonical: post.canonical_url || url
|
|
195
|
+
},
|
|
196
|
+
other: {
|
|
197
|
+
"article:published_time": post.published_at || "",
|
|
198
|
+
"article:section": typeof post.category === "string" ? post.category : post.category?.name || ""
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
function generateBlogIndexMetadata(options) {
|
|
203
|
+
const {
|
|
204
|
+
title = "Blog",
|
|
205
|
+
description = "Read our latest articles and insights.",
|
|
206
|
+
siteName,
|
|
207
|
+
siteUrl = "",
|
|
208
|
+
defaultImage,
|
|
209
|
+
twitterHandle
|
|
210
|
+
} = options;
|
|
211
|
+
return {
|
|
212
|
+
title,
|
|
213
|
+
description,
|
|
214
|
+
openGraph: {
|
|
215
|
+
title,
|
|
216
|
+
description,
|
|
217
|
+
url: `${siteUrl}/blog`,
|
|
218
|
+
siteName,
|
|
219
|
+
type: "website",
|
|
220
|
+
images: defaultImage ? [{ url: defaultImage }] : void 0
|
|
221
|
+
},
|
|
222
|
+
twitter: {
|
|
223
|
+
card: "summary_large_image",
|
|
224
|
+
title,
|
|
225
|
+
description,
|
|
226
|
+
images: defaultImage ? [defaultImage] : void 0,
|
|
227
|
+
creator: twitterHandle
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
function generateBlogCategoryMetadata(categoryName, options = {}) {
|
|
232
|
+
const { siteName, siteUrl = "", defaultImage } = options;
|
|
233
|
+
const title = `${categoryName} - Blog`;
|
|
234
|
+
const description = `Browse all posts in ${categoryName}.`;
|
|
235
|
+
return {
|
|
236
|
+
title,
|
|
237
|
+
description,
|
|
238
|
+
openGraph: {
|
|
239
|
+
title,
|
|
240
|
+
description,
|
|
241
|
+
url: `${siteUrl}/blog/category/${categoryName.toLowerCase()}`,
|
|
242
|
+
siteName,
|
|
243
|
+
type: "website",
|
|
244
|
+
images: defaultImage ? [{ url: defaultImage }] : void 0
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
async function generateBlogStaticParams() {
|
|
249
|
+
const slugs = await getAllBlogSlugs();
|
|
250
|
+
return slugs.map((s) => ({ slug: s.slug }));
|
|
251
|
+
}
|
|
252
|
+
async function generateCategoryStaticParams() {
|
|
253
|
+
const categories = await getBlogCategories();
|
|
254
|
+
return categories.map((c) => ({ category: c.slug }));
|
|
255
|
+
}
|
|
256
|
+
async function generateBlogSitemap(siteUrl) {
|
|
257
|
+
const slugs = await getAllBlogSlugs();
|
|
258
|
+
const categories = await getBlogCategories();
|
|
259
|
+
const entries = [
|
|
260
|
+
// Blog index
|
|
261
|
+
{
|
|
262
|
+
url: `${siteUrl}/blog`,
|
|
263
|
+
changeFrequency: "daily",
|
|
264
|
+
priority: 0.8
|
|
265
|
+
}
|
|
266
|
+
];
|
|
267
|
+
categories.forEach((cat) => {
|
|
268
|
+
entries.push({
|
|
269
|
+
url: `${siteUrl}/blog/category/${cat.slug}`,
|
|
270
|
+
changeFrequency: "weekly",
|
|
271
|
+
priority: 0.6
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
slugs.forEach((post) => {
|
|
275
|
+
entries.push({
|
|
276
|
+
url: `${siteUrl}/blog/${post.slug}`,
|
|
277
|
+
lastModified: post.last_modified ? new Date(post.last_modified) : void 0,
|
|
278
|
+
changeFrequency: "weekly",
|
|
279
|
+
priority: 0.7
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
return entries;
|
|
283
|
+
}
|
|
284
|
+
function generateBlogPostSchema(post, options = {}) {
|
|
285
|
+
const { siteUrl = "", siteName, logoUrl } = options;
|
|
286
|
+
const schema = {
|
|
287
|
+
"@context": "https://schema.org",
|
|
288
|
+
"@type": "BlogPosting",
|
|
289
|
+
headline: post.title,
|
|
290
|
+
description: post.excerpt || post.meta_description,
|
|
291
|
+
url: `${siteUrl}/blog/${post.slug}`,
|
|
292
|
+
datePublished: post.published_at,
|
|
293
|
+
dateModified: post.updated_at,
|
|
294
|
+
author: {
|
|
295
|
+
"@type": "Person",
|
|
296
|
+
name: typeof post.author === "string" ? post.author : post.author?.name
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
if (post.featured_image) {
|
|
300
|
+
schema.image = {
|
|
301
|
+
"@type": "ImageObject",
|
|
302
|
+
url: post.featured_image,
|
|
303
|
+
width: post.featured_image_width || 1200,
|
|
304
|
+
height: post.featured_image_height || 630
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
if (siteName || logoUrl) {
|
|
308
|
+
schema.publisher = {
|
|
309
|
+
"@type": "Organization",
|
|
310
|
+
name: siteName,
|
|
311
|
+
logo: logoUrl ? {
|
|
312
|
+
"@type": "ImageObject",
|
|
313
|
+
url: logoUrl
|
|
314
|
+
} : void 0
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
if (post.faq_items && Array.isArray(post.faq_items) && post.faq_items.length > 0) {
|
|
318
|
+
schema.mainEntity = {
|
|
319
|
+
"@type": "FAQPage",
|
|
320
|
+
mainEntity: post.faq_items.map((faq) => ({
|
|
321
|
+
"@type": "Question",
|
|
322
|
+
name: faq.question,
|
|
323
|
+
acceptedAnswer: {
|
|
324
|
+
"@type": "Answer",
|
|
325
|
+
text: faq.answer
|
|
326
|
+
}
|
|
327
|
+
}))
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
return schema;
|
|
331
|
+
}
|
|
332
|
+
function generateBlogListSchema(options = {}) {
|
|
333
|
+
const { siteUrl = "", siteName, description } = options;
|
|
334
|
+
return {
|
|
335
|
+
"@context": "https://schema.org",
|
|
336
|
+
"@type": "Blog",
|
|
337
|
+
name: siteName ? `${siteName} Blog` : "Blog",
|
|
338
|
+
description: description || "Our latest articles and insights.",
|
|
339
|
+
url: `${siteUrl}/blog`
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
function generateFaqSchema(faqItems) {
|
|
343
|
+
if (!faqItems || faqItems.length === 0) return null;
|
|
344
|
+
return {
|
|
345
|
+
"@context": "https://schema.org",
|
|
346
|
+
"@type": "FAQPage",
|
|
347
|
+
mainEntity: faqItems.map((faq) => ({
|
|
348
|
+
"@type": "Question",
|
|
349
|
+
name: faq.question,
|
|
350
|
+
acceptedAnswer: {
|
|
351
|
+
"@type": "Answer",
|
|
352
|
+
text: faq.answer
|
|
353
|
+
}
|
|
354
|
+
}))
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
function generateHowToSchema(post, steps, options = {}) {
|
|
358
|
+
const { siteUrl = "" } = options;
|
|
359
|
+
return {
|
|
360
|
+
"@context": "https://schema.org",
|
|
361
|
+
"@type": "HowTo",
|
|
362
|
+
name: post.title,
|
|
363
|
+
description: post.excerpt || post.meta_description,
|
|
364
|
+
image: post.featured_image,
|
|
365
|
+
totalTime: post.reading_time ? `PT${post.reading_time}M` : void 0,
|
|
366
|
+
step: steps.map((step, index) => ({
|
|
367
|
+
"@type": "HowToStep",
|
|
368
|
+
position: index + 1,
|
|
369
|
+
name: step.name,
|
|
370
|
+
text: step.text,
|
|
371
|
+
image: step.image,
|
|
372
|
+
url: step.url || `${siteUrl}/blog/${post.slug}#step-${index + 1}`
|
|
373
|
+
}))
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
async function getAllBlogPosts() {
|
|
377
|
+
const { apiUrl, apiKey } = getConfig();
|
|
378
|
+
if (!apiKey) {
|
|
379
|
+
console.warn("[Blog] No API key configured");
|
|
380
|
+
return [];
|
|
381
|
+
}
|
|
382
|
+
try {
|
|
383
|
+
const response = await fetch(`${apiUrl}/public/blog/posts?limit=100`, {
|
|
384
|
+
headers: { "x-api-key": apiKey },
|
|
385
|
+
next: { revalidate: 300 }
|
|
386
|
+
});
|
|
387
|
+
if (!response.ok) return [];
|
|
388
|
+
const data = await response.json();
|
|
389
|
+
return data.posts || [];
|
|
390
|
+
} catch (error) {
|
|
391
|
+
console.error("[Blog] Error fetching posts for RSS:", error);
|
|
392
|
+
return [];
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
function escapeXml(text) {
|
|
396
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
397
|
+
}
|
|
398
|
+
function stripHtml(html) {
|
|
399
|
+
return html.replace(/<[^>]*>/g, "").trim();
|
|
400
|
+
}
|
|
401
|
+
async function generateRssFeed(options) {
|
|
402
|
+
const {
|
|
403
|
+
siteUrl,
|
|
404
|
+
siteName,
|
|
405
|
+
description = "Latest blog posts and insights",
|
|
406
|
+
language = "en-us",
|
|
407
|
+
copyright,
|
|
408
|
+
managingEditor,
|
|
409
|
+
webMaster,
|
|
410
|
+
ttl = 60,
|
|
411
|
+
imageUrl
|
|
412
|
+
} = options;
|
|
413
|
+
const posts = await getAllBlogPosts();
|
|
414
|
+
const now = (/* @__PURE__ */ new Date()).toUTCString();
|
|
415
|
+
let rss = `<?xml version="1.0" encoding="UTF-8"?>
|
|
416
|
+
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
|
|
417
|
+
<channel>
|
|
418
|
+
<title>${escapeXml(siteName)}</title>
|
|
419
|
+
<link>${siteUrl}</link>
|
|
420
|
+
<description>${escapeXml(description)}</description>
|
|
421
|
+
<language>${language}</language>
|
|
422
|
+
<lastBuildDate>${now}</lastBuildDate>
|
|
423
|
+
<pubDate>${now}</pubDate>
|
|
424
|
+
<ttl>${ttl}</ttl>
|
|
425
|
+
<atom:link href="${siteUrl}/blog/rss.xml" rel="self" type="application/rss+xml"/>
|
|
426
|
+
`;
|
|
427
|
+
if (copyright) {
|
|
428
|
+
rss += ` <copyright>${escapeXml(copyright)}</copyright>
|
|
429
|
+
`;
|
|
430
|
+
}
|
|
431
|
+
if (managingEditor) {
|
|
432
|
+
rss += ` <managingEditor>${escapeXml(managingEditor)}</managingEditor>
|
|
433
|
+
`;
|
|
434
|
+
}
|
|
435
|
+
if (webMaster) {
|
|
436
|
+
rss += ` <webMaster>${escapeXml(webMaster)}</webMaster>
|
|
437
|
+
`;
|
|
438
|
+
}
|
|
439
|
+
if (imageUrl) {
|
|
440
|
+
rss += ` <image>
|
|
441
|
+
<url>${imageUrl}</url>
|
|
442
|
+
<title>${escapeXml(siteName)}</title>
|
|
443
|
+
<link>${siteUrl}</link>
|
|
444
|
+
</image>
|
|
445
|
+
`;
|
|
446
|
+
}
|
|
447
|
+
for (const post of posts) {
|
|
448
|
+
const postUrl = `${siteUrl}/blog/${post.slug}`;
|
|
449
|
+
const pubDate = post.published_at ? new Date(post.published_at).toUTCString() : now;
|
|
450
|
+
const author = typeof post.author === "string" ? post.author : post.author?.name || "Unknown";
|
|
451
|
+
const fullContent = post.content_html || post.content || "";
|
|
452
|
+
const description2 = post.excerpt || stripHtml(fullContent).substring(0, 300) + "...";
|
|
453
|
+
rss += ` <item>
|
|
454
|
+
<title>${escapeXml(post.title)}</title>
|
|
455
|
+
<link>${postUrl}</link>
|
|
456
|
+
<guid isPermaLink="true">${postUrl}</guid>
|
|
457
|
+
<description>${escapeXml(description2)}</description>
|
|
458
|
+
<content:encoded><![CDATA[${fullContent}]]></content:encoded>
|
|
459
|
+
<pubDate>${pubDate}</pubDate>
|
|
460
|
+
<author>${escapeXml(author)}</author>
|
|
461
|
+
`;
|
|
462
|
+
if (post.category) {
|
|
463
|
+
const categoryName = typeof post.category === "string" ? post.category : post.category.name;
|
|
464
|
+
rss += ` <category>${escapeXml(categoryName)}</category>
|
|
465
|
+
`;
|
|
466
|
+
}
|
|
467
|
+
if (post.tags && Array.isArray(post.tags)) {
|
|
468
|
+
for (const tag of post.tags) {
|
|
469
|
+
const tagName = typeof tag === "string" ? tag : tag.name;
|
|
470
|
+
rss += ` <category>${escapeXml(tagName)}</category>
|
|
471
|
+
`;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
if (post.featured_image) {
|
|
475
|
+
rss += ` <enclosure url="${post.featured_image}" type="image/jpeg"/>
|
|
476
|
+
`;
|
|
477
|
+
}
|
|
478
|
+
rss += ` </item>
|
|
479
|
+
`;
|
|
480
|
+
}
|
|
481
|
+
rss += ` </channel>
|
|
482
|
+
</rss>`;
|
|
483
|
+
return rss;
|
|
484
|
+
}
|
|
485
|
+
async function generateAtomFeed(options) {
|
|
486
|
+
const {
|
|
487
|
+
siteUrl,
|
|
488
|
+
siteName,
|
|
489
|
+
description = "Latest blog posts and insights",
|
|
490
|
+
managingEditor
|
|
491
|
+
} = options;
|
|
492
|
+
const posts = await getAllBlogPosts();
|
|
493
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
494
|
+
let atom = `<?xml version="1.0" encoding="UTF-8"?>
|
|
495
|
+
<feed xmlns="http://www.w3.org/2005/Atom">
|
|
496
|
+
<title>${escapeXml(siteName)}</title>
|
|
497
|
+
<subtitle>${escapeXml(description)}</subtitle>
|
|
498
|
+
<link href="${siteUrl}/blog/feed.xml" rel="self"/>
|
|
499
|
+
<link href="${siteUrl}"/>
|
|
500
|
+
<id>${siteUrl}/blog</id>
|
|
501
|
+
<updated>${now}</updated>
|
|
502
|
+
`;
|
|
503
|
+
if (managingEditor) {
|
|
504
|
+
atom += ` <author>
|
|
505
|
+
<name>${escapeXml(managingEditor)}</name>
|
|
506
|
+
</author>
|
|
507
|
+
`;
|
|
508
|
+
}
|
|
509
|
+
for (const post of posts) {
|
|
510
|
+
const postUrl = `${siteUrl}/blog/${post.slug}`;
|
|
511
|
+
const updated = post.updated_at || post.published_at || now;
|
|
512
|
+
const published = post.published_at || now;
|
|
513
|
+
const author = typeof post.author === "string" ? post.author : post.author?.name || "Unknown";
|
|
514
|
+
const fullContent = post.content_html || post.content || "";
|
|
515
|
+
atom += ` <entry>
|
|
516
|
+
<title>${escapeXml(post.title)}</title>
|
|
517
|
+
<link href="${postUrl}"/>
|
|
518
|
+
<id>${postUrl}</id>
|
|
519
|
+
<updated>${new Date(updated).toISOString()}</updated>
|
|
520
|
+
<published>${new Date(published).toISOString()}</published>
|
|
521
|
+
<author>
|
|
522
|
+
<name>${escapeXml(author)}</name>
|
|
523
|
+
</author>
|
|
524
|
+
<summary>${escapeXml(post.excerpt || stripHtml(fullContent).substring(0, 300))}</summary>
|
|
525
|
+
<content type="html"><![CDATA[${fullContent}]]></content>
|
|
526
|
+
</entry>
|
|
527
|
+
`;
|
|
528
|
+
}
|
|
529
|
+
atom += `</feed>`;
|
|
530
|
+
return atom;
|
|
531
|
+
}
|
|
532
|
+
async function getPostsByCategory(categorySlug) {
|
|
533
|
+
const { apiUrl, apiKey } = getConfig();
|
|
534
|
+
if (!apiKey) return [];
|
|
535
|
+
try {
|
|
536
|
+
const response = await fetch(`${apiUrl}/public/blog/posts?category=${categorySlug}&limit=50`, {
|
|
537
|
+
headers: { "x-api-key": apiKey },
|
|
538
|
+
next: { revalidate: 300 }
|
|
539
|
+
});
|
|
540
|
+
if (!response.ok) return [];
|
|
541
|
+
const data = await response.json();
|
|
542
|
+
return data.posts || [];
|
|
543
|
+
} catch (error) {
|
|
544
|
+
console.error("[Blog] Error fetching category posts:", error);
|
|
545
|
+
return [];
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
async function identifyTopicClusters(categorySlug) {
|
|
549
|
+
const posts = await getPostsByCategory(categorySlug);
|
|
550
|
+
if (posts.length < 3) return null;
|
|
551
|
+
const sortedByLength = [...posts].sort((a, b) => (b.word_count || 0) - (a.word_count || 0));
|
|
552
|
+
const pillar = sortedByLength[0];
|
|
553
|
+
const supportingPosts = sortedByLength.slice(1);
|
|
554
|
+
const internalLinks = [];
|
|
555
|
+
for (const support of supportingPosts) {
|
|
556
|
+
internalLinks.push({
|
|
557
|
+
from: pillar.slug,
|
|
558
|
+
to: support.slug,
|
|
559
|
+
anchor: support.title
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
for (const support of supportingPosts) {
|
|
563
|
+
internalLinks.push({
|
|
564
|
+
from: support.slug,
|
|
565
|
+
to: pillar.slug,
|
|
566
|
+
anchor: pillar.title
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
return {
|
|
570
|
+
pillar,
|
|
571
|
+
supportingPosts,
|
|
572
|
+
internalLinks
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
async function getRelatedInsights(currentSlug, options = {}) {
|
|
576
|
+
const { apiUrl, apiKey } = getConfig();
|
|
577
|
+
const { limit = 3, category } = options;
|
|
578
|
+
if (!apiKey) return [];
|
|
579
|
+
try {
|
|
580
|
+
const response = await fetch(`${apiUrl}/public/blog/related`, {
|
|
581
|
+
method: "POST",
|
|
582
|
+
headers: {
|
|
583
|
+
"x-api-key": apiKey,
|
|
584
|
+
"Content-Type": "application/json"
|
|
585
|
+
},
|
|
586
|
+
body: JSON.stringify({
|
|
587
|
+
slug: currentSlug,
|
|
588
|
+
limit,
|
|
589
|
+
category
|
|
590
|
+
}),
|
|
591
|
+
next: { revalidate: 300 }
|
|
592
|
+
});
|
|
593
|
+
if (!response.ok) return [];
|
|
594
|
+
const data = await response.json();
|
|
595
|
+
return data.posts || [];
|
|
596
|
+
} catch (error) {
|
|
597
|
+
console.error("[Blog] Error fetching related posts:", error);
|
|
598
|
+
return [];
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
function generateBreadcrumbSchema(post, options = {}) {
|
|
602
|
+
const { siteUrl = "", siteName = "Home" } = options;
|
|
603
|
+
const items = [
|
|
604
|
+
{
|
|
605
|
+
"@type": "ListItem",
|
|
606
|
+
position: 1,
|
|
607
|
+
name: siteName,
|
|
608
|
+
item: siteUrl
|
|
609
|
+
},
|
|
610
|
+
{
|
|
611
|
+
"@type": "ListItem",
|
|
612
|
+
position: 2,
|
|
613
|
+
name: "Blog",
|
|
614
|
+
item: `${siteUrl}/blog`
|
|
615
|
+
}
|
|
616
|
+
];
|
|
617
|
+
if (post.category) {
|
|
618
|
+
const categoryName = typeof post.category === "string" ? post.category : post.category.name;
|
|
619
|
+
const categorySlug = typeof post.category === "string" ? post.category.toLowerCase().replace(/\s+/g, "-") : post.category.slug;
|
|
620
|
+
items.push({
|
|
621
|
+
"@type": "ListItem",
|
|
622
|
+
position: 3,
|
|
623
|
+
name: categoryName,
|
|
624
|
+
item: `${siteUrl}/blog/category/${categorySlug}`
|
|
625
|
+
});
|
|
626
|
+
items.push({
|
|
627
|
+
"@type": "ListItem",
|
|
628
|
+
position: 4,
|
|
629
|
+
name: post.title,
|
|
630
|
+
item: `${siteUrl}/blog/${post.slug}`
|
|
631
|
+
});
|
|
632
|
+
} else {
|
|
633
|
+
items.push({
|
|
634
|
+
"@type": "ListItem",
|
|
635
|
+
position: 3,
|
|
636
|
+
name: post.title,
|
|
637
|
+
item: `${siteUrl}/blog/${post.slug}`
|
|
638
|
+
});
|
|
639
|
+
}
|
|
640
|
+
return {
|
|
641
|
+
"@context": "https://schema.org",
|
|
642
|
+
"@type": "BreadcrumbList",
|
|
643
|
+
itemListElement: items
|
|
644
|
+
};
|
|
645
|
+
}
|
|
646
|
+
function generateAllBlogSchemas(post, options = {}) {
|
|
647
|
+
const schemas = [];
|
|
648
|
+
const eeatSchema = post.schema;
|
|
649
|
+
if (eeatSchema) {
|
|
650
|
+
const arr = Array.isArray(eeatSchema) ? eeatSchema : [eeatSchema];
|
|
651
|
+
schemas.push(...arr);
|
|
652
|
+
} else {
|
|
653
|
+
schemas.push(generateBlogPostSchema(post, options));
|
|
654
|
+
const faqItems = post.faq_items ?? post.faqItems;
|
|
655
|
+
if (faqItems && faqItems.length > 0) {
|
|
656
|
+
const faqSchema = generateFaqSchema(faqItems);
|
|
657
|
+
if (faqSchema) schemas.push(faqSchema);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
schemas.push(generateBreadcrumbSchema(post, options));
|
|
661
|
+
return schemas;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
export { SEO_LIMITS, generateAllBlogSchemas, generateAtomFeed, generateBlogCategoryMetadata, generateBlogIndexMetadata, generateBlogListSchema, generateBlogPostMetadata, generateBlogPostSchema, generateBlogSitemap, generateBlogStaticParams, generateBreadcrumbSchema, generateCategoryStaticParams, generateFaqSchema, generateHowToSchema, generateRssFeed, getAllBlogPosts, getAllBlogSlugs, getBlogCategories, getBlogPost, getPostsByCategory, getRelatedInsights, identifyTopicClusters, validateBlogPostSeo, validateMetaDescription, validateSeoTitle };
|
|
665
|
+
//# sourceMappingURL=server.mjs.map
|
|
666
|
+
//# sourceMappingURL=server.mjs.map
|