@lynxflow/seo-engine 2.0.1 → 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 CHANGED
@@ -4,7 +4,7 @@ High-Performance Universal Programmatic SEO & Structured Data Engine for Modern
4
4
 
5
5
  [![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue.svg)](https://www.typescriptlang.org/)
6
6
  [![License](https://img.shields.io/badge/License-Proprietary-green.svg)](LICENSE)
7
- [![Version](https://img.shields.io/badge/Version-2.0.0-orange.svg)](https://www.npmjs.com/package/@lynxflow/seo-engine)
7
+ [![Version](https://img.shields.io/badge/Version-2.1.1-orange.svg)](https://www.npmjs.com/package/@lynxflow/seo-engine)
8
8
 
9
9
  ---
10
10
 
@@ -0,0 +1,70 @@
1
+ /**
2
+ * 📊 Page Generation & Multi-Cycle Regeneration Tracker
3
+ *
4
+ * Tracks:
5
+ * 1. Unique Live Programmatic Pages in Index
6
+ * 2. Total Generation Cycles (Initial Creation + 2x, 3x+ Re-Optimizations)
7
+ * 3. Execution Mode: 'standard' (Deterministic 0€, 0 Token) vs 'ai-enhanced' (LLM Custom Copy with Token Billing)
8
+ * 4. Market Demand Signals & Emerging Country Opportunities (e.g. Brazil, US, Spain)
9
+ */
10
+ export interface PageGenerationRecord {
11
+ urlPath: string;
12
+ service: string;
13
+ location: string;
14
+ country: string;
15
+ language: string;
16
+ generationCount: number;
17
+ mode: "standard" | "ai-enhanced";
18
+ tokensConsumed: number;
19
+ firstGeneratedAt: string;
20
+ lastGeneratedAt: string;
21
+ }
22
+ export interface MarketOpportunitySignal {
23
+ country: string;
24
+ countryCode: string;
25
+ detectedRequests: number;
26
+ activeIndexedPages: number;
27
+ coverageStatus: "optimal" | "underserved" | "opportunity_detected";
28
+ estimatedMonthlyTrafficUpside: number;
29
+ recommendation: string;
30
+ }
31
+ export interface GenerationMetricsReport {
32
+ uniquePagesCount: number;
33
+ totalGenerationCycles: number;
34
+ regenerationFrequency: {
35
+ singleGeneration: number;
36
+ doubleGeneration: number;
37
+ multiGeneration: number;
38
+ };
39
+ modeDistribution: {
40
+ standardDeterministicCount: number;
41
+ aiEnhancedCount: number;
42
+ };
43
+ totalAiTokensConsumed: number;
44
+ activeCountriesCount: number;
45
+ marketOpportunities: MarketOpportunitySignal[];
46
+ }
47
+ export declare class PageRegenerationTracker {
48
+ private records;
49
+ private demandSignals;
50
+ /**
51
+ * Records a page generation or regeneration event.
52
+ */
53
+ recordGeneration(params: {
54
+ urlPath: string;
55
+ service?: string;
56
+ location?: string;
57
+ country?: string;
58
+ language?: string;
59
+ mode?: "standard" | "ai-enhanced";
60
+ tokensConsumed?: number;
61
+ }): PageGenerationRecord;
62
+ /**
63
+ * Ingests external traffic or search demand signals (e.g. from Cloudflare / Nginx Edge / Google Search Console).
64
+ */
65
+ recordMarketDemand(country: string, requestVolume?: number): void;
66
+ /**
67
+ * Computes comprehensive analytics report for admin dashboards.
68
+ */
69
+ getMetricsReport(): GenerationMetricsReport;
70
+ }
package/dist/index.d.ts CHANGED
@@ -110,6 +110,7 @@ import { LynxRateLimitedTranslator } from "./rate-limited-translator";
110
110
  import { InternalPageRankEngine } from "./internal-pagerank-graph";
111
111
  import { GeoCitationScorer } from "./geo-citation-scorer";
112
112
  import { MarketingSkillsEngine } from "./marketing-skills-engine";
113
+ import { PageRegenerationTracker } from "./generation-tracker";
113
114
  export declare function createLynxSeoEngine(config: EngineConfig): LynxSeoEngine;
114
115
  export declare const LynxSeo: {
115
116
  createEngine: typeof createLynxSeoEngine;
@@ -157,7 +158,9 @@ export declare const LynxSeo: {
157
158
  pageRankGraph: typeof InternalPageRankEngine;
158
159
  geoCitation: typeof GeoCitationScorer;
159
160
  marketingSkills: typeof MarketingSkillsEngine;
161
+ regenerationTracker: typeof PageRegenerationTracker;
160
162
  };
163
+ export * from "./generation-tracker";
161
164
  export * from "./internal-pagerank-graph";
162
165
  export * from "./geo-citation-scorer";
163
166
  export * from "./marketing-skills-engine";
package/dist/index.js CHANGED
@@ -3787,7 +3787,7 @@ class PseoMatrixEngine {
3787
3787
  ])
3788
3788
  ]
3789
3789
  };
3790
- pages.push({
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.countries || options.territories || options.language || "fr");
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 pages = this.generateAllMatrices(domain, data, options);
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 >= 2 && data.services && data.services.length > 0) {
4221
- const serviceSlug = segments[0];
4222
- const locationSlug = segments[segments.length - 1];
4223
- const matchedService = data.services.find((s) => cleanSeoSlug(s.slug, { language: options.language }) === serviceSlug || s.slug === serviceSlug);
4224
- if (matchedService) {
4225
- const countryList = options.allowedCountries || options.countries;
4226
- const fallbackCountry = Array.isArray(countryList) ? countryList[0] : typeof countryList === "string" && countryList !== "international" ? countryList : "United States";
4227
- const dynamicLoc = createDynamicLocation(locationSlug, fallbackCountry, {
4228
- currency: options.defaultCurrency,
4229
- currencySymbol: options.defaultCurrencySymbol
4230
- });
4231
- if (options.strictCountryBoundary && countryList) {
4232
- const allowed = (Array.isArray(countryList) ? countryList : [countryList]).map((c) => c.toLowerCase());
4233
- if (!allowed.includes("international") && !allowed.some((c) => dynamicLoc.country.toLowerCase().includes(c))) {
4234
- return;
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
- ### \uD83C\uDF10 2. ARCHITECTURAL RULE: THE WEBSITE OWNS ITS LANGUAGES (i18n)
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 Gold Stars 4.9★), \`buildAggregateOffer\` (price ranges), \`buildLocalBusiness\` (GPS & opening hours), \`buildSoftwareApplication\`, \`buildHowTo\`, \`buildFAQPage\`, \`buildBreadcrumbs\`.
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).
@@ -9457,6 +9555,114 @@ class MarketingSkillsEngine {
9457
9555
  }
9458
9556
  }
9459
9557
 
9558
+ // src/generation-tracker.ts
9559
+ class PageRegenerationTracker {
9560
+ records = new Map;
9561
+ demandSignals = new Map;
9562
+ recordGeneration(params) {
9563
+ const key = params.urlPath.trim().toLowerCase();
9564
+ const existing = this.records.get(key);
9565
+ const now = new Date().toISOString();
9566
+ const country = params.country || existing?.country || "United States";
9567
+ const language = params.language || existing?.language || "en";
9568
+ const service = params.service || existing?.service || "General Service";
9569
+ const location = params.location || existing?.location || "National";
9570
+ const mode = params.mode || "standard";
9571
+ const tokens = params.tokensConsumed || 0;
9572
+ if (existing) {
9573
+ existing.generationCount += 1;
9574
+ existing.mode = mode;
9575
+ existing.tokensConsumed += tokens;
9576
+ existing.lastGeneratedAt = now;
9577
+ this.records.set(key, existing);
9578
+ return existing;
9579
+ }
9580
+ const newRecord = {
9581
+ urlPath: key,
9582
+ service,
9583
+ location,
9584
+ country,
9585
+ language,
9586
+ generationCount: 1,
9587
+ mode,
9588
+ tokensConsumed: tokens,
9589
+ firstGeneratedAt: now,
9590
+ lastGeneratedAt: now
9591
+ };
9592
+ this.records.set(key, newRecord);
9593
+ const countryKey = country.toLowerCase();
9594
+ const currentSignal = this.demandSignals.get(countryKey) || { requests: 0, country };
9595
+ currentSignal.requests += 1;
9596
+ this.demandSignals.set(countryKey, currentSignal);
9597
+ return newRecord;
9598
+ }
9599
+ recordMarketDemand(country, requestVolume = 1) {
9600
+ const countryKey = country.trim().toLowerCase();
9601
+ const current = this.demandSignals.get(countryKey) || { requests: 0, country };
9602
+ current.requests += requestVolume;
9603
+ this.demandSignals.set(countryKey, current);
9604
+ }
9605
+ getMetricsReport() {
9606
+ let singleGen = 0;
9607
+ let doubleGen = 0;
9608
+ let multiGen = 0;
9609
+ let standardCount = 0;
9610
+ let aiCount = 0;
9611
+ let totalTokens = 0;
9612
+ const countriesSet = new Set;
9613
+ const countryPageCounts = new Map;
9614
+ for (const record of this.records.values()) {
9615
+ if (record.generationCount === 1)
9616
+ singleGen++;
9617
+ else if (record.generationCount === 2)
9618
+ doubleGen++;
9619
+ else
9620
+ multiGen++;
9621
+ if (record.mode === "ai-enhanced") {
9622
+ aiCount++;
9623
+ totalTokens += record.tokensConsumed;
9624
+ } else {
9625
+ standardCount++;
9626
+ }
9627
+ countriesSet.add(record.country);
9628
+ const cKey = record.country.toLowerCase();
9629
+ countryPageCounts.set(cKey, (countryPageCounts.get(cKey) || 0) + 1);
9630
+ }
9631
+ const marketOpportunities = [];
9632
+ for (const [countryKey, signal] of this.demandSignals.entries()) {
9633
+ const indexedPages = countryPageCounts.get(countryKey) || 0;
9634
+ const countryFormatted = signal.country.charAt(0).toUpperCase() + signal.country.slice(1);
9635
+ if (signal.requests > 100 && indexedPages < 20) {
9636
+ marketOpportunities.push({
9637
+ country: countryFormatted,
9638
+ countryCode: countryKey.slice(0, 2).toUpperCase(),
9639
+ detectedRequests: signal.requests,
9640
+ activeIndexedPages: indexedPages,
9641
+ coverageStatus: "opportunity_detected",
9642
+ estimatedMonthlyTrafficUpside: Math.round(signal.requests * 3.4),
9643
+ recommendation: `High demand detected in ${countryFormatted} (${signal.requests} hits). Upgrade to Pro Growth or activate ${countryFormatted} in SEO_CONFIG to capture +${Math.round(signal.requests * 3.4)} visits/month.`
9644
+ });
9645
+ }
9646
+ }
9647
+ return {
9648
+ uniquePagesCount: this.records.size,
9649
+ totalGenerationCycles: singleGen + doubleGen * 2 + multiGen * 3,
9650
+ regenerationFrequency: {
9651
+ singleGeneration: singleGen,
9652
+ doubleGeneration: doubleGen,
9653
+ multiGeneration: multiGen
9654
+ },
9655
+ modeDistribution: {
9656
+ standardDeterministicCount: standardCount,
9657
+ aiEnhancedCount: aiCount
9658
+ },
9659
+ totalAiTokensConsumed: totalTokens,
9660
+ activeCountriesCount: countriesSet.size,
9661
+ marketOpportunities
9662
+ };
9663
+ }
9664
+ }
9665
+
9460
9666
  // src/index.ts
9461
9667
  function createLynxSeoEngine(config) {
9462
9668
  return new LynxSeoEngine(config);
@@ -9506,7 +9712,8 @@ var LynxSeo = {
9506
9712
  translator: LynxRateLimitedTranslator,
9507
9713
  pageRankGraph: InternalPageRankEngine,
9508
9714
  geoCitation: GeoCitationScorer,
9509
- marketingSkills: MarketingSkillsEngine
9715
+ marketingSkills: MarketingSkillsEngine,
9716
+ regenerationTracker: PageRegenerationTracker
9510
9717
  };
9511
9718
  var src_default = LynxSeo;
9512
9719
  export {
@@ -9546,6 +9753,7 @@ export {
9546
9753
  PublicRoutesManifestEngine,
9547
9754
  PseoMatrixEngine,
9548
9755
  PowerWordsPsychologyEngine,
9756
+ PageRegenerationTracker,
9549
9757
  PSEO_AGENT_SYSTEM_PROMPT,
9550
9758
  OgImageGenerator,
9551
9759
  NGramDensityAnalyzer,
package/dist/index.mjs CHANGED
@@ -3787,7 +3787,7 @@ class PseoMatrixEngine {
3787
3787
  ])
3788
3788
  ]
3789
3789
  };
3790
- pages.push({
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.countries || options.territories || options.language || "fr");
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 pages = this.generateAllMatrices(domain, data, options);
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 >= 2 && data.services && data.services.length > 0) {
4221
- const serviceSlug = segments[0];
4222
- const locationSlug = segments[segments.length - 1];
4223
- const matchedService = data.services.find((s) => cleanSeoSlug(s.slug, { language: options.language }) === serviceSlug || s.slug === serviceSlug);
4224
- if (matchedService) {
4225
- const countryList = options.allowedCountries || options.countries;
4226
- const fallbackCountry = Array.isArray(countryList) ? countryList[0] : typeof countryList === "string" && countryList !== "international" ? countryList : "United States";
4227
- const dynamicLoc = createDynamicLocation(locationSlug, fallbackCountry, {
4228
- currency: options.defaultCurrency,
4229
- currencySymbol: options.defaultCurrencySymbol
4230
- });
4231
- if (options.strictCountryBoundary && countryList) {
4232
- const allowed = (Array.isArray(countryList) ? countryList : [countryList]).map((c) => c.toLowerCase());
4233
- if (!allowed.includes("international") && !allowed.some((c) => dynamicLoc.country.toLowerCase().includes(c))) {
4234
- return;
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
- ### \uD83C\uDF10 2. ARCHITECTURAL RULE: THE WEBSITE OWNS ITS LANGUAGES (i18n)
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 Gold Stars 4.9★), \`buildAggregateOffer\` (price ranges), \`buildLocalBusiness\` (GPS & opening hours), \`buildSoftwareApplication\`, \`buildHowTo\`, \`buildFAQPage\`, \`buildBreadcrumbs\`.
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).
@@ -9457,6 +9555,114 @@ class MarketingSkillsEngine {
9457
9555
  }
9458
9556
  }
9459
9557
 
9558
+ // src/generation-tracker.ts
9559
+ class PageRegenerationTracker {
9560
+ records = new Map;
9561
+ demandSignals = new Map;
9562
+ recordGeneration(params) {
9563
+ const key = params.urlPath.trim().toLowerCase();
9564
+ const existing = this.records.get(key);
9565
+ const now = new Date().toISOString();
9566
+ const country = params.country || existing?.country || "United States";
9567
+ const language = params.language || existing?.language || "en";
9568
+ const service = params.service || existing?.service || "General Service";
9569
+ const location = params.location || existing?.location || "National";
9570
+ const mode = params.mode || "standard";
9571
+ const tokens = params.tokensConsumed || 0;
9572
+ if (existing) {
9573
+ existing.generationCount += 1;
9574
+ existing.mode = mode;
9575
+ existing.tokensConsumed += tokens;
9576
+ existing.lastGeneratedAt = now;
9577
+ this.records.set(key, existing);
9578
+ return existing;
9579
+ }
9580
+ const newRecord = {
9581
+ urlPath: key,
9582
+ service,
9583
+ location,
9584
+ country,
9585
+ language,
9586
+ generationCount: 1,
9587
+ mode,
9588
+ tokensConsumed: tokens,
9589
+ firstGeneratedAt: now,
9590
+ lastGeneratedAt: now
9591
+ };
9592
+ this.records.set(key, newRecord);
9593
+ const countryKey = country.toLowerCase();
9594
+ const currentSignal = this.demandSignals.get(countryKey) || { requests: 0, country };
9595
+ currentSignal.requests += 1;
9596
+ this.demandSignals.set(countryKey, currentSignal);
9597
+ return newRecord;
9598
+ }
9599
+ recordMarketDemand(country, requestVolume = 1) {
9600
+ const countryKey = country.trim().toLowerCase();
9601
+ const current = this.demandSignals.get(countryKey) || { requests: 0, country };
9602
+ current.requests += requestVolume;
9603
+ this.demandSignals.set(countryKey, current);
9604
+ }
9605
+ getMetricsReport() {
9606
+ let singleGen = 0;
9607
+ let doubleGen = 0;
9608
+ let multiGen = 0;
9609
+ let standardCount = 0;
9610
+ let aiCount = 0;
9611
+ let totalTokens = 0;
9612
+ const countriesSet = new Set;
9613
+ const countryPageCounts = new Map;
9614
+ for (const record of this.records.values()) {
9615
+ if (record.generationCount === 1)
9616
+ singleGen++;
9617
+ else if (record.generationCount === 2)
9618
+ doubleGen++;
9619
+ else
9620
+ multiGen++;
9621
+ if (record.mode === "ai-enhanced") {
9622
+ aiCount++;
9623
+ totalTokens += record.tokensConsumed;
9624
+ } else {
9625
+ standardCount++;
9626
+ }
9627
+ countriesSet.add(record.country);
9628
+ const cKey = record.country.toLowerCase();
9629
+ countryPageCounts.set(cKey, (countryPageCounts.get(cKey) || 0) + 1);
9630
+ }
9631
+ const marketOpportunities = [];
9632
+ for (const [countryKey, signal] of this.demandSignals.entries()) {
9633
+ const indexedPages = countryPageCounts.get(countryKey) || 0;
9634
+ const countryFormatted = signal.country.charAt(0).toUpperCase() + signal.country.slice(1);
9635
+ if (signal.requests > 100 && indexedPages < 20) {
9636
+ marketOpportunities.push({
9637
+ country: countryFormatted,
9638
+ countryCode: countryKey.slice(0, 2).toUpperCase(),
9639
+ detectedRequests: signal.requests,
9640
+ activeIndexedPages: indexedPages,
9641
+ coverageStatus: "opportunity_detected",
9642
+ estimatedMonthlyTrafficUpside: Math.round(signal.requests * 3.4),
9643
+ recommendation: `High demand detected in ${countryFormatted} (${signal.requests} hits). Upgrade to Pro Growth or activate ${countryFormatted} in SEO_CONFIG to capture +${Math.round(signal.requests * 3.4)} visits/month.`
9644
+ });
9645
+ }
9646
+ }
9647
+ return {
9648
+ uniquePagesCount: this.records.size,
9649
+ totalGenerationCycles: singleGen + doubleGen * 2 + multiGen * 3,
9650
+ regenerationFrequency: {
9651
+ singleGeneration: singleGen,
9652
+ doubleGeneration: doubleGen,
9653
+ multiGeneration: multiGen
9654
+ },
9655
+ modeDistribution: {
9656
+ standardDeterministicCount: standardCount,
9657
+ aiEnhancedCount: aiCount
9658
+ },
9659
+ totalAiTokensConsumed: totalTokens,
9660
+ activeCountriesCount: countriesSet.size,
9661
+ marketOpportunities
9662
+ };
9663
+ }
9664
+ }
9665
+
9460
9666
  // src/index.ts
9461
9667
  function createLynxSeoEngine(config) {
9462
9668
  return new LynxSeoEngine(config);
@@ -9506,7 +9712,8 @@ var LynxSeo = {
9506
9712
  translator: LynxRateLimitedTranslator,
9507
9713
  pageRankGraph: InternalPageRankEngine,
9508
9714
  geoCitation: GeoCitationScorer,
9509
- marketingSkills: MarketingSkillsEngine
9715
+ marketingSkills: MarketingSkillsEngine,
9716
+ regenerationTracker: PageRegenerationTracker
9510
9717
  };
9511
9718
  var src_default = LynxSeo;
9512
9719
  export {
@@ -9546,6 +9753,7 @@ export {
9546
9753
  PublicRoutesManifestEngine,
9547
9754
  PseoMatrixEngine,
9548
9755
  PowerWordsPsychologyEngine,
9756
+ PageRegenerationTracker,
9549
9757
  PSEO_AGENT_SYSTEM_PROMPT,
9550
9758
  OgImageGenerator,
9551
9759
  NGramDensityAnalyzer,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lynxflow/seo-engine",
3
- "version": "2.0.1",
3
+ "version": "2.1.1",
4
4
  "description": "High-Performance Universal Programmatic SEO & AI Search Engine SDK",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",