@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/src/index.ts CHANGED
@@ -15,6 +15,8 @@ export * from "./slug-engine";
15
15
  export * from "./legal-disclaimers";
16
16
  export * from "./matrix-engine";
17
17
  export * from "./brand-icons";
18
+ export * from "./urlytics-engine";
19
+ export * from "./keyword-permutator";
18
20
  export * from "./llm-prompt";
19
21
  export * from "./auth-key";
20
22
  export * from "./token-quota-manager";
@@ -0,0 +1,118 @@
1
+ /**
2
+ * ⚡ High-Speed Keyword Permutator & SEM Matrix Engine
3
+ * Inspired by advertools.kw_generate
4
+ *
5
+ * Generates all permutations and combinations of Products × Modifiers × Intents × Locations
6
+ * with match types (Broad, Phrase, Exact) and estimated search intent tagging.
7
+ */
8
+
9
+ export type SearchIntentType = "commercial" | "transactional" | "informational" | "navigational";
10
+
11
+ export interface GeneratedKeyword {
12
+ keyword: string;
13
+ exactMatch: string;
14
+ phraseMatch: string;
15
+ intent: SearchIntentType;
16
+ product: string;
17
+ modifier?: string;
18
+ location?: string;
19
+ }
20
+
21
+ export interface PermutatorOptions {
22
+ products: string[];
23
+ words?: string[];
24
+ locations?: string[];
25
+ maxCombinations?: number;
26
+ }
27
+
28
+ export class KeywordPermutatorEngine {
29
+ /**
30
+ * Generates a combinatorial matrix of keywords.
31
+ */
32
+ static generateKeywordMatrix(options: PermutatorOptions): GeneratedKeyword[] {
33
+ const { products, words = [], locations = [], maxCombinations = 5000 } = options;
34
+ const results: GeneratedKeyword[] = [];
35
+
36
+ const intentKeywords: Record<SearchIntentType, string[]> = {
37
+ transactional: ["buy", "pricing", "cost", "hire", "quote", "tarif", "prix", "devis", "acheter"],
38
+ commercial: ["best", "top", "review", "vs", "comparison", "alternative", "comparatif", "meilleur"],
39
+ informational: ["how to", "what is", "guide", "tutorial", "definition", "comment", "quest ce que"],
40
+ navigational: ["login", "app", "portal", "website", "connexion"],
41
+ };
42
+
43
+ const detectIntent = (text: string): SearchIntentType => {
44
+ const lower = text.toLowerCase();
45
+ for (const [intent, triggers] of Object.entries(intentKeywords) as [SearchIntentType, string[]][]) {
46
+ if (triggers.some((t) => lower.includes(t))) {
47
+ return intent;
48
+ }
49
+ }
50
+ return "commercial";
51
+ };
52
+
53
+ for (const prod of products) {
54
+ // 1. Product alone
55
+ results.push({
56
+ keyword: prod,
57
+ exactMatch: `[${prod}]`,
58
+ phraseMatch: `"${prod}"`,
59
+ intent: detectIntent(prod),
60
+ product: prod,
61
+ });
62
+
63
+ // 2. Product × Words / Modifiers
64
+ for (const word of words) {
65
+ const kw1 = `${word} ${prod}`;
66
+ const kw2 = `${prod} ${word}`;
67
+
68
+ results.push({
69
+ keyword: kw1,
70
+ exactMatch: `[${kw1}]`,
71
+ phraseMatch: `"${kw1}"`,
72
+ intent: detectIntent(word),
73
+ product: prod,
74
+ modifier: word,
75
+ });
76
+
77
+ results.push({
78
+ keyword: kw2,
79
+ exactMatch: `[${kw2}]`,
80
+ phraseMatch: `"${kw2}"`,
81
+ intent: detectIntent(word),
82
+ product: prod,
83
+ modifier: word,
84
+ });
85
+
86
+ // 3. Product × Words × Locations
87
+ for (const loc of locations) {
88
+ const kwLoc1 = `${word} ${prod} ${loc}`;
89
+ const kwLoc2 = `${prod} ${loc} ${word}`;
90
+
91
+ results.push({
92
+ keyword: kwLoc1,
93
+ exactMatch: `[${kwLoc1}]`,
94
+ phraseMatch: `"${kwLoc1}"`,
95
+ intent: detectIntent(word),
96
+ product: prod,
97
+ modifier: word,
98
+ location: loc,
99
+ });
100
+
101
+ results.push({
102
+ keyword: kwLoc2,
103
+ exactMatch: `[${kwLoc2}]`,
104
+ phraseMatch: `"${kwLoc2}"`,
105
+ intent: detectIntent(word),
106
+ product: prod,
107
+ modifier: word,
108
+ location: loc,
109
+ });
110
+
111
+ if (results.length >= maxCombinations) return results;
112
+ }
113
+ }
114
+ }
115
+
116
+ return results;
117
+ }
118
+ }
package/src/llm-prompt.ts CHANGED
@@ -37,7 +37,21 @@ When discovering product modules, features, or services to build the SEO matrice
37
37
 
