@lynxflow/seo-engine 2.1.0 → 2.1.1
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 +1 -1
- package/dist/index.js +124 -26
- package/dist/index.mjs +124 -26
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ High-Performance Universal Programmatic SEO & Structured Data Engine for Modern
|
|
|
4
4
|
|
|
5
5
|
[](https://www.typescriptlang.org/)
|
|
6
6
|
[](LICENSE)
|
|
7
|
-
[](https://www.npmjs.com/package/@lynxflow/seo-engine)
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
package/dist/index.js
CHANGED
|
@@ -3787,7 +3787,7 @@ class PseoMatrixEngine {
|
|
|
3787
3787
|
])
|
|
3788
3788
|
]
|
|
3789
3789
|
};
|
|
3790
|
-
|
|
3790
|
+
const pageItem = {
|
|
3791
3791
|
matrixFamily: "local-geo",
|
|
3792
3792
|
urlPath,
|
|
3793
3793
|
canonicalUrl: fullUrl,
|
|
@@ -3799,7 +3799,9 @@ class PseoMatrixEngine {
|
|
|
3799
3799
|
neighboringLinks: neighborLinks,
|
|
3800
3800
|
service: s,
|
|
3801
3801
|
location: loc
|
|
3802
|
-
}
|
|
3802
|
+
};
|
|
3803
|
+
pageItem.ogImageUrl = OgImageGenerator.generateOgImageUrl(cleanDomain, pageItem, options.brandName);
|
|
3804
|
+
pages.push(pageItem);
|
|
3803
3805
|
}
|
|
3804
3806
|
}
|
|
3805
3807
|
return pages;
|
|
@@ -4145,7 +4147,7 @@ class PseoMatrixEngine {
|
|
|
4145
4147
|
generateAllMatrices(domain, data, options) {
|
|
4146
4148
|
const allPages = [];
|
|
4147
4149
|
if (data.services) {
|
|
4148
|
-
const locs = data.locations && data.locations.length > 0 ? data.locations : resolveBuiltInLocations(options.
|
|
4150
|
+
const locs = data.locations && data.locations.length > 0 ? data.locations : resolveBuiltInLocations(options.allowedCountries || options.countries || options.territories || "us");
|
|
4149
4151
|
allPages.push(...this.generateLocalGeoMatrix(domain, data.services, locs, options));
|
|
4150
4152
|
}
|
|
4151
4153
|
if (data.competitors) {
|
|
@@ -4212,30 +4214,113 @@ class PseoMatrixEngine {
|
|
|
4212
4214
|
}
|
|
4213
4215
|
resolvePage(slugOrPath, domain, data, options) {
|
|
4214
4216
|
const path = typeof slugOrPath === "string" ? slugOrPath.startsWith("/") ? slugOrPath : `/${slugOrPath}` : `/${slugOrPath.join("/")}`;
|
|
4215
|
-
const
|
|
4216
|
-
const existing = pages.find((p) => p.urlPath === path);
|
|
4217
|
-
if (existing)
|
|
4218
|
-
return existing;
|
|
4217
|
+
const lang = options.language || "en";
|
|
4219
4218
|
const segments = path.split("/").filter(Boolean);
|
|
4220
|
-
if (segments.length
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
|
|
4228
|
-
|
|
4229
|
-
|
|
4230
|
-
|
|
4231
|
-
|
|
4232
|
-
|
|
4233
|
-
|
|
4234
|
-
|
|
4219
|
+
if (segments.length === 0)
|
|
4220
|
+
return;
|
|
4221
|
+
const vsPrefix = this.getPrefix("vs", lang).replace(/^\//, "");
|
|
4222
|
+
if (segments[0] === vsPrefix && segments[1] && data.competitors) {
|
|
4223
|
+
const compSlug = segments[1];
|
|
4224
|
+
const matched = data.competitors.find((c) => cleanSeoSlug(c.slug, { language: lang }) === compSlug || c.slug === compSlug);
|
|
4225
|
+
if (matched) {
|
|
4226
|
+
return this.generateVsMatrix(domain, [matched], options)[0];
|
|
4227
|
+
}
|
|
4228
|
+
}
|
|
4229
|
+
const altPrefix = this.getPrefix("alternative", lang).replace(/^\//, "");
|
|
4230
|
+
if (segments[0] === altPrefix && segments[1] && data.competitors) {
|
|
4231
|
+
const compSlug = segments[1];
|
|
4232
|
+
const matched = data.competitors.find((c) => cleanSeoSlug(c.slug, { language: lang }) === compSlug || c.slug === compSlug);
|
|
4233
|
+
if (matched) {
|
|
4234
|
+
return this.generateAlternativesMatrix(domain, [matched], options)[0];
|
|
4235
|
+
}
|
|
4236
|
+
}
|
|
4237
|
+
const pricingPrefix = this.getPrefix("pricing", lang).replace(/^\//, "");
|
|
4238
|
+
if (segments[0] === pricingPrefix && segments[1] && data.competitors) {
|
|
4239
|
+
const compSlug = segments[1];
|
|
4240
|
+
const matched = data.competitors.find((c) => cleanSeoSlug(c.slug, { language: lang }) === compSlug || c.slug === compSlug);
|
|
4241
|
+
if (matched) {
|
|
4242
|
+
return this.generatePricingMatrix(domain, [matched], options)[0];
|
|
4243
|
+
}
|
|
4244
|
+
}
|
|
4245
|
+
const targetPrefix = this.getPrefix("target", lang).replace(/^\//, "");
|
|
4246
|
+
if (segments[0] === targetPrefix && segments[1] && data.targets) {
|
|
4247
|
+
const targetSlug = segments[1];
|
|
4248
|
+
const matched = data.targets.find((t) => cleanSeoSlug(t.slug, { language: lang }) === targetSlug || t.slug === targetSlug);
|
|
4249
|
+
if (matched) {
|
|
4250
|
+
return this.generateTargetMatrix(domain, [matched], options)[0];
|
|
4251
|
+
}
|
|
4252
|
+
}
|
|
4253
|
+
const integPrefix = this.getPrefix("integration", lang).replace(/^\//, "");
|
|
4254
|
+
if (segments[0] === integPrefix && segments[1] && data.integrations) {
|
|
4255
|
+
const integSlug = segments[1];
|
|
4256
|
+
const matched = data.integrations.find((i) => cleanSeoSlug(i.slug, { language: lang }) === integSlug || i.slug === integSlug);
|
|
4257
|
+
if (matched) {
|
|
4258
|
+
return this.generateIntegrationMatrix(domain, [matched], options)[0];
|
|
4259
|
+
}
|
|
4260
|
+
}
|
|
4261
|
+
const useCasePrefix = this.getPrefix("useCase", lang).replace(/^\//, "");
|
|
4262
|
+
if (segments[0] === useCasePrefix && segments[1] && data.useCases) {
|
|
4263
|
+
const ucSlug = segments[1];
|
|
4264
|
+
const matched = data.useCases.find((u) => cleanSeoSlug(u.slug, { language: lang }) === ucSlug || u.slug === ucSlug);
|
|
4265
|
+
if (matched) {
|
|
4266
|
+
return this.generateUseCaseMatrix(domain, [matched], options)[0];
|
|
4267
|
+
}
|
|
4268
|
+
}
|
|
4269
|
+
const templatePrefix = this.getPrefix("template", lang).replace(/^\//, "");
|
|
4270
|
+
if (segments[0] === templatePrefix && segments[1] && data.templates) {
|
|
4271
|
+
const tSlug = segments[1];
|
|
4272
|
+
const matched = data.templates.find((t) => cleanSeoSlug(t.slug, { language: lang }) === tSlug || t.slug === tSlug);
|
|
4273
|
+
if (matched) {
|
|
4274
|
+
return this.generateTemplateMatrix(domain, [matched], options)[0];
|
|
4275
|
+
}
|
|
4276
|
+
}
|
|
4277
|
+
const glossaryPrefix = this.getPrefix("glossary", lang).replace(/^\//, "");
|
|
4278
|
+
if (segments[0] === glossaryPrefix && segments[1] && data.glossaryTerms) {
|
|
4279
|
+
const gSlug = segments[1];
|
|
4280
|
+
const matched = data.glossaryTerms.find((g) => cleanSeoSlug(g.slug, { language: lang }) === gSlug || g.slug === gSlug);
|
|
4281
|
+
if (matched) {
|
|
4282
|
+
return this.generateGlossaryMatrix(domain, [matched], options)[0];
|
|
4283
|
+
}
|
|
4284
|
+
}
|
|
4285
|
+
const calcPrefix = this.getPrefix("calculator", lang).replace(/^\//, "");
|
|
4286
|
+
if (segments[0] === calcPrefix && segments[1] && data.calculators) {
|
|
4287
|
+
const cSlug = segments[1];
|
|
4288
|
+
const matched = data.calculators.find((c) => cleanSeoSlug(c.slug, { language: lang }) === cSlug || c.slug === cSlug);
|
|
4289
|
+
if (matched) {
|
|
4290
|
+
return this.generateCalculatorMatrix(domain, [matched], options)[0];
|
|
4291
|
+
}
|
|
4292
|
+
}
|
|
4293
|
+
if (data.services && data.services.length > 0) {
|
|
4294
|
+
let serviceSlug = "";
|
|
4295
|
+
let locationSlug = "";
|
|
4296
|
+
if (segments.length === 2 && options.cleanDirectRoutes !== false) {
|
|
4297
|
+
serviceSlug = segments[0];
|
|
4298
|
+
locationSlug = segments[1];
|
|
4299
|
+
} else if (segments.length >= 3 && segments[0] === this.getPrefix("solutions", lang).replace(/^\//, "")) {
|
|
4300
|
+
serviceSlug = segments[1];
|
|
4301
|
+
locationSlug = segments[segments.length - 1];
|
|
4302
|
+
}
|
|
4303
|
+
if (serviceSlug && locationSlug) {
|
|
4304
|
+
const matchedService = data.services.find((s) => cleanSeoSlug(s.slug, { language: lang }) === serviceSlug || s.slug === serviceSlug);
|
|
4305
|
+
if (matchedService) {
|
|
4306
|
+
let resolvedLoc = (data.locations || []).find((l) => cleanSeoSlug(l.slug, { language: lang }) === locationSlug || l.slug === locationSlug);
|
|
4307
|
+
if (!resolvedLoc) {
|
|
4308
|
+
const countryList = options.allowedCountries || options.countries;
|
|
4309
|
+
const fallbackCountry = Array.isArray(countryList) ? countryList[0] : typeof countryList === "string" && countryList !== "international" ? countryList : "United States";
|
|
4310
|
+
resolvedLoc = createDynamicLocation(locationSlug, fallbackCountry, {
|
|
4311
|
+
currency: options.defaultCurrency,
|
|
4312
|
+
currencySymbol: options.defaultCurrencySymbol
|
|
4313
|
+
});
|
|
4314
|
+
if (options.strictCountryBoundary && countryList) {
|
|
4315
|
+
const allowed = (Array.isArray(countryList) ? countryList : [countryList]).map((c) => c.toLowerCase());
|
|
4316
|
+
if (!allowed.includes("international") && !allowed.some((c) => resolvedLoc.country.toLowerCase().includes(c))) {
|
|
4317
|
+
return;
|
|
4318
|
+
}
|
|
4319
|
+
}
|
|
4235
4320
|
}
|
|
4321
|
+
const dynamicPages = this.generateLocalGeoMatrix(domain, [matchedService], [resolvedLoc], options);
|
|
4322
|
+
return dynamicPages[0];
|
|
4236
4323
|
}
|
|
4237
|
-
const dynamicPages = this.generateLocalGeoMatrix(domain, [matchedService], [dynamicLoc], options);
|
|
4238
|
-
return dynamicPages.find((p) => p.urlPath === path) || dynamicPages[0];
|
|
4239
4324
|
}
|
|
4240
4325
|
}
|
|
4241
4326
|
return;
|
|
@@ -5045,7 +5130,20 @@ When discovering product modules, features, or services to build the SEO matrice
|
|
|
5045
5130
|
|
|
5046
5131
|
---
|
|
5047
5132
|
|
|
5048
|
-
### \
|
|
5133
|
+
### \uD83D\uDEA8 2. CRITICAL RULE: ZERO STATIC PRE-COMPILATION AT BUILD TIME (PURE RUNTIME ON-DEMAND)
|
|
5134
|
+
|
|
5135
|
+
- **❌ NEVER PRE-COMPILE CITIES OR PAGES AT BUILD TIME (\`generateStaticParams\` / \`getStaticPaths\`):**
|
|
5136
|
+
- DO NOT create static loops over 365, 61, or any hardcoded list of cities during \`next build\`.
|
|
5137
|
+
- DO NOT freeze pages into static HTML files on disk. That is NOT how high-scale programmatic SEO works.
|
|
5138
|
+
- Doing so breaks infinite dynamic expansion, causes build memory crashes, and falsely caps the website to a tiny subset of pages.
|
|
5139
|
+
- **✅ ALWAYS USE 100% RUNTIME IN-MEMORY RESOLUTION (\`resolvePage\`):**
|
|
5140
|
+
- In Next.js App Router: Always set \`export const dynamic = "force-dynamic"\` and \`export const dynamicParams = true\`.
|
|
5141
|
+
- When an incoming request arrives (e.g. \`/solutions/ac-repair/austin\`, \`/solutions/ac-repair/paris\`, \`/solutions/ac-repair/90210\`), the server executes \`matrixEngine.resolvePage([service, location], ...)\` in RAM in \`< 0.05ms\`.
|
|
5142
|
+
- Build time remains **under 3 seconds** because 0 localized landing pages are built ahead of time. Every page is synthesized on demand.
|
|
5143
|
+
|
|
5144
|
+
---
|
|
5145
|
+
|
|
5146
|
+
### \uD83C\uDF10 3. ARCHITECTURAL RULE: THE WEBSITE OWNS ITS LANGUAGES (i18n)
|
|
5049
5147
|
|
|
5050
5148
|
- **The Website (Application) is the sole owner of its languages, routes, and translations:**
|
|
5051
5149
|
- The website provides its own localized routes (e.g. \`app/[locale]/[...slug]/page.tsx\`) and dictionary files (\`next-intl\`, \`i18next\`, Astro i18n).
|
|
@@ -5308,7 +5406,7 @@ The SDK equips applications with 9 enterprise-grade SEO engines:
|
|
|
5308
5406
|
- Boosts dwell time, organic backlinks, and visitor-to-customer conversion.
|
|
5309
5407
|
|
|
5310
5408
|
8. **\uD83C\uDFDB️ Google Schema.org Rich Graphs (\`ExtendedSchemaGraphBuilder\`):**
|
|
5311
|
-
- \`buildAggregateRating\` (Google
|
|
5409
|
+
- \`buildAggregateRating\` (Verified live reviews synced from Google Places / Trustpilot / WooCommerce), \`buildAggregateOffer\` (price ranges), \`buildLocalBusiness\` (GPS & opening hours), \`buildSoftwareApplication\`, \`buildHowTo\`, \`buildFAQPage\`, \`buildBreadcrumbs\`.
|
|
5312
5410
|
|
|
5313
5411
|
9. **\uD83D\uDD0D URL Decomposition & Analysis (\`UrlyticsEngine\`):**
|
|
5314
5412
|
- Deep structural directory decomposition, depth tracking, query parameter analysis, and slug tokenization (inspired by advertools).
|
package/dist/index.mjs
CHANGED
|
@@ -3787,7 +3787,7 @@ class PseoMatrixEngine {
|
|
|
3787
3787
|
])
|
|
3788
3788
|
]
|
|
3789
3789
|
};
|
|
3790
|
-
|
|
3790
|
+
const pageItem = {
|
|
3791
3791
|
matrixFamily: "local-geo",
|
|
3792
3792
|
urlPath,
|
|
3793
3793
|
canonicalUrl: fullUrl,
|
|
@@ -3799,7 +3799,9 @@ class PseoMatrixEngine {
|
|
|
3799
3799
|
neighboringLinks: neighborLinks,
|
|
3800
3800
|
service: s,
|
|
3801
3801
|
location: loc
|
|
3802
|
-
}
|
|
3802
|
+
};
|
|
3803
|
+
pageItem.ogImageUrl = OgImageGenerator.generateOgImageUrl(cleanDomain, pageItem, options.brandName);
|
|
3804
|
+
pages.push(pageItem);
|
|
3803
3805
|
}
|
|
3804
3806
|
}
|
|
3805
3807
|
return pages;
|
|
@@ -4145,7 +4147,7 @@ class PseoMatrixEngine {
|
|
|
4145
4147
|
generateAllMatrices(domain, data, options) {
|
|
4146
4148
|
const allPages = [];
|
|
4147
4149
|
if (data.services) {
|
|
4148
|
-
const locs = data.locations && data.locations.length > 0 ? data.locations : resolveBuiltInLocations(options.
|
|
4150
|
+
const locs = data.locations && data.locations.length > 0 ? data.locations : resolveBuiltInLocations(options.allowedCountries || options.countries || options.territories || "us");
|
|
4149
4151
|
allPages.push(...this.generateLocalGeoMatrix(domain, data.services, locs, options));
|
|
4150
4152
|
}
|
|
4151
4153
|
if (data.competitors) {
|
|
@@ -4212,30 +4214,113 @@ class PseoMatrixEngine {
|
|
|
4212
4214
|
}
|
|
4213
4215
|
resolvePage(slugOrPath, domain, data, options) {
|
|
4214
4216
|
const path = typeof slugOrPath === "string" ? slugOrPath.startsWith("/") ? slugOrPath : `/${slugOrPath}` : `/${slugOrPath.join("/")}`;
|
|
4215
|
-
const
|
|
4216
|
-
const existing = pages.find((p) => p.urlPath === path);
|
|
4217
|
-
if (existing)
|
|
4218
|
-
return existing;
|
|
4217
|
+
const lang = options.language || "en";
|
|
4219
4218
|
const segments = path.split("/").filter(Boolean);
|
|
4220
|
-
if (segments.length
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
|
|
4228
|
-
|
|
4229
|
-
|
|
4230
|
-
|
|
4231
|
-
|
|
4232
|
-
|
|
4233
|
-
|
|
4234
|
-
|
|
4219
|
+
if (segments.length === 0)
|
|
4220
|
+
return;
|
|
4221
|
+
const vsPrefix = this.getPrefix("vs", lang).replace(/^\//, "");
|
|
4222
|
+
if (segments[0] === vsPrefix && segments[1] && data.competitors) {
|
|
4223
|
+
const compSlug = segments[1];
|
|
4224
|
+
const matched = data.competitors.find((c) => cleanSeoSlug(c.slug, { language: lang }) === compSlug || c.slug === compSlug);
|
|
4225
|
+
if (matched) {
|
|
4226
|
+
return this.generateVsMatrix(domain, [matched], options)[0];
|
|
4227
|
+
}
|
|
4228
|
+
}
|
|
4229
|
+
const altPrefix = this.getPrefix("alternative", lang).replace(/^\//, "");
|
|
4230
|
+
if (segments[0] === altPrefix && segments[1] && data.competitors) {
|
|
4231
|
+
const compSlug = segments[1];
|
|
4232
|
+
const matched = data.competitors.find((c) => cleanSeoSlug(c.slug, { language: lang }) === compSlug || c.slug === compSlug);
|
|
4233
|
+
if (matched) {
|
|
4234
|
+
return this.generateAlternativesMatrix(domain, [matched], options)[0];
|
|
4235
|
+
}
|
|
4236
|
+
}
|
|
4237
|
+
const pricingPrefix = this.getPrefix("pricing", lang).replace(/^\//, "");
|
|
4238
|
+
if (segments[0] === pricingPrefix && segments[1] && data.competitors) {
|
|
4239
|
+
const compSlug = segments[1];
|
|
4240
|
+
const matched = data.competitors.find((c) => cleanSeoSlug(c.slug, { language: lang }) === compSlug || c.slug === compSlug);
|
|
4241
|
+
if (matched) {
|
|
4242
|
+
return this.generatePricingMatrix(domain, [matched], options)[0];
|
|
4243
|
+
}
|
|
4244
|
+
}
|
|
4245
|
+
const targetPrefix = this.getPrefix("target", lang).replace(/^\//, "");
|
|
4246
|
+
if (segments[0] === targetPrefix && segments[1] && data.targets) {
|
|
4247
|
+
const targetSlug = segments[1];
|
|
4248
|
+
const matched = data.targets.find((t) => cleanSeoSlug(t.slug, { language: lang }) === targetSlug || t.slug === targetSlug);
|
|
4249
|
+
if (matched) {
|
|
4250
|
+
return this.generateTargetMatrix(domain, [matched], options)[0];
|
|
4251
|
+
}
|
|
4252
|
+
}
|
|
4253
|
+
const integPrefix = this.getPrefix("integration", lang).replace(/^\//, "");
|
|
4254
|
+
if (segments[0] === integPrefix && segments[1] && data.integrations) {
|
|
4255
|
+
const integSlug = segments[1];
|
|
4256
|
+
const matched = data.integrations.find((i) => cleanSeoSlug(i.slug, { language: lang }) === integSlug || i.slug === integSlug);
|
|
4257
|
+
if (matched) {
|
|
4258
|
+
return this.generateIntegrationMatrix(domain, [matched], options)[0];
|
|
4259
|
+
}
|
|
4260
|
+
}
|
|
4261
|
+
const useCasePrefix = this.getPrefix("useCase", lang).replace(/^\//, "");
|
|
4262
|
+
if (segments[0] === useCasePrefix && segments[1] && data.useCases) {
|
|
4263
|
+
const ucSlug = segments[1];
|
|
4264
|
+
const matched = data.useCases.find((u) => cleanSeoSlug(u.slug, { language: lang }) === ucSlug || u.slug === ucSlug);
|
|
4265
|
+
if (matched) {
|
|
4266
|
+
return this.generateUseCaseMatrix(domain, [matched], options)[0];
|
|
4267
|
+
}
|
|
4268
|
+
}
|
|
4269
|
+
const templatePrefix = this.getPrefix("template", lang).replace(/^\//, "");
|
|
4270
|
+
if (segments[0] === templatePrefix && segments[1] && data.templates) {
|
|
4271
|
+
const tSlug = segments[1];
|
|
4272
|
+
const matched = data.templates.find((t) => cleanSeoSlug(t.slug, { language: lang }) === tSlug || t.slug === tSlug);
|
|
4273
|
+
if (matched) {
|
|
4274
|
+
return this.generateTemplateMatrix(domain, [matched], options)[0];
|
|
4275
|
+
}
|
|
4276
|
+
}
|
|
4277
|
+
const glossaryPrefix = this.getPrefix("glossary", lang).replace(/^\//, "");
|
|
4278
|
+
if (segments[0] === glossaryPrefix && segments[1] && data.glossaryTerms) {
|
|
4279
|
+
const gSlug = segments[1];
|
|
4280
|
+
const matched = data.glossaryTerms.find((g) => cleanSeoSlug(g.slug, { language: lang }) === gSlug || g.slug === gSlug);
|
|
4281
|
+
if (matched) {
|
|
4282
|
+
return this.generateGlossaryMatrix(domain, [matched], options)[0];
|
|
4283
|
+
}
|
|
4284
|
+
}
|
|
4285
|
+
const calcPrefix = this.getPrefix("calculator", lang).replace(/^\//, "");
|
|
4286
|
+
if (segments[0] === calcPrefix && segments[1] && data.calculators) {
|
|
4287
|
+
const cSlug = segments[1];
|
|
4288
|
+
const matched = data.calculators.find((c) => cleanSeoSlug(c.slug, { language: lang }) === cSlug || c.slug === cSlug);
|
|
4289
|
+
if (matched) {
|
|
4290
|
+
return this.generateCalculatorMatrix(domain, [matched], options)[0];
|
|
4291
|
+
}
|
|
4292
|
+
}
|
|
4293
|
+
if (data.services && data.services.length > 0) {
|
|
4294
|
+
let serviceSlug = "";
|
|
4295
|
+
let locationSlug = "";
|
|
4296
|
+
if (segments.length === 2 && options.cleanDirectRoutes !== false) {
|
|
4297
|
+
serviceSlug = segments[0];
|
|
4298
|
+
locationSlug = segments[1];
|
|
4299
|
+
} else if (segments.length >= 3 && segments[0] === this.getPrefix("solutions", lang).replace(/^\//, "")) {
|
|
4300
|
+
serviceSlug = segments[1];
|
|
4301
|
+
locationSlug = segments[segments.length - 1];
|
|
4302
|
+
}
|
|
4303
|
+
if (serviceSlug && locationSlug) {
|
|
4304
|
+
const matchedService = data.services.find((s) => cleanSeoSlug(s.slug, { language: lang }) === serviceSlug || s.slug === serviceSlug);
|
|
4305
|
+
if (matchedService) {
|
|
4306
|
+
let resolvedLoc = (data.locations || []).find((l) => cleanSeoSlug(l.slug, { language: lang }) === locationSlug || l.slug === locationSlug);
|
|
4307
|
+
if (!resolvedLoc) {
|
|
4308
|
+
const countryList = options.allowedCountries || options.countries;
|
|
4309
|
+
const fallbackCountry = Array.isArray(countryList) ? countryList[0] : typeof countryList === "string" && countryList !== "international" ? countryList : "United States";
|
|
4310
|
+
resolvedLoc = createDynamicLocation(locationSlug, fallbackCountry, {
|
|
4311
|
+
currency: options.defaultCurrency,
|
|
4312
|
+
currencySymbol: options.defaultCurrencySymbol
|
|
4313
|
+
});
|
|
4314
|
+
if (options.strictCountryBoundary && countryList) {
|
|
4315
|
+
const allowed = (Array.isArray(countryList) ? countryList : [countryList]).map((c) => c.toLowerCase());
|
|
4316
|
+
if (!allowed.includes("international") && !allowed.some((c) => resolvedLoc.country.toLowerCase().includes(c))) {
|
|
4317
|
+
return;
|
|
4318
|
+
}
|
|
4319
|
+
}
|
|
4235
4320
|
}
|
|
4321
|
+
const dynamicPages = this.generateLocalGeoMatrix(domain, [matchedService], [resolvedLoc], options);
|
|
4322
|
+
return dynamicPages[0];
|
|
4236
4323
|
}
|
|
4237
|
-
const dynamicPages = this.generateLocalGeoMatrix(domain, [matchedService], [dynamicLoc], options);
|
|
4238
|
-
return dynamicPages.find((p) => p.urlPath === path) || dynamicPages[0];
|
|
4239
4324
|
}
|
|
4240
4325
|
}
|
|
4241
4326
|
return;
|
|
@@ -5045,7 +5130,20 @@ When discovering product modules, features, or services to build the SEO matrice
|
|
|
5045
5130
|
|
|
5046
5131
|
---
|
|
5047
5132
|
|
|
5048
|
-
### \
|
|
5133
|
+
### \uD83D\uDEA8 2. CRITICAL RULE: ZERO STATIC PRE-COMPILATION AT BUILD TIME (PURE RUNTIME ON-DEMAND)
|
|
5134
|
+
|
|
5135
|
+
- **❌ NEVER PRE-COMPILE CITIES OR PAGES AT BUILD TIME (\`generateStaticParams\` / \`getStaticPaths\`):**
|
|
5136
|
+
- DO NOT create static loops over 365, 61, or any hardcoded list of cities during \`next build\`.
|
|
5137
|
+
- DO NOT freeze pages into static HTML files on disk. That is NOT how high-scale programmatic SEO works.
|
|
5138
|
+
- Doing so breaks infinite dynamic expansion, causes build memory crashes, and falsely caps the website to a tiny subset of pages.
|
|
5139
|
+
- **✅ ALWAYS USE 100% RUNTIME IN-MEMORY RESOLUTION (\`resolvePage\`):**
|
|
5140
|
+
- In Next.js App Router: Always set \`export const dynamic = "force-dynamic"\` and \`export const dynamicParams = true\`.
|
|
5141
|
+
- When an incoming request arrives (e.g. \`/solutions/ac-repair/austin\`, \`/solutions/ac-repair/paris\`, \`/solutions/ac-repair/90210\`), the server executes \`matrixEngine.resolvePage([service, location], ...)\` in RAM in \`< 0.05ms\`.
|
|
5142
|
+
- Build time remains **under 3 seconds** because 0 localized landing pages are built ahead of time. Every page is synthesized on demand.
|
|
5143
|
+
|
|
5144
|
+
---
|
|
5145
|
+
|
|
5146
|
+
### \uD83C\uDF10 3. ARCHITECTURAL RULE: THE WEBSITE OWNS ITS LANGUAGES (i18n)
|
|
5049
5147
|
|
|
5050
5148
|
- **The Website (Application) is the sole owner of its languages, routes, and translations:**
|
|
5051
5149
|
- The website provides its own localized routes (e.g. \`app/[locale]/[...slug]/page.tsx\`) and dictionary files (\`next-intl\`, \`i18next\`, Astro i18n).
|
|
@@ -5308,7 +5406,7 @@ The SDK equips applications with 9 enterprise-grade SEO engines:
|
|
|
5308
5406
|
- Boosts dwell time, organic backlinks, and visitor-to-customer conversion.
|
|
5309
5407
|
|
|
5310
5408
|
8. **\uD83C\uDFDB️ Google Schema.org Rich Graphs (\`ExtendedSchemaGraphBuilder\`):**
|
|
5311
|
-
- \`buildAggregateRating\` (Google
|
|
5409
|
+
- \`buildAggregateRating\` (Verified live reviews synced from Google Places / Trustpilot / WooCommerce), \`buildAggregateOffer\` (price ranges), \`buildLocalBusiness\` (GPS & opening hours), \`buildSoftwareApplication\`, \`buildHowTo\`, \`buildFAQPage\`, \`buildBreadcrumbs\`.
|
|
5312
5410
|
|
|
5313
5411
|
9. **\uD83D\uDD0D URL Decomposition & Analysis (\`UrlyticsEngine\`):**
|
|
5314
5412
|
- Deep structural directory decomposition, depth tracking, query parameter analysis, and slug tokenization (inspired by advertools).
|