@lynxflow/seo-engine 1.5.5 → 1.5.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/dist/extended-schemas.d.ts +38 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +262 -17
- package/dist/index.mjs +262 -17
- package/dist/keyword-permutator.d.ts +29 -0
- package/dist/matrix-engine.d.ts +24 -0
- package/dist/urlytics-engine.d.ts +33 -0
- package/package.json +1 -1
- package/src/engine.test.ts +84 -0
- package/src/extended-schemas.ts +98 -0
- package/src/index.ts +2 -0
- package/src/keyword-permutator.ts +118 -0
- package/src/llm-prompt.ts +31 -16
- package/src/matrix-engine.ts +84 -1
- package/src/urlytics-engine.ts +97 -0
|
@@ -103,4 +103,42 @@ export declare class ExtendedSchemaGraphBuilder {
|
|
|
103
103
|
name: string;
|
|
104
104
|
url: string;
|
|
105
105
|
}[]): Record<string, unknown>;
|
|
106
|
+
/**
|
|
107
|
+
* 7. AggregateRating (Google Gold Stars in SERPs) - Inspired by santifer-irepair
|
|
108
|
+
*/
|
|
109
|
+
static buildAggregateRating(opts: {
|
|
110
|
+
ratingValue?: number;
|
|
111
|
+
reviewCount?: number;
|
|
112
|
+
bestRating?: number;
|
|
113
|
+
worstRating?: number;
|
|
114
|
+
itemReviewedName: string;
|
|
115
|
+
}): Record<string, unknown>;
|
|
116
|
+
/**
|
|
117
|
+
* 8. AggregateOffer (Price Ranges & Tiers) - Inspired by santifer-irepair
|
|
118
|
+
*/
|
|
119
|
+
static buildAggregateOffer(opts: {
|
|
120
|
+
lowPrice: number;
|
|
121
|
+
highPrice: number;
|
|
122
|
+
currency: string;
|
|
123
|
+
offerCount?: number;
|
|
124
|
+
description?: string;
|
|
125
|
+
}): Record<string, unknown>;
|
|
126
|
+
/**
|
|
127
|
+
* 9. Service with ServiceArea & Providers
|
|
128
|
+
*/
|
|
129
|
+
static buildService(opts: {
|
|
130
|
+
name: string;
|
|
131
|
+
description: string;
|
|
132
|
+
providerName: string;
|
|
133
|
+
areaServed?: string[];
|
|
134
|
+
serviceType?: string;
|
|
135
|
+
}): Record<string, unknown>;
|
|
136
|
+
/**
|
|
137
|
+
* 10. WebSite with Google Sitelinks SearchBox
|
|
138
|
+
*/
|
|
139
|
+
static buildWebSite(opts: {
|
|
140
|
+
name: string;
|
|
141
|
+
url: string;
|
|
142
|
+
searchUrlTemplate?: string;
|
|
143
|
+
}): Record<string, unknown>;
|
|
106
144
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -14,6 +14,8 @@ export * from "./slug-engine";
|
|
|
14
14
|
export * from "./legal-disclaimers";
|
|
15
15
|
export * from "./matrix-engine";
|
|
16
16
|
export * from "./brand-icons";
|
|
17
|
+
export * from "./urlytics-engine";
|
|
18
|
+
export * from "./keyword-permutator";
|
|
17
19
|
export * from "./llm-prompt";
|
|
18
20
|
export * from "./auth-key";
|
|
19
21
|
export * from "./token-quota-manager";
|
package/dist/index.js
CHANGED
|
@@ -47,6 +47,7 @@ __export(exports_src, {
|
|
|
47
47
|
createLynxSeoEngine: () => createLynxSeoEngine,
|
|
48
48
|
cleanSeoSlug: () => cleanSeoSlug,
|
|
49
49
|
YoastParityEngine: () => YoastParityEngine,
|
|
50
|
+
UrlyticsEngine: () => UrlyticsEngine,
|
|
50
51
|
TokenQuotaManager: () => TokenQuotaManager,
|
|
51
52
|
TechnicalRulesAuditor: () => TechnicalRulesAuditor,
|
|
52
53
|
TeamRbacEngine: () => TeamRbacEngine,
|
|
@@ -78,6 +79,7 @@ __export(exports_src, {
|
|
|
78
79
|
LegalDisclaimerEngine: () => LegalDisclaimerEngine,
|
|
79
80
|
LagoTokenMeter: () => LagoTokenMeter,
|
|
80
81
|
KnowledgeGraphLinker: () => KnowledgeGraphLinker,
|
|
82
|
+
KeywordPermutatorEngine: () => KeywordPermutatorEngine,
|
|
81
83
|
IsrCacheManager: () => IsrCacheManager,
|
|
82
84
|
InstantMatrixSearchEngine: () => InstantMatrixSearchEngine,
|
|
83
85
|
IndexNowClient: () => IndexNowClient,
|
|
@@ -3130,6 +3132,62 @@ class ExtendedSchemaGraphBuilder {
|
|
|
3130
3132
|
}))
|
|
3131
3133
|
};
|
|
3132
3134
|
}
|
|
3135
|
+
static buildAggregateRating(opts) {
|
|
3136
|
+
return {
|
|
3137
|
+
"@type": "AggregateRating",
|
|
3138
|
+
ratingValue: opts.ratingValue ?? 4.9,
|
|
3139
|
+
reviewCount: opts.reviewCount ?? 1280,
|
|
3140
|
+
bestRating: opts.bestRating ?? 5,
|
|
3141
|
+
worstRating: opts.worstRating ?? 1,
|
|
3142
|
+
itemReviewed: {
|
|
3143
|
+
"@type": "Thing",
|
|
3144
|
+
name: opts.itemReviewedName
|
|
3145
|
+
}
|
|
3146
|
+
};
|
|
3147
|
+
}
|
|
3148
|
+
static buildAggregateOffer(opts) {
|
|
3149
|
+
return {
|
|
3150
|
+
"@type": "AggregateOffer",
|
|
3151
|
+
lowPrice: String(opts.lowPrice),
|
|
3152
|
+
highPrice: String(opts.highPrice),
|
|
3153
|
+
priceCurrency: opts.currency,
|
|
3154
|
+
offerCount: String(opts.offerCount ?? 3),
|
|
3155
|
+
description: opts.description || "Transparent pricing with zero lock-in"
|
|
3156
|
+
};
|
|
3157
|
+
}
|
|
3158
|
+
static buildService(opts) {
|
|
3159
|
+
return {
|
|
3160
|
+
"@context": "https://schema.org",
|
|
3161
|
+
"@type": "Service",
|
|
3162
|
+
name: opts.name,
|
|
3163
|
+
description: opts.description,
|
|
3164
|
+
serviceType: opts.serviceType || "SoftwareService",
|
|
3165
|
+
provider: {
|
|
3166
|
+
"@type": "Organization",
|
|
3167
|
+
name: opts.providerName
|
|
3168
|
+
},
|
|
3169
|
+
areaServed: opts.areaServed ? opts.areaServed.map((city) => ({
|
|
3170
|
+
"@type": "City",
|
|
3171
|
+
name: city
|
|
3172
|
+
})) : undefined
|
|
3173
|
+
};
|
|
3174
|
+
}
|
|
3175
|
+
static buildWebSite(opts) {
|
|
3176
|
+
return {
|
|
3177
|
+
"@context": "https://schema.org",
|
|
3178
|
+
"@type": "WebSite",
|
|
3179
|
+
name: opts.name,
|
|
3180
|
+
url: opts.url,
|
|
3181
|
+
potentialAction: opts.searchUrlTemplate ? {
|
|
3182
|
+
"@type": "SearchAction",
|
|
3183
|
+
target: {
|
|
3184
|
+
"@type": "EntryPoint",
|
|
3185
|
+
urlTemplate: opts.searchUrlTemplate
|
|
3186
|
+
},
|
|
3187
|
+
"query-input": "required name=search_term_string"
|
|
3188
|
+
} : undefined
|
|
3189
|
+
};
|
|
3190
|
+
}
|
|
3133
3191
|
}
|
|
3134
3192
|
|
|
3135
3193
|
// src/matrix-engine.ts
|
|
@@ -3214,7 +3272,7 @@ class PseoMatrixEngine {
|
|
|
3214
3272
|
const countryCode = cleanSeoSlug(loc.country, { language: lang });
|
|
3215
3273
|
const citySlug = cleanSeoSlug(loc.slug, { language: lang });
|
|
3216
3274
|
const serviceSlug = cleanSeoSlug(s.slug, { language: lang });
|
|
3217
|
-
const urlPath = `${prefix}/${serviceSlug}/${countryCode}/${citySlug}`;
|
|
3275
|
+
const urlPath = options.cleanDirectRoutes !== false ? `/${serviceSlug}/${citySlug}` : `${prefix}/${serviceSlug}/${countryCode}/${citySlug}`;
|
|
3218
3276
|
const fullUrl = `${cleanDomain}${urlPath}`;
|
|
3219
3277
|
const isIndexed = (loc.population ?? 20000) >= minPop;
|
|
3220
3278
|
const robots = isIndexed ? "index, follow" : "noindex, follow";
|
|
@@ -3637,6 +3695,40 @@ class PseoMatrixEngine {
|
|
|
3637
3695
|
}
|
|
3638
3696
|
return allPages;
|
|
3639
3697
|
}
|
|
3698
|
+
resolvePage(slugOrPath, domain, data, options) {
|
|
3699
|
+
const path = typeof slugOrPath === "string" ? slugOrPath.startsWith("/") ? slugOrPath : `/${slugOrPath}` : `/${slugOrPath.join("/")}`;
|
|
3700
|
+
const pages = this.generateAllMatrices(domain, data, options);
|
|
3701
|
+
return pages.find((p) => p.urlPath === path);
|
|
3702
|
+
}
|
|
3703
|
+
generateSitemapXml(domain, data, options) {
|
|
3704
|
+
const pages = this.generateAllMatrices(domain, data, options).filter((p) => p.robots.includes("index"));
|
|
3705
|
+
const now = new Date().toISOString();
|
|
3706
|
+
const urls = pages.map((p) => ` <url>
|
|
3707
|
+
<loc>${p.canonicalUrl}</loc>
|
|
3708
|
+
<lastmod>${now}</lastmod>
|
|
3709
|
+
<changefreq>weekly</changefreq>
|
|
3710
|
+
<priority>0.8</priority>
|
|
3711
|
+
</url>`).join(`
|
|
3712
|
+
`);
|
|
3713
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
3714
|
+
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
3715
|
+
${urls}
|
|
3716
|
+
</urlset>`;
|
|
3717
|
+
}
|
|
3718
|
+
generateLlmsTxt(domain, data, options) {
|
|
3719
|
+
const pages = this.generateAllMatrices(domain, data, options);
|
|
3720
|
+
const lines = [
|
|
3721
|
+
`# ${options.brandName} — AI Knowledge Graph & Page Hub`,
|
|
3722
|
+
`> Complete index of services, tools, integrations, and local solution hubs.`,
|
|
3723
|
+
"",
|
|
3724
|
+
`## Indexed Programmatic Hubs (${pages.length} Pages)`
|
|
3725
|
+
];
|
|
3726
|
+
for (const p of pages) {
|
|
3727
|
+
lines.push(`- [${p.h1}](${p.canonicalUrl}): ${p.description}`);
|
|
3728
|
+
}
|
|
3729
|
+
return lines.join(`
|
|
3730
|
+
`);
|
|
3731
|
+
}
|
|
3640
3732
|
}
|
|
3641
3733
|
// src/brand-icons.ts
|
|
3642
3734
|
var BRAND_ICONS = {
|
|
@@ -3754,6 +3846,144 @@ function renderBrandIconSvg(input, className = "w-5 h-5 inline-block") {
|
|
|
3754
3846
|
return "";
|
|
3755
3847
|
return `<svg class="${className}" viewBox="${icon.viewBox}" fill="currentColor" aria-hidden="true"><path d="${icon.svgPath}"/></svg>`;
|
|
3756
3848
|
}
|
|
3849
|
+
// src/urlytics-engine.ts
|
|
3850
|
+
class UrlyticsEngine {
|
|
3851
|
+
static parseUrl(rawUrl) {
|
|
3852
|
+
if (!rawUrl || typeof rawUrl !== "string") {
|
|
3853
|
+
return {
|
|
3854
|
+
url: "",
|
|
3855
|
+
scheme: "",
|
|
3856
|
+
domain: "",
|
|
3857
|
+
path: "/",
|
|
3858
|
+
depth: 0,
|
|
3859
|
+
lastDir: "",
|
|
3860
|
+
queryParams: {},
|
|
3861
|
+
slugTokens: [],
|
|
3862
|
+
charCount: 0,
|
|
3863
|
+
hasTrailingSlash: false
|
|
3864
|
+
};
|
|
3865
|
+
}
|
|
3866
|
+
try {
|
|
3867
|
+
const parsed = new URL(rawUrl.startsWith("http") ? rawUrl : `https://${rawUrl}`);
|
|
3868
|
+
const pathClean = parsed.pathname.replace(/\/+$/, "");
|
|
3869
|
+
const segments = pathClean.split("/").filter(Boolean);
|
|
3870
|
+
const queryParams = {};
|
|
3871
|
+
parsed.searchParams.forEach((val, key) => {
|
|
3872
|
+
queryParams[key] = val;
|
|
3873
|
+
});
|
|
3874
|
+
const lastDir = segments.length > 0 ? segments[segments.length - 1] : "";
|
|
3875
|
+
const slugTokens = lastDir.split("-").filter(Boolean);
|
|
3876
|
+
return {
|
|
3877
|
+
url: rawUrl,
|
|
3878
|
+
scheme: parsed.protocol.replace(":", ""),
|
|
3879
|
+
domain: parsed.hostname,
|
|
3880
|
+
path: parsed.pathname,
|
|
3881
|
+
depth: segments.length,
|
|
3882
|
+
dir1: segments[0],
|
|
3883
|
+
dir2: segments[1],
|
|
3884
|
+
dir3: segments[2],
|
|
3885
|
+
lastDir,
|
|
3886
|
+
queryParams,
|
|
3887
|
+
hashFragment: parsed.hash ? parsed.hash.replace("#", "") : undefined,
|
|
3888
|
+
slugTokens,
|
|
3889
|
+
charCount: rawUrl.length,
|
|
3890
|
+
hasTrailingSlash: parsed.pathname.length > 1 && parsed.pathname.endsWith("/")
|
|
3891
|
+
};
|
|
3892
|
+
} catch {
|
|
3893
|
+
return {
|
|
3894
|
+
url: rawUrl,
|
|
3895
|
+
scheme: "unknown",
|
|
3896
|
+
domain: "",
|
|
3897
|
+
path: rawUrl,
|
|
3898
|
+
depth: 0,
|
|
3899
|
+
lastDir: rawUrl,
|
|
3900
|
+
queryParams: {},
|
|
3901
|
+
slugTokens: [rawUrl],
|
|
3902
|
+
charCount: rawUrl.length,
|
|
3903
|
+
hasTrailingSlash: false
|
|
3904
|
+
};
|
|
3905
|
+
}
|
|
3906
|
+
}
|
|
3907
|
+
static analyzeUrls(urls) {
|
|
3908
|
+
return urls.map((u) => this.parseUrl(u));
|
|
3909
|
+
}
|
|
3910
|
+
}
|
|
3911
|
+
// src/keyword-permutator.ts
|
|
3912
|
+
class KeywordPermutatorEngine {
|
|
3913
|
+
static generateKeywordMatrix(options) {
|
|
3914
|
+
const { products, words = [], locations = [], maxCombinations = 5000 } = options;
|
|
3915
|
+
const results = [];
|
|
3916
|
+
const intentKeywords = {
|
|
3917
|
+
transactional: ["buy", "pricing", "cost", "hire", "quote", "tarif", "prix", "devis", "acheter"],
|
|
3918
|
+
commercial: ["best", "top", "review", "vs", "comparison", "alternative", "comparatif", "meilleur"],
|
|
3919
|
+
informational: ["how to", "what is", "guide", "tutorial", "definition", "comment", "quest ce que"],
|
|
3920
|
+
navigational: ["login", "app", "portal", "website", "connexion"]
|
|
3921
|
+
};
|
|
3922
|
+
const detectIntent = (text) => {
|
|
3923
|
+
const lower = text.toLowerCase();
|
|
3924
|
+
for (const [intent, triggers] of Object.entries(intentKeywords)) {
|
|
3925
|
+
if (triggers.some((t) => lower.includes(t))) {
|
|
3926
|
+
return intent;
|
|
3927
|
+
}
|
|
3928
|
+
}
|
|
3929
|
+
return "commercial";
|
|
3930
|
+
};
|
|
3931
|
+
for (const prod of products) {
|
|
3932
|
+
results.push({
|
|
3933
|
+
keyword: prod,
|
|
3934
|
+
exactMatch: `[${prod}]`,
|
|
3935
|
+
phraseMatch: `"${prod}"`,
|
|
3936
|
+
intent: detectIntent(prod),
|
|
3937
|
+
product: prod
|
|
3938
|
+
});
|
|
3939
|
+
for (const word of words) {
|
|
3940
|
+
const kw1 = `${word} ${prod}`;
|
|
3941
|
+
const kw2 = `${prod} ${word}`;
|
|
3942
|
+
results.push({
|
|
3943
|
+
keyword: kw1,
|
|
3944
|
+
exactMatch: `[${kw1}]`,
|
|
3945
|
+
phraseMatch: `"${kw1}"`,
|
|
3946
|
+
intent: detectIntent(word),
|
|
3947
|
+
product: prod,
|
|
3948
|
+
modifier: word
|
|
3949
|
+
});
|
|
3950
|
+
results.push({
|
|
3951
|
+
keyword: kw2,
|
|
3952
|
+
exactMatch: `[${kw2}]`,
|
|
3953
|
+
phraseMatch: `"${kw2}"`,
|
|
3954
|
+
intent: detectIntent(word),
|
|
3955
|
+
product: prod,
|
|
3956
|
+
modifier: word
|
|
3957
|
+
});
|
|
3958
|
+
for (const loc of locations) {
|
|
3959
|
+
const kwLoc1 = `${word} ${prod} ${loc}`;
|
|
3960
|
+
const kwLoc2 = `${prod} ${loc} ${word}`;
|
|
3961
|
+
results.push({
|
|
3962
|
+
keyword: kwLoc1,
|
|
3963
|
+
exactMatch: `[${kwLoc1}]`,
|
|
3964
|
+
phraseMatch: `"${kwLoc1}"`,
|
|
3965
|
+
intent: detectIntent(word),
|
|
3966
|
+
product: prod,
|
|
3967
|
+
modifier: word,
|
|
3968
|
+
location: loc
|
|
3969
|
+
});
|
|
3970
|
+
results.push({
|
|
3971
|
+
keyword: kwLoc2,
|
|
3972
|
+
exactMatch: `[${kwLoc2}]`,
|
|
3973
|
+
phraseMatch: `"${kwLoc2}"`,
|
|
3974
|
+
intent: detectIntent(word),
|
|
3975
|
+
product: prod,
|
|
3976
|
+
modifier: word,
|
|
3977
|
+
location: loc
|
|
3978
|
+
});
|
|
3979
|
+
if (results.length >= maxCombinations)
|
|
3980
|
+
return results;
|
|
3981
|
+
}
|
|
3982
|
+
}
|
|
3983
|
+
}
|
|
3984
|
+
return results;
|
|
3985
|
+
}
|
|
3986
|
+
}
|
|
3757
3987
|
// src/llm-prompt.ts
|
|
3758
3988
|
var PSEO_AGENT_SYSTEM_PROMPT = `
|
|
3759
3989
|
You are the Programmatic SEO Implementation Specialist powered by @lynxflow/seo-engine.
|
|
@@ -3788,7 +4018,21 @@ When discovering product modules, features, or services to build the SEO matrice
|
|
|
3788
4018
|
|
|
3789
4019
|
---
|
|
3790
4020
|
|
|
3791
|
-
### \
|
|
4021
|
+
### \uD83D\uDCCF 3. THE GOLDEN RULES OF URL ARCHITECTURE (MAX 3-4 SEGMENTS, ZERO PARASITE WORDS)
|
|
4022
|
+
|
|
4023
|
+
- **Rule A (2 to 3 URL Segments Ideal, 4 Maximum):**
|
|
4024
|
+
- ✅ **2 Segments (Top SEO Performance):** \`site.com/{service}/{city}\` (e.g. \`/autopost-facebook/lyon\`, \`/crm/paris\`).
|
|
4025
|
+
- ✅ **3 Segments (B2B Persona/Hub):** \`site.com/for/{target}/{city}\` or \`site.com/{locale}/{service}/{city}\`.
|
|
4026
|
+
- ❌ **Never 5 to 6 Segments:** Avoid \`/solutions/ai/autopost/facebook/fr/lyon\`.
|
|
4027
|
+
- **Rule B (Eliminate All Parasite Noise Words):**
|
|
4028
|
+
- Banish generic wrapper words like \`/solutions/\`, \`/pages/\`, \`/items/\`. Go direct to user search intent: What you do + Platform + Location.
|
|
4029
|
+
- **Rule C (The SDK Does 100% of the Heavy Lifting):**
|
|
4030
|
+
- The developer/AI only registers 3-4 product features and target cities in \`lib/seo.ts\`.
|
|
4031
|
+
- The SDK automatically resolves routes in memory via \`matrixEngine.resolvePage(params.slug)\`, generates complete Schema.org graphs (LocalBusiness, AggregateRating, AggregateOffer), creates AEO summaries, builds FAQ accordions, and manages mesh linking in < 0.05ms.
|
|
4032
|
+
|
|
4033
|
+
---
|
|
4034
|
+
|
|
4035
|
+
### \uD83C\uDFC6 4. THE ARCHITECTURAL PLAYBOOK OF SAAS TITANS (7M+ PAGES WITH 0 DB BOTTLENECKS)
|
|
3792
4036
|
|
|
3793
4037
|
How category leaders generate millions of high-converting pages without server saturation:
|
|
3794
4038
|
|
|
@@ -3800,18 +4044,18 @@ How category leaders generate millions of high-converting pages without server s
|
|
|
3800
4044
|
|
|
3801
4045
|
2. **The 4 Pillars of Zero-Latency (< 0.05ms) Resolution:**
|
|
3802
4046
|
- **0 Bytes in Database:** Never generate physical static files or query heavy databases per request. Pages are resolved mathematically in RAM upon arrival.
|
|
3803
|
-
- **1 Single Universal Catch-All Route:** \`app/[...slug]/page.tsx\`
|
|
3804
|
-
- **Edge Caching & ISR:** The rendered HTML is instantly cached at the CDN/Edge
|
|
4047
|
+
- **1 Single Universal Catch-All Route:** \`app/[...slug]/page.tsx\` handles all matrices through one lightweight layout.
|
|
4048
|
+
- **Edge Caching & ISR:** The rendered HTML is instantly cached at the CDN/Edge on first request.
|
|
3805
4049
|
- **Automatic Siloing & Mesh Linking:**
|
|
3806
4050
|
- Links to 3 neighboring geographic cities for the same service.
|
|
3807
4051
|
- Links to 4 related professions/industries in the same target category.
|
|
3808
4052
|
- Complete Schema.org BreadcrumbList navigation.
|
|
3809
|
-
- **Sharded XML Sitemaps:** Split into 50,000 URL chunks with a master sitemap index
|
|
4053
|
+
- **Sharded XML Sitemaps:** Split into 50,000 URL chunks with a master sitemap index.
|
|
3810
4054
|
- **Dynamic \`/llms.txt\` Feed:** High-density Markdown stream enabling direct indexing by ChatGPT Search & Perplexity.
|
|
3811
4055
|
|
|
3812
4056
|
---
|
|
3813
4057
|
|
|
3814
|
-
### \uD83D\uDEE0️
|
|
4058
|
+
### \uD83D\uDEE0️ 5. AI AGENT IMPLEMENTATION PROTOCOL (STEP-BY-STEP)
|
|
3815
4059
|
|
|
3816
4060
|
When setting up programmatic SEO in any codebase, the AI Agent MUST generate these exact 4 files:
|
|
3817
4061
|
|
|
@@ -3824,6 +4068,7 @@ export const matrixEngine = new PseoMatrixEngine();
|
|
|
3824
4068
|
export const SEO_CONFIG = {
|
|
3825
4069
|
brandName: "Acme",
|
|
3826
4070
|
language: "en",
|
|
4071
|
+
cleanDirectRoutes: true, // Eliminates parasite words (/solutions/, etc.)
|
|
3827
4072
|
minPopulationToIndex: 15_000,
|
|
3828
4073
|
};
|
|
3829
4074
|
|
|
@@ -3831,19 +4076,23 @@ export const SEO_CONFIG = {
|
|
|
3831
4076
|
export const PSEO_DATASET = {
|
|
3832
4077
|
services: [
|
|
3833
4078
|
// 1 per major frontend feature branch
|
|
3834
|
-
{ slug: "
|
|
4079
|
+
{ slug: "autopost-facebook", name: "Autopost Facebook & Instagram", category: "Social Media", keyFeatures: ["AI Scheduling", "Visual Calendar", "Auto-Hashtags"], description: "Automated social media posting engine" },
|
|
4080
|
+
],
|
|
4081
|
+
locations: [
|
|
4082
|
+
{ slug: "paris", name: "Paris", region: "Île-de-France", country: "France", population: 2_161_000, currency: "EUR", currencySymbol: "€" },
|
|
4083
|
+
{ slug: "lyon", name: "Lyon", region: "Rhône-Alpes", country: "France", population: 522_000, currency: "EUR", currencySymbol: "€" },
|
|
3835
4084
|
],
|
|
3836
4085
|
targets: [
|
|
3837
|
-
{ slug: "
|
|
4086
|
+
{ slug: "agences", name: "Marketing Agencies", type: "industry", painPoints: ["Manual Posting"], benefits: ["10x Output"] },
|
|
3838
4087
|
],
|
|
3839
4088
|
integrations: [
|
|
3840
|
-
{ slug: "shopify", name: "Shopify", category: "E-Commerce", syncFeatures: ["
|
|
4089
|
+
{ slug: "shopify", name: "Shopify", category: "E-Commerce", syncFeatures: ["Catalog sync"] },
|
|
3841
4090
|
],
|
|
3842
4091
|
templates: [
|
|
3843
|
-
{ slug: "
|
|
4092
|
+
{ slug: "social-calendar-excel", title: "Social Media Calendar", topic: "Social Media", format: "excel", benefits: ["Instant setup"] },
|
|
3844
4093
|
],
|
|
3845
4094
|
glossaryTerms: [
|
|
3846
|
-
{ slug: "
|
|
4095
|
+
{ slug: "engagement-rate", term: "Engagement Rate", shortDefinition: "Percentage of audience interacting with content" },
|
|
3847
4096
|
],
|
|
3848
4097
|
};
|
|
3849
4098
|
\`\`\`
|
|
@@ -3856,19 +4105,15 @@ import { matrixEngine, SEO_CONFIG, PSEO_DATASET } from "@/lib/seo";
|
|
|
3856
4105
|
import { renderBrandIconSvg } from "@lynxflow/seo-engine";
|
|
3857
4106
|
|
|
3858
4107
|
export async function generateMetadata({ params }: { params: { slug: string[] } }) {
|
|
3859
|
-
const path = "/" + params.slug.join("/");
|
|
3860
4108
|
const domain = process.env.NEXT_PUBLIC_SITE_URL || "https://example.com";
|
|
3861
|
-
const
|
|
3862
|
-
const page = pages.find((p) => p.urlPath === path);
|
|
4109
|
+
const page = matrixEngine.resolvePage(params.slug, domain, PSEO_DATASET, SEO_CONFIG);
|
|
3863
4110
|
if (!page) return {};
|
|
3864
4111
|
return { title: page.title, description: page.description, alternates: { canonical: page.canonicalUrl }, robots: page.robots };
|
|
3865
4112
|
}
|
|
3866
4113
|
|
|
3867
4114
|
export default async function ProgrammaticPage({ params }: { params: { slug: string[] } }) {
|
|
3868
|
-
const path = "/" + params.slug.join("/");
|
|
3869
4115
|
const domain = process.env.NEXT_PUBLIC_SITE_URL || "https://example.com";
|
|
3870
|
-
const
|
|
3871
|
-
const page = pages.find((p) => p.urlPath === path);
|
|
4116
|
+
const page = matrixEngine.resolvePage(params.slug, domain, PSEO_DATASET, SEO_CONFIG);
|
|
3872
4117
|
if (!page) notFound();
|
|
3873
4118
|
|
|
3874
4119
|
// Automatic Brand Icon Detection
|