38
38
  ---
39
39
 
40
- ### 🏆 3. THE ARCHITECTURAL PLAYBOOK OF SAAS TITANS (7M+ PAGES WITH 0 DB BOTTLENECKS)
40
+ ### 📏 3. THE GOLDEN RULES OF URL ARCHITECTURE (MAX 3-4 SEGMENTS, ZERO PARASITE WORDS)
41
+
42
+ - **Rule A (2 to 3 URL Segments Ideal, 4 Maximum):**
43
+ - ✅ **2 Segments (Top SEO Performance):** \`site.com/{service}/{city}\` (e.g. \`/autopost-facebook/lyon\`, \`/crm/paris\`).
44
+ - ✅ **3 Segments (B2B Persona/Hub):** \`site.com/for/{target}/{city}\` or \`site.com/{locale}/{service}/{city}\`.
45
+ - ❌ **Never 5 to 6 Segments:** Avoid \`/solutions/ai/autopost/facebook/fr/lyon\`.
46
+ - **Rule B (Eliminate All Parasite Noise Words):**
47
+ - Banish generic wrapper words like \`/solutions/\`, \`/pages/\`, \`/items/\`. Go direct to user search intent: What you do + Platform + Location.
48
+ - **Rule C (The SDK Does 100% of the Heavy Lifting):**
49
+ - The developer/AI only registers 3-4 product features and target cities in \`lib/seo.ts\`.
50
+ - 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.
51
+
52
+ ---
53
+
54
+ ### 🏆 4. THE ARCHITECTURAL PLAYBOOK OF SAAS TITANS (7M+ PAGES WITH 0 DB BOTTLENECKS)
41
55
 
42
56
  How category leaders generate millions of high-converting pages without server saturation:
43
57
 
@@ -49,18 +63,18 @@ How category leaders generate millions of high-converting pages without server s
49
63
 
50
64
  2. **The 4 Pillars of Zero-Latency (< 0.05ms) Resolution:**
51
65
  - **0 Bytes in Database:** Never generate physical static files or query heavy databases per request. Pages are resolved mathematically in RAM upon arrival.
52
- - **1 Single Universal Catch-All Route:** \`app/[...slug]/page.tsx\` (or \`app/[locale]/[...slug]/page.tsx\`) handles all matrices through one lightweight layout.
53
- - **Edge Caching & ISR:** The rendered HTML is instantly cached at the CDN/Edge (Cloudflare / Vercel Edge) on first request.
66
+ - **1 Single Universal Catch-All Route:** \`app/[...slug]/page.tsx\` handles all matrices through one lightweight layout.
67
+ - **Edge Caching & ISR:** The rendered HTML is instantly cached at the CDN/Edge on first request.
54
68
  - **Automatic Siloing & Mesh Linking:**
55
69
  - Links to 3 neighboring geographic cities for the same service.
56
70
  - Links to 4 related professions/industries in the same target category.
57
71
  - Complete Schema.org BreadcrumbList navigation.
58
- - **Sharded XML Sitemaps:** Split into 50,000 URL chunks with a master sitemap index to respect Google's crawl budget.
72
+ - **Sharded XML Sitemaps:** Split into 50,000 URL chunks with a master sitemap index.
59
73
  - **Dynamic \`/llms.txt\` Feed:** High-density Markdown stream enabling direct indexing by ChatGPT Search & Perplexity.
60
74
 
61
75
  ---
62
76
 
63
- ### 🛠️ 4. AI AGENT IMPLEMENTATION PROTOCOL (STEP-BY-STEP)
77
+ ### 🛠️ 5. AI AGENT IMPLEMENTATION PROTOCOL (STEP-BY-STEP)
64
78
 
65
79
  When setting up programmatic SEO in any codebase, the AI Agent MUST generate these exact 4 files:
66
80
 
@@ -73,6 +87,7 @@ export const matrixEngine = new PseoMatrixEngine();
73
87
  export const SEO_CONFIG = {
74
88
  brandName: "Acme",
75
89
  language: "en",
90
+ cleanDirectRoutes: true, // Eliminates parasite words (/solutions/, etc.)
76
91
  minPopulationToIndex: 15_000,
77
92
  };
78
93
 
@@ -80,19 +95,23 @@ export const SEO_CONFIG = {
80
95
  export const PSEO_DATASET = {
81
96
  services: [
82
97
  // 1 per major frontend feature branch
83
- { slug: "crm-pipeline", name: "Visual CRM Pipeline", category: "Sales", keyFeatures: ["Kanban", "Lead scoring"], description: "Visual deal tracking" },
98
+ { slug: "autopost-facebook", name: "Autopost Facebook & Instagram", category: "Social Media", keyFeatures: ["AI Scheduling", "Visual Calendar", "Auto-Hashtags"], description: "Automated social media posting engine" },
99
+ ],
100
+ locations: [
101
+ { slug: "paris", name: "Paris", region: "Île-de-France", country: "France", population: 2_161_000, currency: "EUR", currencySymbol: "€" },
102
+ { slug: "lyon", name: "Lyon", region: "Rhône-Alpes", country: "France", population: 522_000, currency: "EUR", currencySymbol: "€" },
84
103
  ],
85
104
  targets: [
86
- { slug: "law-firms", name: "Law Firms", type: "industry", painPoints: ["Billing"], benefits: ["Compliance"] },
105
+ { slug: "agences", name: "Marketing Agencies", type: "industry", painPoints: ["Manual Posting"], benefits: ["10x Output"] },
87
106
  ],
88
107
  integrations: [
89
- { slug: "shopify", name: "Shopify", category: "E-Commerce", syncFeatures: ["Order sync"] },
108
+ { slug: "shopify", name: "Shopify", category: "E-Commerce", syncFeatures: ["Catalog sync"] },
90
109
  ],
91
110
  templates: [
92
- { slug: "sales-tracker-excel", title: "Sales Tracker", topic: "Sales", format: "excel", benefits: ["Instant setup"] },
111
+ { slug: "social-calendar-excel", title: "Social Media Calendar", topic: "Social Media", format: "excel", benefits: ["Instant setup"] },
93
112
  ],
94
113
  glossaryTerms: [
95
- { slug: "mrr", term: "MRR", shortDefinition: "Monthly Recurring Revenue" },
114
+ { slug: "engagement-rate", term: "Engagement Rate", shortDefinition: "Percentage of audience interacting with content" },
96
115
  ],
97
116
  };
98
117
  \`\`\`
@@ -105,19 +124,15 @@ import { matrixEngine, SEO_CONFIG, PSEO_DATASET } from "@/lib/seo";
105
124
  import { renderBrandIconSvg } from "@lynxflow/seo-engine";
106
125
 
107
126
  export async function generateMetadata({ params }: { params: { slug: string[] } }) {
108
- const path = "/" + params.slug.join("/");
109
127
  const domain = process.env.NEXT_PUBLIC_SITE_URL || "https://example.com";
110
- const pages = matrixEngine.generateAllMatrices(domain, PSEO_DATASET, SEO_CONFIG);
111
- const page = pages.find((p) => p.urlPath === path);
128
+ const page = matrixEngine.resolvePage(params.slug, domain, PSEO_DATASET, SEO_CONFIG);
112
129
  if (!page) return {};
113
130
  return { title: page.title, description: page.description, alternates: { canonical: page.canonicalUrl }, robots: page.robots };
114
131
  }
115
132
 
116
133
  export default async function ProgrammaticPage({ params }: { params: { slug: string[] } }) {
117
- const path = "/" + params.slug.join("/");
118
134
  const domain = process.env.NEXT_PUBLIC_SITE_URL || "https://example.com";
119
- const pages = matrixEngine.generateAllMatrices(domain, PSEO_DATASET, SEO_CONFIG);
120
- const page = pages.find((p) => p.urlPath === path);
135
+ const page = matrixEngine.resolvePage(params.slug, domain, PSEO_DATASET, SEO_CONFIG);
121
136
  if (!page) notFound();
122
137
 
123
138
  // Automatic Brand Icon Detection
@@ -155,6 +155,7 @@ export interface GeneratedPageMeta {
155
155
  export interface MatrixOptions {
156
156
  brandName: string;
157
157
  language?: string; // Default: "en", supports "fr", "de", "es", "pt", "it", etc.
158
+ cleanDirectRoutes?: boolean; // When true (default), eliminates parasite words (/solutions/, etc.) for direct /{service}/{city}
158
159
  minPopulationToIndex?: number;
159
160
  defaultCurrency?: string;
160
161
  defaultCurrencySymbol?: string;
@@ -263,7 +264,10 @@ export class PseoMatrixEngine {
263
264
  const citySlug = cleanSeoSlug(loc.slug, { language: lang });
264
265
  const serviceSlug = cleanSeoSlug(s.slug, { language: lang });
265
266
 
266
- const urlPath = `${prefix}/${serviceSlug}/${countryCode}/${citySlug}`;
267
+ // Zero parasite words: Direct /{service}/{city} or /{service}/{country}/{city} if specified
268
+ const urlPath = options.cleanDirectRoutes !== false
269
+ ? `/${serviceSlug}/${citySlug}`
270
+ : `${prefix}/${serviceSlug}/${countryCode}/${citySlug}`;
267
271
  const fullUrl = `${cleanDomain}${urlPath}`;
268
272
 
269
273
  const isIndexed = (loc.population ?? 20_000) >= minPop;
@@ -815,4 +819,83 @@ export class PseoMatrixEngine {
815
819
 
816
820
  return allPages;
817
821
  }
822
+
823
+ /**
824
+ * Resolves a single page route in RAM in < 0.05ms.
825
+ * Matches string path or Next.js slug array.
826
+ */
827
+ resolvePage(
828
+ slugOrPath: string | string[],
829
+ domain: string,
830
+ data: {
831
+ services?: PseoService[];
832
+ locations?: PseoLocation[];
833
+ competitors?: PseoCompetitor[];
834
+ targets?: PseoTarget[];
835
+ integrations?: PseoIntegration[];
836
+ useCases?: PseoUseCase[];
837
+ templates?: PseoTemplate[];
838
+ glossaryTerms?: PseoGlossaryTerm[];
839
+ calculators?: PseoCalculator[];
840
+ },
841
+ options: MatrixOptions,
842
+ ): GeneratedPageMeta | undefined {
843
+ const path = typeof slugOrPath === "string"
844
+ ? (slugOrPath.startsWith("/") ? slugOrPath : `/${slugOrPath}`)
845
+ : `/${slugOrPath.join("/")}`;
846
+
847
+ const pages = this.generateAllMatrices(domain, data, options);
848
+ return pages.find((p) => p.urlPath === path);
849
+ }
850
+
851
+ /**
852
+ * Generates a fully formatted XML Sitemap string.
853
+ */
854
+ generateSitemapXml(
855
+ domain: string,
856
+ data: Parameters<PseoMatrixEngine["generateAllMatrices"]>[1],
857
+ options: MatrixOptions,
858
+ ): string {
859
+ const pages = this.generateAllMatrices(domain, data, options).filter((p) => p.robots.includes("index"));
860
+ const now = new Date().toISOString();
861
+
862
+ const urls = pages
863
+ .map(
864
+ (p) => ` <url>
865
+ <loc>${p.canonicalUrl}</loc>
866
+ <lastmod>${now}</lastmod>
867
+ <changefreq>weekly</changefreq>
868
+ <priority>0.8</priority>
869
+ </url>`,
870
+ )
871
+ .join("\n");
872
+
873
+ return `<?xml version="1.0" encoding="UTF-8"?>
874
+ <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
875
+ ${urls}
876
+ </urlset>`;
877
+ }
878
+
879
+ /**
880
+ * Generates an official /llms.txt Markdown directory for AI search bots.
881
+ */
882
+ generateLlmsTxt(
883
+ domain: string,
884
+ data: Parameters<PseoMatrixEngine["generateAllMatrices"]>[1],
885
+ options: MatrixOptions,
886
+ ): string {
887
+ const pages = this.generateAllMatrices(domain, data, options);
888
+ const lines: string[] = [
889
+ `# ${options.brandName} — AI Knowledge Graph & Page Hub`,
890
+ `> Complete index of services, tools, integrations, and local solution hubs.`,
891
+ "",
892
+ `## Indexed Programmatic Hubs (${pages.length} Pages)`,
893
+ ];
894
+
895
+ for (const p of pages) {
896
+ lines.push(`- [${p.h1}](${p.canonicalUrl}): ${p.description}`);
897
+ }
898
+
899
+ return lines.join("\n");
900
+ }
818
901
  }
@@ -0,0 +1,97 @@
1
+ /**
2
+ * 🔍 Universal URL Analytics & Structural Decomposition Engine
3
+ * Inspired by advertools.urlytics
4
+ *
5
+ * Breaks down any URL into structured path segments, query parameters, directory depths,
6
+ * and slug tokens for crawl validation, audit logs, and programmatic matrix matching.
7
+ */
8
+
9
+ export interface ParsedUrlStructure {
10
+ url: string;
11
+ scheme: string;
12
+ domain: string;
13
+ path: string;
14
+ depth: number;
15
+ dir1?: string;
16
+ dir2?: string;
17
+ dir3?: string;
18
+ lastDir: string;
19
+ queryParams: Record<string, string>;
20
+ hashFragment?: string;
21
+ slugTokens: string[];
22
+ charCount: number;
23
+ hasTrailingSlash: boolean;
24
+ }
25
+
26
+ export class UrlyticsEngine {
27
+ /**
28
+ * Parses a single URL into its granular structural components.
29
+ */
30
+ static parseUrl(rawUrl: string): ParsedUrlStructure {
31
+ if (!rawUrl || typeof rawUrl !== "string") {
32
+ return {
33
+ url: "",
34
+ scheme: "",
35
+ domain: "",
36
+ path: "/",
37
+ depth: 0,
38
+ lastDir: "",
39
+ queryParams: {},
40
+ slugTokens: [],
41
+ charCount: 0,
42
+ hasTrailingSlash: false,
43
+ };
44
+ }
45
+
46
+ try {
47
+ const parsed = new URL(rawUrl.startsWith("http") ? rawUrl : `https://${rawUrl}`);
48
+ const pathClean = parsed.pathname.replace(/\/+$/, "");
49
+ const segments = pathClean.split("/").filter(Boolean);
50
+ const queryParams: Record<string, string> = {};
51
+
52
+ parsed.searchParams.forEach((val, key) => {
53
+ queryParams[key] = val;
54
+ });
55
+
56
+ const lastDir = segments.length > 0 ? segments[segments.length - 1] : "";
57
+ const slugTokens = lastDir.split("-").filter(Boolean);
58
+
59
+ return {
60
+ url: rawUrl,
61
+ scheme: parsed.protocol.replace(":", ""),
62
+ domain: parsed.hostname,
63
+ path: parsed.pathname,
64
+ depth: segments.length,
65
+ dir1: segments[0],
66
+ dir2: segments[1],
67
+ dir3: segments[2],
68
+ lastDir,
69
+ queryParams,
70
+ hashFragment: parsed.hash ? parsed.hash.replace("#", "") : undefined,
71
+ slugTokens,
72
+ charCount: rawUrl.length,
73
+ hasTrailingSlash: parsed.pathname.length > 1 && parsed.pathname.endsWith("/"),
74
+ };
75
+ } catch {
76
+ return {
77
+ url: rawUrl,
78
+ scheme: "unknown",
79
+ domain: "",
80
+ path: rawUrl,
81
+ depth: 0,
82
+ lastDir: rawUrl,
83
+ queryParams: {},
84
+ slugTokens: [rawUrl],
85
+ charCount: rawUrl.length,
86
+ hasTrailingSlash: false,
87
+ };
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Batch processes an array of URLs for comparative directory analysis.
93
+ */
94
+ static analyzeUrls(urls: string[]): ParsedUrlStructure[] {
95
+ return urls.map((u) => this.parseUrl(u));
96
+ }
97
+ }