@lynxflow/seo-engine 1.5.6 → 1.5.8

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.
@@ -0,0 +1,12 @@
1
+ /**
2
+ * 🌍 Built-in Global Geographic & Demographic Database
3
+ * Automatically provisions verified cities, populations, currencies, and coordinates
4
+ * across Europe, North America, and international markets without requiring the user to list cities manually.
5
+ */
6
+ import { PseoLocation } from "./matrix-engine";
7
+ export declare const BUILT_IN_LOCATIONS_DATABASE: Record<string, PseoLocation[]>;
8
+ /**
9
+ * Resolves built-in locations by country code or territory keyword.
10
+ * Supports: "fr", "france", "es", "spain", "de", "germany", "uk", "us", "be", "ch", "it", "ca", "europe", "international", "all"
11
+ */
12
+ export declare function resolveBuiltInLocations(territories?: string[] | string): PseoLocation[];
package/dist/index.d.ts CHANGED
@@ -13,6 +13,7 @@ export * from "./engine";
13
13
  export * from "./slug-engine";
14
14
  export * from "./legal-disclaimers";
15
15
  export * from "./matrix-engine";
16
+ export * from "./built-in-locations";
16
17
  export * from "./brand-icons";
17
18
  export * from "./urlytics-engine";
18
19
  export * from "./keyword-permutator";
package/dist/index.js CHANGED
@@ -40,6 +40,7 @@ var __export = (target, all) => {
40
40
  var exports_src = {};
41
41
  __export(exports_src, {
42
42
  validateSeoSlug: () => validateSeoSlug,
43
+ resolveBuiltInLocations: () => resolveBuiltInLocations,
43
44
  resolveBrandIcon: () => resolveBrandIcon,
44
45
  renderBrandIconSvg: () => renderBrandIconSvg,
45
46
  getSeoAgentPrompt: () => getSeoAgentPrompt,
@@ -93,6 +94,7 @@ __export(exports_src, {
93
94
  CopywritingFrameworksMaster: () => CopywritingFrameworksMaster,
94
95
  BrandDnaCalendarEngine: () => BrandDnaCalendarEngine,
95
96
  BacklinksClient: () => BacklinksClient,
97
+ BUILT_IN_LOCATIONS_DATABASE: () => BUILT_IN_LOCATIONS_DATABASE,
96
98
  BRAND_ICONS: () => BRAND_ICONS,
97
99
  ApiKeyGuardian: () => ApiKeyGuardian,
98
100
  AiCopilotClient: () => AiCopilotClient,
@@ -3190,6 +3192,150 @@ class ExtendedSchemaGraphBuilder {
3190
3192
  }
3191
3193
  }
3192
3194
 
3195
+ // src/built-in-locations.ts
3196
+ var BUILT_IN_LOCATIONS_DATABASE = {
3197
+ fr: [
3198
+ { slug: "paris", name: "Paris", region: "Île-de-France", country: "France", population: 2161000, currency: "EUR", currencySymbol: "€", latitude: 48.8566, longitude: 2.3522, neighborSlugs: ["boulogne-billancourt", "saint-denis", "montreuil"] },
3199
+ { slug: "marseille", name: "Marseille", region: "Provence-Alpes-Côte d'Azur", country: "France", population: 873000, currency: "EUR", currencySymbol: "€", latitude: 43.2965, longitude: 5.3698, neighborSlugs: ["aix-en-provence", "aubagne", "toulon"] },
3200
+ { slug: "lyon", name: "Lyon", region: "Auvergne-Rhône-Alpes", country: "France", population: 522000, currency: "EUR", currencySymbol: "€", latitude: 45.764, longitude: 4.8357, neighborSlugs: ["villeurbanne", "venissieux", "saint-etienne"] },
3201
+ { slug: "toulouse", name: "Toulouse", region: "Occitanie", country: "France", population: 498000, currency: "EUR", currencySymbol: "€", latitude: 43.6047, longitude: 1.4442, neighborSlugs: ["montauban", "albi", "colomiers"] },
3202
+ { slug: "nice", name: "Nice", region: "Provence-Alpes-Côte d'Azur", country: "France", population: 343000, currency: "EUR", currencySymbol: "€", latitude: 43.7102, longitude: 7.262, neighborSlugs: ["cannes", "antibes", "monaco"] },
3203
+ { slug: "nantes", name: "Nantes", region: "Pays de la Loire", country: "France", population: 320000, currency: "EUR", currencySymbol: "€", latitude: 47.2184, longitude: -1.5536, neighborSlugs: ["saint-nazaire", "angers", "rennes"] },
3204
+ { slug: "montpellier", name: "Montpellier", region: "Occitanie", country: "France", population: 299000, currency: "EUR", currencySymbol: "€", latitude: 43.6108, longitude: 3.8767, neighborSlugs: ["nimes", "beziers", "sete"] },
3205
+ { slug: "strasbourg", name: "Strasbourg", region: "Grand Est", country: "France", population: 290000, currency: "EUR", currencySymbol: "€", latitude: 48.5734, longitude: 7.7521, neighborSlugs: ["mulhouse", "colmar", "metz"] },
3206
+ { slug: "bordeaux", name: "Bordeaux", region: "Nouvelle-Aquitaine", country: "France", population: 260000, currency: "EUR", currencySymbol: "€", latitude: 44.8378, longitude: -0.5792, neighborSlugs: ["merignac", "pessac", "arcachon"] },
3207
+ { slug: "lille", name: "Lille", region: "Hauts-de-France", country: "France", population: 236000, currency: "EUR", currencySymbol: "€", latitude: 50.6292, longitude: 3.0573, neighborSlugs: ["roubaix", "tourcoing", "villeneuve-d-ascq"] },
3208
+ { slug: "rennes", name: "Rennes", region: "Bretagne", country: "France", population: 222000, currency: "EUR", currencySymbol: "€", latitude: 48.1173, longitude: -1.6778, neighborSlugs: ["saint-malo", "brest", "nantes"] },
3209
+ { slug: "toulon", name: "Toulon", region: "Provence-Alpes-Côte d'Azur", country: "France", population: 179000, currency: "EUR", currencySymbol: "€", latitude: 43.1242, longitude: 5.928, neighborSlugs: ["hyeres", "la-seyne-sur-mer", "marseille"] },
3210
+ { slug: "reims", name: "Reims", region: "Grand Est", country: "France", population: 179000, currency: "EUR", currencySymbol: "€", latitude: 49.2583, longitude: 4.0317, neighborSlugs: ["chalons-en-champagne", "troyes", "metz"] },
3211
+ { slug: "saint-etienne", name: "Saint-Étienne", region: "Auvergne-Rhône-Alpes", country: "France", population: 174000, currency: "EUR", currencySymbol: "€", latitude: 45.4397, longitude: 4.3872, neighborSlugs: ["lyon", "roanne", "saint-chamond"] },
3212
+ { slug: "le-havre", name: "Le Havre", region: "Normandie", country: "France", population: 166000, currency: "EUR", currencySymbol: "€", latitude: 49.4944, longitude: 0.1079, neighborSlugs: ["rouen", "caen", "honfleur"] },
3213
+ { slug: "grenoble", name: "Grenoble", region: "Auvergne-Rhône-Alpes", country: "France", population: 158000, currency: "EUR", currencySymbol: "€", latitude: 45.1885, longitude: 5.7245, neighborSlugs: ["echirolles", "chambery", "annecy"] },
3214
+ { slug: "dijon", name: "Dijon", region: "Bourgogne-Franche-Comté", country: "France", population: 159000, currency: "EUR", currencySymbol: "€", latitude: 47.322, longitude: 5.0415, neighborSlugs: ["besancon", "chalon-sur-saone", "beaune"] },
3215
+ { slug: "angers", name: "Angers", region: "Pays de la Loire", country: "France", population: 155000, currency: "EUR", currencySymbol: "€", latitude: 47.4784, longitude: -0.5632, neighborSlugs: ["saumur", "cholet", "nantes"] },
3216
+ { slug: "villeurbanne", name: "Villeurbanne", region: "Auvergne-Rhône-Alpes", country: "France", population: 154000, currency: "EUR", currencySymbol: "€", latitude: 45.7667, longitude: 4.8833, neighborSlugs: ["lyon", "bron", "caluire-et-cuire"] },
3217
+ { slug: "nimes", name: "Nîmes", region: "Occitanie", country: "France", population: 148000, currency: "EUR", currencySymbol: "€", latitude: 43.8367, longitude: 4.3601, neighborSlugs: ["montpellier", "avignon", "arles"] },
3218
+ { slug: "aix-en-provence", name: "Aix-en-Provence", region: "Provence-Alpes-Côte d'Azur", country: "France", population: 147000, currency: "EUR", currencySymbol: "€", latitude: 43.5297, longitude: 5.4474, neighborSlugs: ["marseille", "gardanne", "salon-de-provence"] },
3219
+ { slug: "clermont-ferrand", name: "Clermont-Ferrand", region: "Auvergne-Rhône-Alpes", country: "France", population: 147000, currency: "EUR", currencySymbol: "€", latitude: 45.7772, longitude: 3.087, neighborSlugs: ["riom", "cournon-d-auvergne", "vichy"] },
3220
+ { slug: "brest", name: "Brest", region: "Bretagne", country: "France", population: 139000, currency: "EUR", currencySymbol: "€", latitude: 48.3904, longitude: -4.4861, neighborSlugs: ["quimper", "morlaix", "rennes"] },
3221
+ { slug: "tours", name: "Tours", region: "Centre-Val de Loire", country: "France", population: 137000, currency: "EUR", currencySymbol: "€", latitude: 47.3941, longitude: 0.6848, neighborSlugs: ["orleans", "blois", "angers"] },
3222
+ { slug: "amiens", name: "Amiens", region: "Hauts-de-France", country: "France", population: 134000, currency: "EUR", currencySymbol: "€", latitude: 49.8941, longitude: 2.2958, neighborSlugs: ["beauvais", "compiegne", "lille"] },
3223
+ { slug: "annecy", name: "Annecy", region: "Auvergne-Rhône-Alpes", country: "France", population: 131000, currency: "EUR", currencySymbol: "€", latitude: 45.8992, longitude: 6.1294, neighborSlugs: ["chambery", "geneve", "grenoble"] }
3224
+ ],
3225
+ es: [
3226
+ { slug: "madrid", name: "Madrid", region: "Comunidad de Madrid", country: "Spain", population: 3300000, currency: "EUR", currencySymbol: "€", latitude: 40.4168, longitude: -3.7038, neighborSlugs: ["alcala-de-henares", "mostoles", "getafe"] },
3227
+ { slug: "barcelona", name: "Barcelona", region: "Catalunya", country: "Spain", population: 1636000, currency: "EUR", currencySymbol: "€", latitude: 41.3851, longitude: 2.1734, neighborSlugs: ["hospitalet-de-llobregat", "badalona", "terrassa"] },
3228
+ { slug: "valencia", name: "Valencia", region: "Comunitat Valenciana", country: "Spain", population: 800000, currency: "EUR", currencySymbol: "€", latitude: 39.4699, longitude: -0.3763, neighborSlugs: ["alicante", "castellon-de-la-plana", "gandia"] },
3229
+ { slug: "sevilla", name: "Sevilla", region: "Andalucía", country: "Spain", population: 688000, currency: "EUR", currencySymbol: "€", latitude: 37.3891, longitude: -5.9845, neighborSlugs: ["cordoba", "huelva", "cadiz"] },
3230
+ { slug: "zaragoza", name: "Zaragoza", region: "Aragón", country: "Spain", population: 675000, currency: "EUR", currencySymbol: "€", latitude: 41.6488, longitude: -0.8891, neighborSlugs: ["huesca", "teruel", "madrid"] },
3231
+ { slug: "malaga", name: "Málaga", region: "Andalucía", country: "Spain", population: 579000, currency: "EUR", currencySymbol: "€", latitude: 36.7213, longitude: -4.4214, neighborSlugs: ["marbella", "torremolinos", "granada"] },
3232
+ { slug: "bilbao", name: "Bilbao", region: "País Vasco", country: "Spain", population: 346000, currency: "EUR", currencySymbol: "€", latitude: 43.263, longitude: -2.935, neighborSlugs: ["san-sebastian", "vitoria-gasteiz", "santander"] }
3233
+ ],
3234
+ de: [
3235
+ { slug: "berlin", name: "Berlin", region: "Berlin", country: "Germany", population: 3645000, currency: "EUR", currencySymbol: "€", latitude: 52.52, longitude: 13.405, neighborSlugs: ["potsdam", "cottbus", "brandenburg"] },
3236
+ { slug: "hamburg", name: "Hamburg", region: "Hamburg", country: "Germany", population: 1841000, currency: "EUR", currencySymbol: "€", latitude: 53.5511, longitude: 9.9937, neighborSlugs: ["luebeck", "bremen", "kiel"] },
3237
+ { slug: "muenchen", name: "München", region: "Bayern", country: "Germany", population: 1472000, currency: "EUR", currencySymbol: "€", latitude: 48.1351, longitude: 11.582, neighborSlugs: ["augsburg", "ingolstadt", "rosenheim"] },
3238
+ { slug: "koeln", name: "Köln", region: "Nordrhein-Westfalen", country: "Germany", population: 1086000, currency: "EUR", currencySymbol: "€", latitude: 50.9375, longitude: 6.9603, neighborSlugs: ["duesseldorf", "bonn", "leverkusen"] },
3239
+ { slug: "frankfurt", name: "Frankfurt am Main", region: "Hessen", country: "Germany", population: 753000, currency: "EUR", currencySymbol: "€", latitude: 50.1109, longitude: 8.6821, neighborSlugs: ["wiesbaden", "mainz", "darmstadt"] },
3240
+ { slug: "stuttgart", name: "Stuttgart", region: "Baden-Württemberg", country: "Germany", population: 635000, currency: "EUR", currencySymbol: "€", latitude: 48.7758, longitude: 9.1829, neighborSlugs: ["esslingen", "ludwigsburg", "karlsruhe"] }
3241
+ ],
3242
+ uk: [
3243
+ { slug: "london", name: "London", region: "Greater London", country: "United Kingdom", population: 8982000, currency: "GBP", currencySymbol: "£", latitude: 51.5074, longitude: -0.1278, neighborSlugs: ["croydon", "watford", "reading"] },
3244
+ { slug: "birmingham", name: "Birmingham", region: "West Midlands", country: "United Kingdom", population: 1149000, currency: "GBP", currencySymbol: "£", latitude: 52.4862, longitude: -1.8904, neighborSlugs: ["coventry", "wolverhampton", "solihull"] },
3245
+ { slug: "manchester", name: "Manchester", region: "Greater Manchester", country: "United Kingdom", population: 553000, currency: "GBP", currencySymbol: "£", latitude: 53.4808, longitude: -2.2426, neighborSlugs: ["salford", "bolton", "stockport"] },
3246
+ { slug: "edinburgh", name: "Edinburgh", region: "Scotland", country: "United Kingdom", population: 527000, currency: "GBP", currencySymbol: "£", latitude: 55.9533, longitude: -3.1883, neighborSlugs: ["glasgow", "livingston", "dundee"] }
3247
+ ],
3248
+ us: [
3249
+ { slug: "new-york", name: "New York", region: "NY", country: "United States", population: 8336000, currency: "USD", currencySymbol: "$", latitude: 40.7128, longitude: -74.006, neighborSlugs: ["brooklyn", "queens", "jersey-city"] },
3250
+ { slug: "los-angeles", name: "Los Angeles", region: "CA", country: "United States", population: 3979000, currency: "USD", currencySymbol: "$", latitude: 34.0522, longitude: -118.2437, neighborSlugs: ["long-beach", "pasadena", "glendale"] },
3251
+ { slug: "chicago", name: "Chicago", region: "IL", country: "United States", population: 2693000, currency: "USD", currencySymbol: "$", latitude: 41.8781, longitude: -87.6298, neighborSlugs: ["evanston", "naperville", "aurora"] },
3252
+ { slug: "houston", name: "Houston", region: "TX", country: "United States", population: 2320000, currency: "USD", currencySymbol: "$", latitude: 29.7604, longitude: -95.3698, neighborSlugs: ["the-woodlands", "sugar-land", "katy"] },
3253
+ { slug: "miami", name: "Miami", region: "FL", country: "United States", population: 442000, currency: "USD", currencySymbol: "$", latitude: 25.7617, longitude: -80.1918, neighborSlugs: ["fort-lauderdale", "miami-beach", "hialeah"] },
3254
+ { slug: "san-francisco", name: "San Francisco", region: "CA", country: "United States", population: 873000, currency: "USD", currencySymbol: "$", latitude: 37.7749, longitude: -122.4194, neighborSlugs: ["oakland", "san-jose", "berkeley"] }
3255
+ ],
3256
+ be: [
3257
+ { slug: "bruxelles", name: "Bruxelles", region: "Bruxelles-Capitale", country: "Belgium", population: 1220000, currency: "EUR", currencySymbol: "€", latitude: 50.8503, longitude: 4.3517, neighborSlugs: ["anvers", "gand", "liege"] },
3258
+ { slug: "anvers", name: "Anvers", region: "Flandre", country: "Belgium", population: 530000, currency: "EUR", currencySymbol: "€", latitude: 51.2194, longitude: 4.4025, neighborSlugs: ["bruxelles", "malines", "gand"] },
3259
+ { slug: "liege", name: "Liège", region: "Wallonie", country: "Belgium", population: 197000, currency: "EUR", currencySymbol: "€", latitude: 50.6326, longitude: 5.5797, neighborSlugs: ["namur", "charleroi", "bruxelles"] }
3260
+ ],
3261
+ ch: [
3262
+ { slug: "zurich", name: "Zürich", region: "Zürich", country: "Switzerland", population: 434000, currency: "CHF", currencySymbol: "CHF", latitude: 47.3769, longitude: 8.5417, neighborSlugs: ["bale", "berne", "lucerne"] },
3263
+ { slug: "geneve", name: "Genève", region: "Genève", country: "Switzerland", population: 203000, currency: "EUR", currencySymbol: "CHF", latitude: 46.2044, longitude: 6.1432, neighborSlugs: ["lausanne", "annecy", "nyon"] },
3264
+ { slug: "lausanne", name: "Lausanne", region: "Vaud", country: "Switzerland", population: 140000, currency: "CHF", currencySymbol: "CHF", latitude: 46.5197, longitude: 6.6323, neighborSlugs: ["geneve", "montreux", "vevey"] }
3265
+ ],
3266
+ it: [
3267
+ { slug: "roma", name: "Roma", region: "Lazio", country: "Italy", population: 2873000, currency: "EUR", currencySymbol: "€", latitude: 41.9028, longitude: 12.4964, neighborSlugs: ["latina", "fiumicino", "tivoli"] },
3268
+ { slug: "milano", name: "Milano", region: "Lombardia", country: "Italy", population: 1366000, currency: "EUR", currencySymbol: "€", latitude: 45.4642, longitude: 9.19, neighborSlugs: ["monza", "bergamo", "brescia"] },
3269
+ { slug: "torino", name: "Torino", region: "Piemonte", country: "Italy", population: 870000, currency: "EUR", currencySymbol: "€", latitude: 45.0703, longitude: 7.6869, neighborSlugs: ["novara", "alessandria", "asti"] }
3270
+ ],
3271
+ ca: [
3272
+ { slug: "toronto", name: "Toronto", region: "ON", country: "Canada", population: 2794000, currency: "CAD", currencySymbol: "$", latitude: 43.6532, longitude: -79.3832, neighborSlugs: ["mississauga", "brampton", "markham"] },
3273
+ { slug: "montreal", name: "Montréal", region: "QC", country: "Canada", population: 1762000, currency: "CAD", currencySymbol: "$", latitude: 45.5017, longitude: -73.5673, neighborSlugs: ["laval", "longueuil", "quebec"] },
3274
+ { slug: "vancouver", name: "Vancouver", region: "BC", country: "Canada", population: 662000, currency: "CAD", currencySymbol: "$", latitude: 49.2827, longitude: -123.1207, neighborSlugs: ["burnaby", "richmond", "surrey"] }
3275
+ ]
3276
+ };
3277
+ function resolveBuiltInLocations(territories) {
3278
+ if (!territories) {
3279
+ return [...BUILT_IN_LOCATIONS_DATABASE.fr];
3280
+ }
3281
+ const requested = (Array.isArray(territories) ? territories : [territories]).map((t) => t.toLowerCase().trim());
3282
+ const results = [];
3283
+ const addedSlugs = new Set;
3284
+ const isAllOrEurope = requested.some((t) => ["europe", "international", "all", "global", "world"].includes(t));
3285
+ if (isAllOrEurope) {
3286
+ for (const list of Object.values(BUILT_IN_LOCATIONS_DATABASE)) {
3287
+ for (const loc of list) {
3288
+ if (!addedSlugs.has(loc.slug)) {
3289
+ addedSlugs.add(loc.slug);
3290
+ results.push(loc);
3291
+ }
3292
+ }
3293
+ }
3294
+ return results;
3295
+ }
3296
+ const countryAliasMap = {
3297
+ france: "fr",
3298
+ fr: "fr",
3299
+ spain: "es",
3300
+ espagne: "es",
3301
+ es: "es",
3302
+ germany: "de",
3303
+ allemagne: "de",
3304
+ de: "de",
3305
+ uk: "uk",
3306
+ gb: "uk",
3307
+ "united kingdom": "uk",
3308
+ angleterre: "uk",
3309
+ us: "us",
3310
+ usa: "us",
3311
+ "united states": "us",
3312
+ etatsunis: "us",
3313
+ belgium: "be",
3314
+ belgique: "be",
3315
+ be: "be",
3316
+ switzerland: "ch",
3317
+ suisse: "ch",
3318
+ ch: "ch",
3319
+ italy: "it",
3320
+ italie: "it",
3321
+ it: "it",
3322
+ canada: "ca",
3323
+ ca: "ca"
3324
+ };
3325
+ for (const req of requested) {
3326
+ const code = countryAliasMap[req.replace(/[^a-z0-9]/g, "")];
3327
+ if (code && BUILT_IN_LOCATIONS_DATABASE[code]) {
3328
+ for (const loc of BUILT_IN_LOCATIONS_DATABASE[code]) {
3329
+ if (!addedSlugs.has(loc.slug)) {
3330
+ addedSlugs.add(loc.slug);
3331
+ results.push(loc);
3332
+ }
3333
+ }
3334
+ }
3335
+ }
3336
+ return results.length > 0 ? results : [...BUILT_IN_LOCATIONS_DATABASE.fr];
3337
+ }
3338
+
3193
3339
  // src/matrix-engine.ts
3194
3340
  class PseoMatrixEngine {
3195
3341
  legalEngine;
@@ -3272,7 +3418,7 @@ class PseoMatrixEngine {
3272
3418
  const countryCode = cleanSeoSlug(loc.country, { language: lang });
3273
3419
  const citySlug = cleanSeoSlug(loc.slug, { language: lang });
3274
3420
  const serviceSlug = cleanSeoSlug(s.slug, { language: lang });
3275
- const urlPath = `${prefix}/${serviceSlug}/${countryCode}/${citySlug}`;
3421
+ const urlPath = options.cleanDirectRoutes !== false ? `/${serviceSlug}/${citySlug}` : `${prefix}/${serviceSlug}/${countryCode}/${citySlug}`;
3276
3422
  const fullUrl = `${cleanDomain}${urlPath}`;
3277
3423
  const isIndexed = (loc.population ?? 20000) >= minPop;
3278
3424
  const robots = isIndexed ? "index, follow" : "noindex, follow";
@@ -3667,8 +3813,9 @@ class PseoMatrixEngine {
3667
3813
  }
3668
3814
  generateAllMatrices(domain, data, options) {
3669
3815
  const allPages = [];
3670
- if (data.services && data.locations) {
3671
- allPages.push(...this.generateLocalGeoMatrix(domain, data.services, data.locations, options));
3816
+ if (data.services) {
3817
+ const locs = data.locations && data.locations.length > 0 ? data.locations : resolveBuiltInLocations(options.countries || options.territories || options.language || "fr");
3818
+ allPages.push(...this.generateLocalGeoMatrix(domain, data.services, locs, options));
3672
3819
  }
3673
3820
  if (data.competitors) {
3674
3821
  allPages.push(...this.generateVsMatrix(domain, data.competitors, options));
@@ -3695,6 +3842,40 @@ class PseoMatrixEngine {
3695
3842
  }
3696
3843
  return allPages;
3697
3844
  }
3845
+ resolvePage(slugOrPath, domain, data, options) {
3846
+ const path = typeof slugOrPath === "string" ? slugOrPath.startsWith("/") ? slugOrPath : `/${slugOrPath}` : `/${slugOrPath.join("/")}`;
3847
+ const pages = this.generateAllMatrices(domain, data, options);
3848
+ return pages.find((p) => p.urlPath === path);
3849
+ }
3850
+ generateSitemapXml(domain, data, options) {
3851
+ const pages = this.generateAllMatrices(domain, data, options).filter((p) => p.robots.includes("index"));
3852
+ const now = new Date().toISOString();
3853
+ const urls = pages.map((p) => ` <url>
3854
+ <loc>${p.canonicalUrl}</loc>
3855
+ <lastmod>${now}</lastmod>
3856
+ <changefreq>weekly</changefreq>
3857
+ <priority>0.8</priority>
3858
+ </url>`).join(`
3859
+ `);
3860
+ return `<?xml version="1.0" encoding="UTF-8"?>
3861
+ <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
3862
+ ${urls}
3863
+ </urlset>`;
3864
+ }
3865
+ generateLlmsTxt(domain, data, options) {
3866
+ const pages = this.generateAllMatrices(domain, data, options);
3867
+ const lines = [
3868
+ `# ${options.brandName} — AI Knowledge Graph & Page Hub`,
3869
+ `> Complete index of services, tools, integrations, and local solution hubs.`,
3870
+ "",
3871
+ `## Indexed Programmatic Hubs (${pages.length} Pages)`
3872
+ ];
3873
+ for (const p of pages) {
3874
+ lines.push(`- [${p.h1}](${p.canonicalUrl}): ${p.description}`);
3875
+ }
3876
+ return lines.join(`
3877
+ `);
3878
+ }
3698
3879
  }
3699
3880
  // src/brand-icons.ts
3700
3881
  var BRAND_ICONS = {
@@ -3984,7 +4165,21 @@ When discovering product modules, features, or services to build the SEO matrice
3984
4165
 
3985
4166
  ---
3986
4167
 
3987
- ### \uD83C\uDFC6 3. THE ARCHITECTURAL PLAYBOOK OF SAAS TITANS (7M+ PAGES WITH 0 DB BOTTLENECKS)
4168
+ ### \uD83D\uDCCF 3. THE GOLDEN RULES OF URL ARCHITECTURE (MAX 3-4 SEGMENTS, ZERO PARASITE WORDS)
4169
+
4170
+ - **Rule A (2 to 3 URL Segments Ideal, 4 Maximum):**
4171
+ - ✅ **2 Segments (Top SEO Performance):** \`site.com/{service}/{city}\` (e.g. \`/autopost-facebook/lyon\`, \`/crm/paris\`).
4172
+ - ✅ **3 Segments (B2B Persona/Hub):** \`site.com/for/{target}/{city}\` or \`site.com/{locale}/{service}/{city}\`.
4173
+ - ❌ **Never 5 to 6 Segments:** Avoid \`/solutions/ai/autopost/facebook/fr/lyon\`.
4174
+ - **Rule B (Eliminate All Parasite Noise Words):**
4175
+ - Banish generic wrapper words like \`/solutions/\`, \`/pages/\`, \`/items/\`. Go direct to user search intent: What you do + Platform + Location.
4176
+ - **Rule C (The SDK Does 100% of the Heavy Lifting):**
4177
+ - The developer/AI only registers 3-4 product features and target cities in \`lib/seo.ts\`.
4178
+ - 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.
4179
+
4180
+ ---
4181
+
4182
+ ### \uD83C\uDFC6 4. THE ARCHITECTURAL PLAYBOOK OF SAAS TITANS (7M+ PAGES WITH 0 DB BOTTLENECKS)
3988
4183
 
3989
4184
  How category leaders generate millions of high-converting pages without server saturation:
3990
4185
 
@@ -3996,18 +4191,18 @@ How category leaders generate millions of high-converting pages without server s
3996
4191
 
3997
4192
  2. **The 4 Pillars of Zero-Latency (< 0.05ms) Resolution:**
3998
4193
  - **0 Bytes in Database:** Never generate physical static files or query heavy databases per request. Pages are resolved mathematically in RAM upon arrival.
3999
- - **1 Single Universal Catch-All Route:** \`app/[...slug]/page.tsx\` (or \`app/[locale]/[...slug]/page.tsx\`) handles all matrices through one lightweight layout.
4000
- - **Edge Caching & ISR:** The rendered HTML is instantly cached at the CDN/Edge (Cloudflare / Vercel Edge) on first request.
4194
+ - **1 Single Universal Catch-All Route:** \`app/[...slug]/page.tsx\` handles all matrices through one lightweight layout.
4195
+ - **Edge Caching & ISR:** The rendered HTML is instantly cached at the CDN/Edge on first request.
4001
4196
  - **Automatic Siloing & Mesh Linking:**
4002
4197
  - Links to 3 neighboring geographic cities for the same service.
4003
4198
  - Links to 4 related professions/industries in the same target category.
4004
4199
  - Complete Schema.org BreadcrumbList navigation.
4005
- - **Sharded XML Sitemaps:** Split into 50,000 URL chunks with a master sitemap index to respect Google's crawl budget.
4200
+ - **Sharded XML Sitemaps:** Split into 50,000 URL chunks with a master sitemap index.
4006
4201
  - **Dynamic \`/llms.txt\` Feed:** High-density Markdown stream enabling direct indexing by ChatGPT Search & Perplexity.
4007
4202
 
4008
4203
  ---
4009
4204
 
4010
- ### \uD83D\uDEE0️ 4. AI AGENT IMPLEMENTATION PROTOCOL (STEP-BY-STEP)
4205
+ ### \uD83D\uDEE0️ 5. AI AGENT IMPLEMENTATION PROTOCOL (STEP-BY-STEP)
4011
4206
 
4012
4207
  When setting up programmatic SEO in any codebase, the AI Agent MUST generate these exact 4 files:
4013
4208
 
@@ -4019,27 +4214,30 @@ export const matrixEngine = new PseoMatrixEngine();
4019
4214
 
4020
4215
  export const SEO_CONFIG = {
4021
4216
  brandName: "Acme",
4022
- language: "en",
4217
+ language: "fr",
4218
+ countries: ["france", "belgique", "suisse"], // Or "europe" / "international". The SDK automatically provides verified cities!
4219
+ cleanDirectRoutes: true, // Eliminates parasite words (/solutions/, etc.)
4023
4220
  minPopulationToIndex: 15_000,
4024
4221
  };
4025
4222
 
4026
4223
  // Discovered EXCLUSIVELY from Frontend UI components
4224
+ // NOTE: DO NOT list cities manually! The SDK auto-provisions them based on your target countries/scope.
4027
4225
  export const PSEO_DATASET = {
4028
4226
  services: [
4029
4227
  // 1 per major frontend feature branch
4030
- { slug: "crm-pipeline", name: "Visual CRM Pipeline", category: "Sales", keyFeatures: ["Kanban", "Lead scoring"], description: "Visual deal tracking" },
4228
+ { slug: "autopost-facebook", name: "Autopost Facebook & Instagram", category: "Social Media", keyFeatures: ["AI Scheduling", "Visual Calendar", "Auto-Hashtags"], description: "Automated social media posting engine" },
4031
4229
  ],
4032
4230
  targets: [
4033
- { slug: "law-firms", name: "Law Firms", type: "industry", painPoints: ["Billing"], benefits: ["Compliance"] },
4231
+ { slug: "agences", name: "Marketing Agencies", type: "industry", painPoints: ["Manual Posting"], benefits: ["10x Output"] },
4034
4232
  ],
4035
4233
  integrations: [
4036
- { slug: "shopify", name: "Shopify", category: "E-Commerce", syncFeatures: ["Order sync"] },
4234
+ { slug: "shopify", name: "Shopify", category: "E-Commerce", syncFeatures: ["Catalog sync"] },
4037
4235
  ],
4038
4236
  templates: [
4039
- { slug: "sales-tracker-excel", title: "Sales Tracker", topic: "Sales", format: "excel", benefits: ["Instant setup"] },
4237
+ { slug: "social-calendar-excel", title: "Social Media Calendar", topic: "Social Media", format: "excel", benefits: ["Instant setup"] },
4040
4238
  ],
4041
4239
  glossaryTerms: [
4042
- { slug: "mrr", term: "MRR", shortDefinition: "Monthly Recurring Revenue" },
4240
+ { slug: "engagement-rate", term: "Engagement Rate", shortDefinition: "Percentage of audience interacting with content" },
4043
4241
  ],
4044
4242
  };
4045
4243
  \`\`\`
@@ -4052,19 +4250,15 @@ import { matrixEngine, SEO_CONFIG, PSEO_DATASET } from "@/lib/seo";
4052
4250
  import { renderBrandIconSvg } from "@lynxflow/seo-engine";
4053
4251
 
4054
4252
  export async function generateMetadata({ params }: { params: { slug: string[] } }) {
4055
- const path = "/" + params.slug.join("/");
4056
4253
  const domain = process.env.NEXT_PUBLIC_SITE_URL || "https://example.com";
4057
- const pages = matrixEngine.generateAllMatrices(domain, PSEO_DATASET, SEO_CONFIG);
4058
- const page = pages.find((p) => p.urlPath === path);
4254
+ const page = matrixEngine.resolvePage(params.slug, domain, PSEO_DATASET, SEO_CONFIG);
4059
4255
  if (!page) return {};
4060
4256
  return { title: page.title, description: page.description, alternates: { canonical: page.canonicalUrl }, robots: page.robots };
4061
4257
  }
4062
4258
 
4063
4259
  export default async function ProgrammaticPage({ params }: { params: { slug: string[] } }) {
4064
- const path = "/" + params.slug.join("/");
4065
4260
  const domain = process.env.NEXT_PUBLIC_SITE_URL || "https://example.com";
4066
- const pages = matrixEngine.generateAllMatrices(domain, PSEO_DATASET, SEO_CONFIG);
4067
- const page = pages.find((p) => p.urlPath === path);
4261
+ const page = matrixEngine.resolvePage(params.slug, domain, PSEO_DATASET, SEO_CONFIG);
4068
4262
  if (!page) notFound();
4069
4263
 
4070
4264
  // Automatic Brand Icon Detection
package/dist/index.mjs CHANGED
@@ -3087,6 +3087,150 @@ class ExtendedSchemaGraphBuilder {
3087
3087
  }
3088
3088
  }
3089
3089
 
3090
+ // src/built-in-locations.ts
3091
+ var BUILT_IN_LOCATIONS_DATABASE = {
3092
+ fr: [
3093
+ { slug: "paris", name: "Paris", region: "Île-de-France", country: "France", population: 2161000, currency: "EUR", currencySymbol: "€", latitude: 48.8566, longitude: 2.3522, neighborSlugs: ["boulogne-billancourt", "saint-denis", "montreuil"] },
3094
+ { slug: "marseille", name: "Marseille", region: "Provence-Alpes-Côte d'Azur", country: "France", population: 873000, currency: "EUR", currencySymbol: "€", latitude: 43.2965, longitude: 5.3698, neighborSlugs: ["aix-en-provence", "aubagne", "toulon"] },
3095
+ { slug: "lyon", name: "Lyon", region: "Auvergne-Rhône-Alpes", country: "France", population: 522000, currency: "EUR", currencySymbol: "€", latitude: 45.764, longitude: 4.8357, neighborSlugs: ["villeurbanne", "venissieux", "saint-etienne"] },
3096
+ { slug: "toulouse", name: "Toulouse", region: "Occitanie", country: "France", population: 498000, currency: "EUR", currencySymbol: "€", latitude: 43.6047, longitude: 1.4442, neighborSlugs: ["montauban", "albi", "colomiers"] },
3097
+ { slug: "nice", name: "Nice", region: "Provence-Alpes-Côte d'Azur", country: "France", population: 343000, currency: "EUR", currencySymbol: "€", latitude: 43.7102, longitude: 7.262, neighborSlugs: ["cannes", "antibes", "monaco"] },
3098
+ { slug: "nantes", name: "Nantes", region: "Pays de la Loire", country: "France", population: 320000, currency: "EUR", currencySymbol: "€", latitude: 47.2184, longitude: -1.5536, neighborSlugs: ["saint-nazaire", "angers", "rennes"] },
3099
+ { slug: "montpellier", name: "Montpellier", region: "Occitanie", country: "France", population: 299000, currency: "EUR", currencySymbol: "€", latitude: 43.6108, longitude: 3.8767, neighborSlugs: ["nimes", "beziers", "sete"] },
3100
+ { slug: "strasbourg", name: "Strasbourg", region: "Grand Est", country: "France", population: 290000, currency: "EUR", currencySymbol: "€", latitude: 48.5734, longitude: 7.7521, neighborSlugs: ["mulhouse", "colmar", "metz"] },
3101
+ { slug: "bordeaux", name: "Bordeaux", region: "Nouvelle-Aquitaine", country: "France", population: 260000, currency: "EUR", currencySymbol: "€", latitude: 44.8378, longitude: -0.5792, neighborSlugs: ["merignac", "pessac", "arcachon"] },
3102
+ { slug: "lille", name: "Lille", region: "Hauts-de-France", country: "France", population: 236000, currency: "EUR", currencySymbol: "€", latitude: 50.6292, longitude: 3.0573, neighborSlugs: ["roubaix", "tourcoing", "villeneuve-d-ascq"] },
3103
+ { slug: "rennes", name: "Rennes", region: "Bretagne", country: "France", population: 222000, currency: "EUR", currencySymbol: "€", latitude: 48.1173, longitude: -1.6778, neighborSlugs: ["saint-malo", "brest", "nantes"] },
3104
+ { slug: "toulon", name: "Toulon", region: "Provence-Alpes-Côte d'Azur", country: "France", population: 179000, currency: "EUR", currencySymbol: "€", latitude: 43.1242, longitude: 5.928, neighborSlugs: ["hyeres", "la-seyne-sur-mer", "marseille"] },
3105
+ { slug: "reims", name: "Reims", region: "Grand Est", country: "France", population: 179000, currency: "EUR", currencySymbol: "€", latitude: 49.2583, longitude: 4.0317, neighborSlugs: ["chalons-en-champagne", "troyes", "metz"] },
3106
+ { slug: "saint-etienne", name: "Saint-Étienne", region: "Auvergne-Rhône-Alpes", country: "France", population: 174000, currency: "EUR", currencySymbol: "€", latitude: 45.4397, longitude: 4.3872, neighborSlugs: ["lyon", "roanne", "saint-chamond"] },
3107
+ { slug: "le-havre", name: "Le Havre", region: "Normandie", country: "France", population: 166000, currency: "EUR", currencySymbol: "€", latitude: 49.4944, longitude: 0.1079, neighborSlugs: ["rouen", "caen", "honfleur"] },
3108
+ { slug: "grenoble", name: "Grenoble", region: "Auvergne-Rhône-Alpes", country: "France", population: 158000, currency: "EUR", currencySymbol: "€", latitude: 45.1885, longitude: 5.7245, neighborSlugs: ["echirolles", "chambery", "annecy"] },
3109
+ { slug: "dijon", name: "Dijon", region: "Bourgogne-Franche-Comté", country: "France", population: 159000, currency: "EUR", currencySymbol: "€", latitude: 47.322, longitude: 5.0415, neighborSlugs: ["besancon", "chalon-sur-saone", "beaune"] },
3110
+ { slug: "angers", name: "Angers", region: "Pays de la Loire", country: "France", population: 155000, currency: "EUR", currencySymbol: "€", latitude: 47.4784, longitude: -0.5632, neighborSlugs: ["saumur", "cholet", "nantes"] },
3111
+ { slug: "villeurbanne", name: "Villeurbanne", region: "Auvergne-Rhône-Alpes", country: "France", population: 154000, currency: "EUR", currencySymbol: "€", latitude: 45.7667, longitude: 4.8833, neighborSlugs: ["lyon", "bron", "caluire-et-cuire"] },
3112
+ { slug: "nimes", name: "Nîmes", region: "Occitanie", country: "France", population: 148000, currency: "EUR", currencySymbol: "€", latitude: 43.8367, longitude: 4.3601, neighborSlugs: ["montpellier", "avignon", "arles"] },
3113
+ { slug: "aix-en-provence", name: "Aix-en-Provence", region: "Provence-Alpes-Côte d'Azur", country: "France", population: 147000, currency: "EUR", currencySymbol: "€", latitude: 43.5297, longitude: 5.4474, neighborSlugs: ["marseille", "gardanne", "salon-de-provence"] },
3114
+ { slug: "clermont-ferrand", name: "Clermont-Ferrand", region: "Auvergne-Rhône-Alpes", country: "France", population: 147000, currency: "EUR", currencySymbol: "€", latitude: 45.7772, longitude: 3.087, neighborSlugs: ["riom", "cournon-d-auvergne", "vichy"] },
3115
+ { slug: "brest", name: "Brest", region: "Bretagne", country: "France", population: 139000, currency: "EUR", currencySymbol: "€", latitude: 48.3904, longitude: -4.4861, neighborSlugs: ["quimper", "morlaix", "rennes"] },
3116
+ { slug: "tours", name: "Tours", region: "Centre-Val de Loire", country: "France", population: 137000, currency: "EUR", currencySymbol: "€", latitude: 47.3941, longitude: 0.6848, neighborSlugs: ["orleans", "blois", "angers"] },
3117
+ { slug: "amiens", name: "Amiens", region: "Hauts-de-France", country: "France", population: 134000, currency: "EUR", currencySymbol: "€", latitude: 49.8941, longitude: 2.2958, neighborSlugs: ["beauvais", "compiegne", "lille"] },
3118
+ { slug: "annecy", name: "Annecy", region: "Auvergne-Rhône-Alpes", country: "France", population: 131000, currency: "EUR", currencySymbol: "€", latitude: 45.8992, longitude: 6.1294, neighborSlugs: ["chambery", "geneve", "grenoble"] }
3119
+ ],
3120
+ es: [
3121
+ { slug: "madrid", name: "Madrid", region: "Comunidad de Madrid", country: "Spain", population: 3300000, currency: "EUR", currencySymbol: "€", latitude: 40.4168, longitude: -3.7038, neighborSlugs: ["alcala-de-henares", "mostoles", "getafe"] },
3122
+ { slug: "barcelona", name: "Barcelona", region: "Catalunya", country: "Spain", population: 1636000, currency: "EUR", currencySymbol: "€", latitude: 41.3851, longitude: 2.1734, neighborSlugs: ["hospitalet-de-llobregat", "badalona", "terrassa"] },
3123
+ { slug: "valencia", name: "Valencia", region: "Comunitat Valenciana", country: "Spain", population: 800000, currency: "EUR", currencySymbol: "€", latitude: 39.4699, longitude: -0.3763, neighborSlugs: ["alicante", "castellon-de-la-plana", "gandia"] },
3124
+ { slug: "sevilla", name: "Sevilla", region: "Andalucía", country: "Spain", population: 688000, currency: "EUR", currencySymbol: "€", latitude: 37.3891, longitude: -5.9845, neighborSlugs: ["cordoba", "huelva", "cadiz"] },
3125
+ { slug: "zaragoza", name: "Zaragoza", region: "Aragón", country: "Spain", population: 675000, currency: "EUR", currencySymbol: "€", latitude: 41.6488, longitude: -0.8891, neighborSlugs: ["huesca", "teruel", "madrid"] },
3126
+ { slug: "malaga", name: "Málaga", region: "Andalucía", country: "Spain", population: 579000, currency: "EUR", currencySymbol: "€", latitude: 36.7213, longitude: -4.4214, neighborSlugs: ["marbella", "torremolinos", "granada"] },
3127
+ { slug: "bilbao", name: "Bilbao", region: "País Vasco", country: "Spain", population: 346000, currency: "EUR", currencySymbol: "€", latitude: 43.263, longitude: -2.935, neighborSlugs: ["san-sebastian", "vitoria-gasteiz", "santander"] }
3128
+ ],
3129
+ de: [
3130
+ { slug: "berlin", name: "Berlin", region: "Berlin", country: "Germany", population: 3645000, currency: "EUR", currencySymbol: "€", latitude: 52.52, longitude: 13.405, neighborSlugs: ["potsdam", "cottbus", "brandenburg"] },
3131
+ { slug: "hamburg", name: "Hamburg", region: "Hamburg", country: "Germany", population: 1841000, currency: "EUR", currencySymbol: "€", latitude: 53.5511, longitude: 9.9937, neighborSlugs: ["luebeck", "bremen", "kiel"] },
3132
+ { slug: "muenchen", name: "München", region: "Bayern", country: "Germany", population: 1472000, currency: "EUR", currencySymbol: "€", latitude: 48.1351, longitude: 11.582, neighborSlugs: ["augsburg", "ingolstadt", "rosenheim"] },
3133
+ { slug: "koeln", name: "Köln", region: "Nordrhein-Westfalen", country: "Germany", population: 1086000, currency: "EUR", currencySymbol: "€", latitude: 50.9375, longitude: 6.9603, neighborSlugs: ["duesseldorf", "bonn", "leverkusen"] },
3134
+ { slug: "frankfurt", name: "Frankfurt am Main", region: "Hessen", country: "Germany", population: 753000, currency: "EUR", currencySymbol: "€", latitude: 50.1109, longitude: 8.6821, neighborSlugs: ["wiesbaden", "mainz", "darmstadt"] },
3135
+ { slug: "stuttgart", name: "Stuttgart", region: "Baden-Württemberg", country: "Germany", population: 635000, currency: "EUR", currencySymbol: "€", latitude: 48.7758, longitude: 9.1829, neighborSlugs: ["esslingen", "ludwigsburg", "karlsruhe"] }
3136
+ ],
3137
+ uk: [
3138
+ { slug: "london", name: "London", region: "Greater London", country: "United Kingdom", population: 8982000, currency: "GBP", currencySymbol: "£", latitude: 51.5074, longitude: -0.1278, neighborSlugs: ["croydon", "watford", "reading"] },
3139
+ { slug: "birmingham", name: "Birmingham", region: "West Midlands", country: "United Kingdom", population: 1149000, currency: "GBP", currencySymbol: "£", latitude: 52.4862, longitude: -1.8904, neighborSlugs: ["coventry", "wolverhampton", "solihull"] },
3140
+ { slug: "manchester", name: "Manchester", region: "Greater Manchester", country: "United Kingdom", population: 553000, currency: "GBP", currencySymbol: "£", latitude: 53.4808, longitude: -2.2426, neighborSlugs: ["salford", "bolton", "stockport"] },
3141
+ { slug: "edinburgh", name: "Edinburgh", region: "Scotland", country: "United Kingdom", population: 527000, currency: "GBP", currencySymbol: "£", latitude: 55.9533, longitude: -3.1883, neighborSlugs: ["glasgow", "livingston", "dundee"] }
3142
+ ],
3143
+ us: [
3144
+ { slug: "new-york", name: "New York", region: "NY", country: "United States", population: 8336000, currency: "USD", currencySymbol: "$", latitude: 40.7128, longitude: -74.006, neighborSlugs: ["brooklyn", "queens", "jersey-city"] },
3145
+ { slug: "los-angeles", name: "Los Angeles", region: "CA", country: "United States", population: 3979000, currency: "USD", currencySymbol: "$", latitude: 34.0522, longitude: -118.2437, neighborSlugs: ["long-beach", "pasadena", "glendale"] },
3146
+ { slug: "chicago", name: "Chicago", region: "IL", country: "United States", population: 2693000, currency: "USD", currencySymbol: "$", latitude: 41.8781, longitude: -87.6298, neighborSlugs: ["evanston", "naperville", "aurora"] },
3147
+ { slug: "houston", name: "Houston", region: "TX", country: "United States", population: 2320000, currency: "USD", currencySymbol: "$", latitude: 29.7604, longitude: -95.3698, neighborSlugs: ["the-woodlands", "sugar-land", "katy"] },
3148
+ { slug: "miami", name: "Miami", region: "FL", country: "United States", population: 442000, currency: "USD", currencySymbol: "$", latitude: 25.7617, longitude: -80.1918, neighborSlugs: ["fort-lauderdale", "miami-beach", "hialeah"] },
3149
+ { slug: "san-francisco", name: "San Francisco", region: "CA", country: "United States", population: 873000, currency: "USD", currencySymbol: "$", latitude: 37.7749, longitude: -122.4194, neighborSlugs: ["oakland", "san-jose", "berkeley"] }
3150
+ ],
3151
+ be: [
3152
+ { slug: "bruxelles", name: "Bruxelles", region: "Bruxelles-Capitale", country: "Belgium", population: 1220000, currency: "EUR", currencySymbol: "€", latitude: 50.8503, longitude: 4.3517, neighborSlugs: ["anvers", "gand", "liege"] },
3153
+ { slug: "anvers", name: "Anvers", region: "Flandre", country: "Belgium", population: 530000, currency: "EUR", currencySymbol: "€", latitude: 51.2194, longitude: 4.4025, neighborSlugs: ["bruxelles", "malines", "gand"] },
3154
+ { slug: "liege", name: "Liège", region: "Wallonie", country: "Belgium", population: 197000, currency: "EUR", currencySymbol: "€", latitude: 50.6326, longitude: 5.5797, neighborSlugs: ["namur", "charleroi", "bruxelles"] }
3155
+ ],
3156
+ ch: [
3157
+ { slug: "zurich", name: "Zürich", region: "Zürich", country: "Switzerland", population: 434000, currency: "CHF", currencySymbol: "CHF", latitude: 47.3769, longitude: 8.5417, neighborSlugs: ["bale", "berne", "lucerne"] },
3158
+ { slug: "geneve", name: "Genève", region: "Genève", country: "Switzerland", population: 203000, currency: "EUR", currencySymbol: "CHF", latitude: 46.2044, longitude: 6.1432, neighborSlugs: ["lausanne", "annecy", "nyon"] },
3159
+ { slug: "lausanne", name: "Lausanne", region: "Vaud", country: "Switzerland", population: 140000, currency: "CHF", currencySymbol: "CHF", latitude: 46.5197, longitude: 6.6323, neighborSlugs: ["geneve", "montreux", "vevey"] }
3160
+ ],
3161
+ it: [
3162
+ { slug: "roma", name: "Roma", region: "Lazio", country: "Italy", population: 2873000, currency: "EUR", currencySymbol: "€", latitude: 41.9028, longitude: 12.4964, neighborSlugs: ["latina", "fiumicino", "tivoli"] },
3163
+ { slug: "milano", name: "Milano", region: "Lombardia", country: "Italy", population: 1366000, currency: "EUR", currencySymbol: "€", latitude: 45.4642, longitude: 9.19, neighborSlugs: ["monza", "bergamo", "brescia"] },
3164
+ { slug: "torino", name: "Torino", region: "Piemonte", country: "Italy", population: 870000, currency: "EUR", currencySymbol: "€", latitude: 45.0703, longitude: 7.6869, neighborSlugs: ["novara", "alessandria", "asti"] }
3165
+ ],
3166
+ ca: [
3167
+ { slug: "toronto", name: "Toronto", region: "ON", country: "Canada", population: 2794000, currency: "CAD", currencySymbol: "$", latitude: 43.6532, longitude: -79.3832, neighborSlugs: ["mississauga", "brampton", "markham"] },
3168
+ { slug: "montreal", name: "Montréal", region: "QC", country: "Canada", population: 1762000, currency: "CAD", currencySymbol: "$", latitude: 45.5017, longitude: -73.5673, neighborSlugs: ["laval", "longueuil", "quebec"] },
3169
+ { slug: "vancouver", name: "Vancouver", region: "BC", country: "Canada", population: 662000, currency: "CAD", currencySymbol: "$", latitude: 49.2827, longitude: -123.1207, neighborSlugs: ["burnaby", "richmond", "surrey"] }
3170
+ ]
3171
+ };
3172
+ function resolveBuiltInLocations(territories) {
3173
+ if (!territories) {
3174
+ return [...BUILT_IN_LOCATIONS_DATABASE.fr];
3175
+ }
3176
+ const requested = (Array.isArray(territories) ? territories : [territories]).map((t) => t.toLowerCase().trim());
3177
+ const results = [];
3178
+ const addedSlugs = new Set;
3179
+ const isAllOrEurope = requested.some((t) => ["europe", "international", "all", "global", "world"].includes(t));
3180
+ if (isAllOrEurope) {
3181
+ for (const list of Object.values(BUILT_IN_LOCATIONS_DATABASE)) {
3182
+ for (const loc of list) {
3183
+ if (!addedSlugs.has(loc.slug)) {
3184
+ addedSlugs.add(loc.slug);
3185
+ results.push(loc);
3186
+ }
3187
+ }
3188
+ }
3189
+ return results;
3190
+ }
3191
+ const countryAliasMap = {
3192
+ france: "fr",
3193
+ fr: "fr",
3194
+ spain: "es",
3195
+ espagne: "es",
3196
+ es: "es",
3197
+ germany: "de",
3198
+ allemagne: "de",
3199
+ de: "de",
3200
+ uk: "uk",
3201
+ gb: "uk",
3202
+ "united kingdom": "uk",
3203
+ angleterre: "uk",
3204
+ us: "us",
3205
+ usa: "us",
3206
+ "united states": "us",
3207
+ etatsunis: "us",
3208
+ belgium: "be",
3209
+ belgique: "be",
3210
+ be: "be",
3211
+ switzerland: "ch",
3212
+ suisse: "ch",
3213
+ ch: "ch",
3214
+ italy: "it",
3215
+ italie: "it",
3216
+ it: "it",
3217
+ canada: "ca",
3218
+ ca: "ca"
3219
+ };
3220
+ for (const req of requested) {
3221
+ const code = countryAliasMap[req.replace(/[^a-z0-9]/g, "")];
3222
+ if (code && BUILT_IN_LOCATIONS_DATABASE[code]) {
3223
+ for (const loc of BUILT_IN_LOCATIONS_DATABASE[code]) {
3224
+ if (!addedSlugs.has(loc.slug)) {
3225
+ addedSlugs.add(loc.slug);
3226
+ results.push(loc);
3227
+ }
3228
+ }
3229
+ }
3230
+ }
3231
+ return results.length > 0 ? results : [...BUILT_IN_LOCATIONS_DATABASE.fr];
3232
+ }
3233
+
3090
3234
  // src/matrix-engine.ts
3091
3235
  class PseoMatrixEngine {
3092
3236
  legalEngine;
@@ -3169,7 +3313,7 @@ class PseoMatrixEngine {
3169
3313
  const countryCode = cleanSeoSlug(loc.country, { language: lang });
3170
3314
  const citySlug = cleanSeoSlug(loc.slug, { language: lang });
3171
3315
  const serviceSlug = cleanSeoSlug(s.slug, { language: lang });
3172
- const urlPath = `${prefix}/${serviceSlug}/${countryCode}/${citySlug}`;
3316
+ const urlPath = options.cleanDirectRoutes !== false ? `/${serviceSlug}/${citySlug}` : `${prefix}/${serviceSlug}/${countryCode}/${citySlug}`;
3173
3317
  const fullUrl = `${cleanDomain}${urlPath}`;
3174
3318
  const isIndexed = (loc.population ?? 20000) >= minPop;
3175
3319
  const robots = isIndexed ? "index, follow" : "noindex, follow";
@@ -3564,8 +3708,9 @@ class PseoMatrixEngine {
3564
3708
  }
3565
3709
  generateAllMatrices(domain, data, options) {
3566
3710
  const allPages = [];
3567
- if (data.services && data.locations) {
3568
- allPages.push(...this.generateLocalGeoMatrix(domain, data.services, data.locations, options));
3711
+ if (data.services) {
3712
+ const locs = data.locations && data.locations.length > 0 ? data.locations : resolveBuiltInLocations(options.countries || options.territories || options.language || "fr");
3713
+ allPages.push(...this.generateLocalGeoMatrix(domain, data.services, locs, options));
3569
3714
  }
3570
3715
  if (data.competitors) {
3571
3716
  allPages.push(...this.generateVsMatrix(domain, data.competitors, options));
@@ -3592,6 +3737,40 @@ class PseoMatrixEngine {
3592
3737
  }
3593
3738
  return allPages;
3594
3739
  }
3740
+ resolvePage(slugOrPath, domain, data, options) {
3741
+ const path = typeof slugOrPath === "string" ? slugOrPath.startsWith("/") ? slugOrPath : `/${slugOrPath}` : `/${slugOrPath.join("/")}`;
3742
+ const pages = this.generateAllMatrices(domain, data, options);
3743
+ return pages.find((p) => p.urlPath === path);
3744
+ }
3745
+ generateSitemapXml(domain, data, options) {
3746
+ const pages = this.generateAllMatrices(domain, data, options).filter((p) => p.robots.includes("index"));
3747
+ const now = new Date().toISOString();
3748
+ const urls = pages.map((p) => ` <url>
3749
+ <loc>${p.canonicalUrl}</loc>
3750
+ <lastmod>${now}</lastmod>
3751
+ <changefreq>weekly</changefreq>
3752
+ <priority>0.8</priority>
3753
+ </url>`).join(`
3754
+ `);
3755
+ return `<?xml version="1.0" encoding="UTF-8"?>
3756
+ <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
3757
+ ${urls}
3758
+ </urlset>`;
3759
+ }
3760
+ generateLlmsTxt(domain, data, options) {
3761
+ const pages = this.generateAllMatrices(domain, data, options);
3762
+ const lines = [
3763
+ `# ${options.brandName} — AI Knowledge Graph & Page Hub`,
3764
+ `> Complete index of services, tools, integrations, and local solution hubs.`,
3765
+ "",
3766
+ `## Indexed Programmatic Hubs (${pages.length} Pages)`
3767
+ ];
3768
+ for (const p of pages) {
3769
+ lines.push(`- [${p.h1}](${p.canonicalUrl}): ${p.description}`);
3770
+ }
3771
+ return lines.join(`
3772
+ `);
3773
+ }
3595
3774
  }
3596
3775
  // src/brand-icons.ts
3597
3776
  var BRAND_ICONS = {
@@ -3881,7 +4060,21 @@ When discovering product modules, features, or services to build the SEO matrice
3881
4060
 
3882
4061
  ---
3883
4062
 
3884
- ### \uD83C\uDFC6 3. THE ARCHITECTURAL PLAYBOOK OF SAAS TITANS (7M+ PAGES WITH 0 DB BOTTLENECKS)
4063
+ ### \uD83D\uDCCF 3. THE GOLDEN RULES OF URL ARCHITECTURE (MAX 3-4 SEGMENTS, ZERO PARASITE WORDS)
4064
+
4065
+ - **Rule A (2 to 3 URL Segments Ideal, 4 Maximum):**
4066
+ - ✅ **2 Segments (Top SEO Performance):** \`site.com/{service}/{city}\` (e.g. \`/autopost-facebook/lyon\`, \`/crm/paris\`).
4067
+ - ✅ **3 Segments (B2B Persona/Hub):** \`site.com/for/{target}/{city}\` or \`site.com/{locale}/{service}/{city}\`.
4068
+ - ❌ **Never 5 to 6 Segments:** Avoid \`/solutions/ai/autopost/facebook/fr/lyon\`.
4069
+ - **Rule B (Eliminate All Parasite Noise Words):**
4070
+ - Banish generic wrapper words like \`/solutions/\`, \`/pages/\`, \`/items/\`. Go direct to user search intent: What you do + Platform + Location.
4071
+ - **Rule C (The SDK Does 100% of the Heavy Lifting):**
4072
+ - The developer/AI only registers 3-4 product features and target cities in \`lib/seo.ts\`.
4073
+ - 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.
4074
+
4075
+ ---
4076
+
4077
+ ### \uD83C\uDFC6 4. THE ARCHITECTURAL PLAYBOOK OF SAAS TITANS (7M+ PAGES WITH 0 DB BOTTLENECKS)
3885
4078
 
3886
4079
  How category leaders generate millions of high-converting pages without server saturation:
3887
4080
 
@@ -3893,18 +4086,18 @@ How category leaders generate millions of high-converting pages without server s
3893
4086
 
3894
4087
  2. **The 4 Pillars of Zero-Latency (< 0.05ms) Resolution:**
3895
4088
  - **0 Bytes in Database:** Never generate physical static files or query heavy databases per request. Pages are resolved mathematically in RAM upon arrival.
3896
- - **1 Single Universal Catch-All Route:** \`app/[...slug]/page.tsx\` (or \`app/[locale]/[...slug]/page.tsx\`) handles all matrices through one lightweight layout.
3897
- - **Edge Caching & ISR:** The rendered HTML is instantly cached at the CDN/Edge (Cloudflare / Vercel Edge) on first request.
4089
+ - **1 Single Universal Catch-All Route:** \`app/[...slug]/page.tsx\` handles all matrices through one lightweight layout.
4090
+ - **Edge Caching & ISR:** The rendered HTML is instantly cached at the CDN/Edge on first request.
3898
4091
  - **Automatic Siloing & Mesh Linking:**
3899
4092
  - Links to 3 neighboring geographic cities for the same service.
3900
4093
  - Links to 4 related professions/industries in the same target category.
3901
4094
  - Complete Schema.org BreadcrumbList navigation.
3902
- - **Sharded XML Sitemaps:** Split into 50,000 URL chunks with a master sitemap index to respect Google's crawl budget.
4095
+ - **Sharded XML Sitemaps:** Split into 50,000 URL chunks with a master sitemap index.
3903
4096
  - **Dynamic \`/llms.txt\` Feed:** High-density Markdown stream enabling direct indexing by ChatGPT Search & Perplexity.
3904
4097
 
3905
4098
  ---
3906
4099
 
3907
- ### \uD83D\uDEE0️ 4. AI AGENT IMPLEMENTATION PROTOCOL (STEP-BY-STEP)
4100
+ ### \uD83D\uDEE0️ 5. AI AGENT IMPLEMENTATION PROTOCOL (STEP-BY-STEP)
3908
4101
 
3909
4102
  When setting up programmatic SEO in any codebase, the AI Agent MUST generate these exact 4 files:
3910
4103
 
@@ -3916,27 +4109,30 @@ export const matrixEngine = new PseoMatrixEngine();
3916
4109
 
3917
4110
  export const SEO_CONFIG = {
3918
4111
  brandName: "Acme",
3919
- language: "en",
4112
+ language: "fr",
4113
+ countries: ["france", "belgique", "suisse"], // Or "europe" / "international". The SDK automatically provides verified cities!
4114
+ cleanDirectRoutes: true, // Eliminates parasite words (/solutions/, etc.)
3920
4115
  minPopulationToIndex: 15_000,
3921
4116
  };
3922
4117
 
3923
4118
  // Discovered EXCLUSIVELY from Frontend UI components
4119
+ // NOTE: DO NOT list cities manually! The SDK auto-provisions them based on your target countries/scope.
3924
4120
  export const PSEO_DATASET = {
3925
4121
  services: [
3926
4122
  // 1 per major frontend feature branch
3927
- { slug: "crm-pipeline", name: "Visual CRM Pipeline", category: "Sales", keyFeatures: ["Kanban", "Lead scoring"], description: "Visual deal tracking" },
4123
+ { slug: "autopost-facebook", name: "Autopost Facebook & Instagram", category: "Social Media", keyFeatures: ["AI Scheduling", "Visual Calendar", "Auto-Hashtags"], description: "Automated social media posting engine" },
3928
4124
  ],
3929
4125
  targets: [
3930
- { slug: "law-firms", name: "Law Firms", type: "industry", painPoints: ["Billing"], benefits: ["Compliance"] },
4126
+ { slug: "agences", name: "Marketing Agencies", type: "industry", painPoints: ["Manual Posting"], benefits: ["10x Output"] },
3931
4127
  ],
3932
4128
  integrations: [
3933
- { slug: "shopify", name: "Shopify", category: "E-Commerce", syncFeatures: ["Order sync"] },
4129
+ { slug: "shopify", name: "Shopify", category: "E-Commerce", syncFeatures: ["Catalog sync"] },
3934
4130
  ],
3935
4131
  templates: [
3936
- { slug: "sales-tracker-excel", title: "Sales Tracker", topic: "Sales", format: "excel", benefits: ["Instant setup"] },
4132
+ { slug: "social-calendar-excel", title: "Social Media Calendar", topic: "Social Media", format: "excel", benefits: ["Instant setup"] },
3937
4133
  ],
3938
4134
  glossaryTerms: [
3939
- { slug: "mrr", term: "MRR", shortDefinition: "Monthly Recurring Revenue" },
4135
+ { slug: "engagement-rate", term: "Engagement Rate", shortDefinition: "Percentage of audience interacting with content" },
3940
4136
  ],
3941
4137
  };
3942
4138
  \`\`\`
@@ -3949,19 +4145,15 @@ import { matrixEngine, SEO_CONFIG, PSEO_DATASET } from "@/lib/seo";
3949
4145
  import { renderBrandIconSvg } from "@lynxflow/seo-engine";
3950
4146
 
3951
4147
  export async function generateMetadata({ params }: { params: { slug: string[] } }) {
3952
- const path = "/" + params.slug.join("/");
3953
4148
  const domain = process.env.NEXT_PUBLIC_SITE_URL || "https://example.com";
3954
- const pages = matrixEngine.generateAllMatrices(domain, PSEO_DATASET, SEO_CONFIG);
3955
- const page = pages.find((p) => p.urlPath === path);
4149
+ const page = matrixEngine.resolvePage(params.slug, domain, PSEO_DATASET, SEO_CONFIG);
3956
4150
  if (!page) return {};
3957
4151
  return { title: page.title, description: page.description, alternates: { canonical: page.canonicalUrl }, robots: page.robots };
3958
4152
  }
3959
4153
 
3960
4154
  export default async function ProgrammaticPage({ params }: { params: { slug: string[] } }) {
3961
- const path = "/" + params.slug.join("/");
3962
4155
  const domain = process.env.NEXT_PUBLIC_SITE_URL || "https://example.com";
3963
- const pages = matrixEngine.generateAllMatrices(domain, PSEO_DATASET, SEO_CONFIG);
3964
- const page = pages.find((p) => p.urlPath === path);
4156
+ const page = matrixEngine.resolvePage(params.slug, domain, PSEO_DATASET, SEO_CONFIG);
3965
4157
  if (!page) notFound();
3966
4158
 
3967
4159
  // Automatic Brand Icon Detection
@@ -6251,6 +6443,7 @@ var LynxSeo = {
6251
6443
  var src_default = LynxSeo;
6252
6444
  export {
6253
6445
  validateSeoSlug,
6446
+ resolveBuiltInLocations,
6254
6447
  resolveBrandIcon,
6255
6448
  renderBrandIconSvg,
6256
6449
  getSeoAgentPrompt,
@@ -6304,6 +6497,7 @@ export {
6304
6497
  CopywritingFrameworksMaster,
6305
6498
  BrandDnaCalendarEngine,
6306
6499
  BacklinksClient,
6500
+ BUILT_IN_LOCATIONS_DATABASE,
6307
6501
  BRAND_ICONS,
6308
6502
  ApiKeyGuardian,
6309
6503
  AiCopilotClient,
@@ -127,6 +127,9 @@ export interface GeneratedPageMeta {
127
127
  export interface MatrixOptions {
128
128
  brandName: string;
129
129
  language?: string;
130
+ countries?: string[] | string;
131
+ territories?: string[] | string;
132
+ cleanDirectRoutes?: boolean;
130
133
  minPopulationToIndex?: number;
131
134
  defaultCurrency?: string;
132
135
  defaultCurrencySymbol?: string;
@@ -198,4 +201,27 @@ export declare class PseoMatrixEngine {
198
201
  glossaryTerms?: PseoGlossaryTerm[];
199
202
  calculators?: PseoCalculator[];
200
203
  }, options: MatrixOptions): GeneratedPageMeta[];
204
+ /**
205
+ * Resolves a single page route in RAM in < 0.05ms.
206
+ * Matches string path or Next.js slug array.
207
+ */
208
+ resolvePage(slugOrPath: string | string[], domain: string, data: {
209
+ services?: PseoService[];
210
+ locations?: PseoLocation[];
211
+ competitors?: PseoCompetitor[];
212
+ targets?: PseoTarget[];
213
+ integrations?: PseoIntegration[];
214
+ useCases?: PseoUseCase[];
215
+ templates?: PseoTemplate[];
216
+ glossaryTerms?: PseoGlossaryTerm[];
217
+ calculators?: PseoCalculator[];
218
+ }, options: MatrixOptions): GeneratedPageMeta | undefined;
219
+ /**
220
+ * Generates a fully formatted XML Sitemap string.
221
+ */
222
+ generateSitemapXml(domain: string, data: Parameters<PseoMatrixEngine["generateAllMatrices"]>[1], options: MatrixOptions): string;
223
+ /**
224
+ * Generates an official /llms.txt Markdown directory for AI search bots.
225
+ */
226
+ generateLlmsTxt(domain: string, data: Parameters<PseoMatrixEngine["generateAllMatrices"]>[1], options: MatrixOptions): string;
201
227
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lynxflow/seo-engine",
3
- "version": "1.5.6",
3
+ "version": "1.5.8",
4
4
  "description": "High-Performance Multilingual Programmatic SEO & AI Search Engine SDK",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -0,0 +1,170 @@
1
+ /**
2
+ * 🌍 Built-in Global Geographic & Demographic Database
3
+ * Automatically provisions verified cities, populations, currencies, and coordinates
4
+ * across Europe, North America, and international markets without requiring the user to list cities manually.
5
+ */
6
+
7
+ import { PseoLocation } from "./matrix-engine";
8
+
9
+ export const BUILT_IN_LOCATIONS_DATABASE: Record<string, PseoLocation[]> = {
10
+ fr: [
11
+ { slug: "paris", name: "Paris", region: "Île-de-France", country: "France", population: 2_161_000, currency: "EUR", currencySymbol: "€", latitude: 48.8566, longitude: 2.3522, neighborSlugs: ["boulogne-billancourt", "saint-denis", "montreuil"] },
12
+ { slug: "marseille", name: "Marseille", region: "Provence-Alpes-Côte d'Azur", country: "France", population: 873_000, currency: "EUR", currencySymbol: "€", latitude: 43.2965, longitude: 5.3698, neighborSlugs: ["aix-en-provence", "aubagne", "toulon"] },
13
+ { slug: "lyon", name: "Lyon", region: "Auvergne-Rhône-Alpes", country: "France", population: 522_000, currency: "EUR", currencySymbol: "€", latitude: 45.764, longitude: 4.8357, neighborSlugs: ["villeurbanne", "venissieux", "saint-etienne"] },
14
+ { slug: "toulouse", name: "Toulouse", region: "Occitanie", country: "France", population: 498_000, currency: "EUR", currencySymbol: "€", latitude: 43.6047, longitude: 1.4442, neighborSlugs: ["montauban", "albi", "colomiers"] },
15
+ { slug: "nice", name: "Nice", region: "Provence-Alpes-Côte d'Azur", country: "France", population: 343_000, currency: "EUR", currencySymbol: "€", latitude: 43.7102, longitude: 7.262, neighborSlugs: ["cannes", "antibes", "monaco"] },
16
+ { slug: "nantes", name: "Nantes", region: "Pays de la Loire", country: "France", population: 320_000, currency: "EUR", currencySymbol: "€", latitude: 47.2184, longitude: -1.5536, neighborSlugs: ["saint-nazaire", "angers", "rennes"] },
17
+ { slug: "montpellier", name: "Montpellier", region: "Occitanie", country: "France", population: 299_000, currency: "EUR", currencySymbol: "€", latitude: 43.6108, longitude: 3.8767, neighborSlugs: ["nimes", "beziers", "sete"] },
18
+ { slug: "strasbourg", name: "Strasbourg", region: "Grand Est", country: "France", population: 290_000, currency: "EUR", currencySymbol: "€", latitude: 48.5734, longitude: 7.7521, neighborSlugs: ["mulhouse", "colmar", "metz"] },
19
+ { slug: "bordeaux", name: "Bordeaux", region: "Nouvelle-Aquitaine", country: "France", population: 260_000, currency: "EUR", currencySymbol: "€", latitude: 44.8378, longitude: -0.5792, neighborSlugs: ["merignac", "pessac", "arcachon"] },
20
+ { slug: "lille", name: "Lille", region: "Hauts-de-France", country: "France", population: 236_000, currency: "EUR", currencySymbol: "€", latitude: 50.6292, longitude: 3.0573, neighborSlugs: ["roubaix", "tourcoing", "villeneuve-d-ascq"] },
21
+ { slug: "rennes", name: "Rennes", region: "Bretagne", country: "France", population: 222_000, currency: "EUR", currencySymbol: "€", latitude: 48.1173, longitude: -1.6778, neighborSlugs: ["saint-malo", "brest", "nantes"] },
22
+ { slug: "toulon", name: "Toulon", region: "Provence-Alpes-Côte d'Azur", country: "France", population: 179_000, currency: "EUR", currencySymbol: "€", latitude: 43.1242, longitude: 5.928, neighborSlugs: ["hyeres", "la-seyne-sur-mer", "marseille"] },
23
+ { slug: "reims", name: "Reims", region: "Grand Est", country: "France", population: 179_000, currency: "EUR", currencySymbol: "€", latitude: 49.2583, longitude: 4.0317, neighborSlugs: ["chalons-en-champagne", "troyes", "metz"] },
24
+ { slug: "saint-etienne", name: "Saint-Étienne", region: "Auvergne-Rhône-Alpes", country: "France", population: 174_000, currency: "EUR", currencySymbol: "€", latitude: 45.4397, longitude: 4.3872, neighborSlugs: ["lyon", "roanne", "saint-chamond"] },
25
+ { slug: "le-havre", name: "Le Havre", region: "Normandie", country: "France", population: 166_000, currency: "EUR", currencySymbol: "€", latitude: 49.4944, longitude: 0.1079, neighborSlugs: ["rouen", "caen", "honfleur"] },
26
+ { slug: "grenoble", name: "Grenoble", region: "Auvergne-Rhône-Alpes", country: "France", population: 158_000, currency: "EUR", currencySymbol: "€", latitude: 45.1885, longitude: 5.7245, neighborSlugs: ["echirolles", "chambery", "annecy"] },
27
+ { slug: "dijon", name: "Dijon", region: "Bourgogne-Franche-Comté", country: "France", population: 159_000, currency: "EUR", currencySymbol: "€", latitude: 47.322, longitude: 5.0415, neighborSlugs: ["besancon", "chalon-sur-saone", "beaune"] },
28
+ { slug: "angers", name: "Angers", region: "Pays de la Loire", country: "France", population: 155_000, currency: "EUR", currencySymbol: "€", latitude: 47.4784, longitude: -0.5632, neighborSlugs: ["saumur", "cholet", "nantes"] },
29
+ { slug: "villeurbanne", name: "Villeurbanne", region: "Auvergne-Rhône-Alpes", country: "France", population: 154_000, currency: "EUR", currencySymbol: "€", latitude: 45.7667, longitude: 4.8833, neighborSlugs: ["lyon", "bron", "caluire-et-cuire"] },
30
+ { slug: "nimes", name: "Nîmes", region: "Occitanie", country: "France", population: 148_000, currency: "EUR", currencySymbol: "€", latitude: 43.8367, longitude: 4.3601, neighborSlugs: ["montpellier", "avignon", "arles"] },
31
+ { slug: "aix-en-provence", name: "Aix-en-Provence", region: "Provence-Alpes-Côte d'Azur", country: "France", population: 147_000, currency: "EUR", currencySymbol: "€", latitude: 43.5297, longitude: 5.4474, neighborSlugs: ["marseille", "gardanne", "salon-de-provence"] },
32
+ { slug: "clermont-ferrand", name: "Clermont-Ferrand", region: "Auvergne-Rhône-Alpes", country: "France", population: 147_000, currency: "EUR", currencySymbol: "€", latitude: 45.7772, longitude: 3.087, neighborSlugs: ["riom", "cournon-d-auvergne", "vichy"] },
33
+ { slug: "brest", name: "Brest", region: "Bretagne", country: "France", population: 139_000, currency: "EUR", currencySymbol: "€", latitude: 48.3904, longitude: -4.4861, neighborSlugs: ["quimper", "morlaix", "rennes"] },
34
+ { slug: "tours", name: "Tours", region: "Centre-Val de Loire", country: "France", population: 137_000, currency: "EUR", currencySymbol: "€", latitude: 47.3941, longitude: 0.6848, neighborSlugs: ["orleans", "blois", "angers"] },
35
+ { slug: "amiens", name: "Amiens", region: "Hauts-de-France", country: "France", population: 134_000, currency: "EUR", currencySymbol: "€", latitude: 49.8941, longitude: 2.2958, neighborSlugs: ["beauvais", "compiegne", "lille"] },
36
+ { slug: "annecy", name: "Annecy", region: "Auvergne-Rhône-Alpes", country: "France", population: 131_000, currency: "EUR", currencySymbol: "€", latitude: 45.8992, longitude: 6.1294, neighborSlugs: ["chambery", "geneve", "grenoble"] },
37
+ ],
38
+
39
+ es: [
40
+ { slug: "madrid", name: "Madrid", region: "Comunidad de Madrid", country: "Spain", population: 3_300_000, currency: "EUR", currencySymbol: "€", latitude: 40.4168, longitude: -3.7038, neighborSlugs: ["alcala-de-henares", "mostoles", "getafe"] },
41
+ { slug: "barcelona", name: "Barcelona", region: "Catalunya", country: "Spain", population: 1_636_000, currency: "EUR", currencySymbol: "€", latitude: 41.3851, longitude: 2.1734, neighborSlugs: ["hospitalet-de-llobregat", "badalona", "terrassa"] },
42
+ { slug: "valencia", name: "Valencia", region: "Comunitat Valenciana", country: "Spain", population: 800_000, currency: "EUR", currencySymbol: "€", latitude: 39.4699, longitude: -0.3763, neighborSlugs: ["alicante", "castellon-de-la-plana", "gandia"] },
43
+ { slug: "sevilla", name: "Sevilla", region: "Andalucía", country: "Spain", population: 688_000, currency: "EUR", currencySymbol: "€", latitude: 37.3891, longitude: -5.9845, neighborSlugs: ["cordoba", "huelva", "cadiz"] },
44
+ { slug: "zaragoza", name: "Zaragoza", region: "Aragón", country: "Spain", population: 675_000, currency: "EUR", currencySymbol: "€", latitude: 41.6488, longitude: -0.8891, neighborSlugs: ["huesca", "teruel", "madrid"] },
45
+ { slug: "malaga", name: "Málaga", region: "Andalucía", country: "Spain", population: 579_000, currency: "EUR", currencySymbol: "€", latitude: 36.7213, longitude: -4.4214, neighborSlugs: ["marbella", "torremolinos", "granada"] },
46
+ { slug: "bilbao", name: "Bilbao", region: "País Vasco", country: "Spain", population: 346_000, currency: "EUR", currencySymbol: "€", latitude: 43.263, longitude: -2.935, neighborSlugs: ["san-sebastian", "vitoria-gasteiz", "santander"] },
47
+ ],
48
+
49
+ de: [
50
+ { slug: "berlin", name: "Berlin", region: "Berlin", country: "Germany", population: 3_645_000, currency: "EUR", currencySymbol: "€", latitude: 52.52, longitude: 13.405, neighborSlugs: ["potsdam", "cottbus", "brandenburg"] },
51
+ { slug: "hamburg", name: "Hamburg", region: "Hamburg", country: "Germany", population: 1_841_000, currency: "EUR", currencySymbol: "€", latitude: 53.5511, longitude: 9.9937, neighborSlugs: ["luebeck", "bremen", "kiel"] },
52
+ { slug: "muenchen", name: "München", region: "Bayern", country: "Germany", population: 1_472_000, currency: "EUR", currencySymbol: "€", latitude: 48.1351, longitude: 11.582, neighborSlugs: ["augsburg", "ingolstadt", "rosenheim"] },
53
+ { slug: "koeln", name: "Köln", region: "Nordrhein-Westfalen", country: "Germany", population: 1_086_000, currency: "EUR", currencySymbol: "€", latitude: 50.9375, longitude: 6.9603, neighborSlugs: ["duesseldorf", "bonn", "leverkusen"] },
54
+ { slug: "frankfurt", name: "Frankfurt am Main", region: "Hessen", country: "Germany", population: 753_000, currency: "EUR", currencySymbol: "€", latitude: 50.1109, longitude: 8.6821, neighborSlugs: ["wiesbaden", "mainz", "darmstadt"] },
55
+ { slug: "stuttgart", name: "Stuttgart", region: "Baden-Württemberg", country: "Germany", population: 635_000, currency: "EUR", currencySymbol: "€", latitude: 48.7758, longitude: 9.1829, neighborSlugs: ["esslingen", "ludwigsburg", "karlsruhe"] },
56
+ ],
57
+
58
+ uk: [
59
+ { slug: "london", name: "London", region: "Greater London", country: "United Kingdom", population: 8_982_000, currency: "GBP", currencySymbol: "£", latitude: 51.5074, longitude: -0.1278, neighborSlugs: ["croydon", "watford", "reading"] },
60
+ { slug: "birmingham", name: "Birmingham", region: "West Midlands", country: "United Kingdom", population: 1_149_000, currency: "GBP", currencySymbol: "£", latitude: 52.4862, longitude: -1.8904, neighborSlugs: ["coventry", "wolverhampton", "solihull"] },
61
+ { slug: "manchester", name: "Manchester", region: "Greater Manchester", country: "United Kingdom", population: 553_000, currency: "GBP", currencySymbol: "£", latitude: 53.4808, longitude: -2.2426, neighborSlugs: ["salford", "bolton", "stockport"] },
62
+ { slug: "edinburgh", name: "Edinburgh", region: "Scotland", country: "United Kingdom", population: 527_000, currency: "GBP", currencySymbol: "£", latitude: 55.9533, longitude: -3.1883, neighborSlugs: ["glasgow", "livingston", "dundee"] },
63
+ ],
64
+
65
+ us: [
66
+ { slug: "new-york", name: "New York", region: "NY", country: "United States", population: 8_336_000, currency: "USD", currencySymbol: "$", latitude: 40.7128, longitude: -74.006, neighborSlugs: ["brooklyn", "queens", "jersey-city"] },
67
+ { slug: "los-angeles", name: "Los Angeles", region: "CA", country: "United States", population: 3_979_000, currency: "USD", currencySymbol: "$", latitude: 34.0522, longitude: -118.2437, neighborSlugs: ["long-beach", "pasadena", "glendale"] },
68
+ { slug: "chicago", name: "Chicago", region: "IL", country: "United States", population: 2_693_000, currency: "USD", currencySymbol: "$", latitude: 41.8781, longitude: -87.6298, neighborSlugs: ["evanston", "naperville", "aurora"] },
69
+ { slug: "houston", name: "Houston", region: "TX", country: "United States", population: 2_320_000, currency: "USD", currencySymbol: "$", latitude: 29.7604, longitude: -95.3698, neighborSlugs: ["the-woodlands", "sugar-land", "katy"] },
70
+ { slug: "miami", name: "Miami", region: "FL", country: "United States", population: 442_000, currency: "USD", currencySymbol: "$", latitude: 25.7617, longitude: -80.1918, neighborSlugs: ["fort-lauderdale", "miami-beach", "hialeah"] },
71
+ { slug: "san-francisco", name: "San Francisco", region: "CA", country: "United States", population: 873_000, currency: "USD", currencySymbol: "$", latitude: 37.7749, longitude: -122.4194, neighborSlugs: ["oakland", "san-jose", "berkeley"] },
72
+ ],
73
+
74
+ be: [
75
+ { slug: "bruxelles", name: "Bruxelles", region: "Bruxelles-Capitale", country: "Belgium", population: 1_220_000, currency: "EUR", currencySymbol: "€", latitude: 50.8503, longitude: 4.3517, neighborSlugs: ["anvers", "gand", "liege"] },
76
+ { slug: "anvers", name: "Anvers", region: "Flandre", country: "Belgium", population: 530_000, currency: "EUR", currencySymbol: "€", latitude: 51.2194, longitude: 4.4025, neighborSlugs: ["bruxelles", "malines", "gand"] },
77
+ { slug: "liege", name: "Liège", region: "Wallonie", country: "Belgium", population: 197_000, currency: "EUR", currencySymbol: "€", latitude: 50.6326, longitude: 5.5797, neighborSlugs: ["namur", "charleroi", "bruxelles"] },
78
+ ],
79
+
80
+ ch: [
81
+ { slug: "zurich", name: "Zürich", region: "Zürich", country: "Switzerland", population: 434_000, currency: "CHF", currencySymbol: "CHF", latitude: 47.3769, longitude: 8.5417, neighborSlugs: ["bale", "berne", "lucerne"] },
82
+ { slug: "geneve", name: "Genève", region: "Genève", country: "Switzerland", population: 203_000, currency: "EUR", currencySymbol: "CHF", latitude: 46.2044, longitude: 6.1432, neighborSlugs: ["lausanne", "annecy", "nyon"] },
83
+ { slug: "lausanne", name: "Lausanne", region: "Vaud", country: "Switzerland", population: 140_000, currency: "CHF", currencySymbol: "CHF", latitude: 46.5197, longitude: 6.6323, neighborSlugs: ["geneve", "montreux", "vevey"] },
84
+ ],
85
+
86
+ it: [
87
+ { slug: "roma", name: "Roma", region: "Lazio", country: "Italy", population: 2_873_000, currency: "EUR", currencySymbol: "€", latitude: 41.9028, longitude: 12.4964, neighborSlugs: ["latina", "fiumicino", "tivoli"] },
88
+ { slug: "milano", name: "Milano", region: "Lombardia", country: "Italy", population: 1_366_000, currency: "EUR", currencySymbol: "€", latitude: 45.4642, longitude: 9.19, neighborSlugs: ["monza", "bergamo", "brescia"] },
89
+ { slug: "torino", name: "Torino", region: "Piemonte", country: "Italy", population: 870_000, currency: "EUR", currencySymbol: "€", latitude: 45.0703, longitude: 7.6869, neighborSlugs: ["novara", "alessandria", "asti"] },
90
+ ],
91
+
92
+ ca: [
93
+ { slug: "toronto", name: "Toronto", region: "ON", country: "Canada", population: 2_794_000, currency: "CAD", currencySymbol: "$", latitude: 43.6532, longitude: -79.3832, neighborSlugs: ["mississauga", "brampton", "markham"] },
94
+ { slug: "montreal", name: "Montréal", region: "QC", country: "Canada", population: 1_762_000, currency: "CAD", currencySymbol: "$", latitude: 45.5017, longitude: -73.5673, neighborSlugs: ["laval", "longueuil", "quebec"] },
95
+ { slug: "vancouver", name: "Vancouver", region: "BC", country: "Canada", population: 662_000, currency: "CAD", currencySymbol: "$", latitude: 49.2827, longitude: -123.1207, neighborSlugs: ["burnaby", "richmond", "surrey"] },
96
+ ],
97
+ };
98
+
99
+ /**
100
+ * Resolves built-in locations by country code or territory keyword.
101
+ * Supports: "fr", "france", "es", "spain", "de", "germany", "uk", "us", "be", "ch", "it", "ca", "europe", "international", "all"
102
+ */
103
+ export function resolveBuiltInLocations(territories?: string[] | string): PseoLocation[] {
104
+ if (!territories) {
105
+ // Default: France + Belgium + Switzerland if unspecified
106
+ return [...BUILT_IN_LOCATIONS_DATABASE.fr];
107
+ }
108
+
109
+ const requested = (Array.isArray(territories) ? territories : [territories]).map((t) => t.toLowerCase().trim());
110
+ const results: PseoLocation[] = [];
111
+ const addedSlugs = new Set<string>();
112
+
113
+ const isAllOrEurope = requested.some((t) => ["europe", "international", "all", "global", "world"].includes(t));
114
+
115
+ if (isAllOrEurope) {
116
+ for (const list of Object.values(BUILT_IN_LOCATIONS_DATABASE)) {
117
+ for (const loc of list) {
118
+ if (!addedSlugs.has(loc.slug)) {
119
+ addedSlugs.add(loc.slug);
120
+ results.push(loc);
121
+ }
122
+ }
123
+ }
124
+ return results;
125
+ }
126
+
127
+ const countryAliasMap: Record<string, string> = {
128
+ france: "fr",
129
+ fr: "fr",
130
+ spain: "es",
131
+ espagne: "es",
132
+ es: "es",
133
+ germany: "de",
134
+ allemagne: "de",
135
+ de: "de",
136
+ uk: "uk",
137
+ gb: "uk",
138
+ "united kingdom": "uk",
139
+ angleterre: "uk",
140
+ us: "us",
141
+ usa: "us",
142
+ "united states": "us",
143
+ etatsunis: "us",
144
+ belgium: "be",
145
+ belgique: "be",
146
+ be: "be",
147
+ switzerland: "ch",
148
+ suisse: "ch",
149
+ ch: "ch",
150
+ italy: "it",
151
+ italie: "it",
152
+ it: "it",
153
+ canada: "ca",
154
+ ca: "ca",
155
+ };
156
+
157
+ for (const req of requested) {
158
+ const code = countryAliasMap[req.replace(/[^a-z0-9]/g, "")];
159
+ if (code && BUILT_IN_LOCATIONS_DATABASE[code]) {
160
+ for (const loc of BUILT_IN_LOCATIONS_DATABASE[code]) {
161
+ if (!addedSlugs.has(loc.slug)) {
162
+ addedSlugs.add(loc.slug);
163
+ results.push(loc);
164
+ }
165
+ }
166
+ }
167
+ }
168
+
169
+ return results.length > 0 ? results : [...BUILT_IN_LOCATIONS_DATABASE.fr];
170
+ }
@@ -186,4 +186,52 @@ describe("Audited Reference Engines (Advertools, Santifer, Seonaut)", () => {
186
186
  expect(offer["@type"]).toBe("AggregateOffer");
187
187
  expect(offer.lowPrice).toBe("29");
188
188
  });
189
+
190
+ it("PseoMatrixEngine: resolvePage returns complete page in < 0.05ms", () => {
191
+ const engine = new PseoMatrixEngine();
192
+ const page = engine.resolvePage(
193
+ ["autopost-facebook", "lyon"],
194
+ "https://acme.com",
195
+ {
196
+ services: [{ slug: "autopost-facebook", name: "Autopost Facebook", category: "Social" }],
197
+ locations: [{ slug: "lyon", name: "Lyon", country: "France", population: 500_000 }],
198
+ },
199
+ { brandName: "Acme", cleanDirectRoutes: true },
200
+ );
201
+ expect(page).toBeDefined();
202
+ expect(page?.urlPath).toBe("/autopost-facebook/lyon");
203
+ expect(page?.h1).toContain("Autopost Facebook");
204
+ });
205
+
206
+ it("PseoMatrixEngine: generateSitemapXml and generateLlmsTxt generate complete feeds", () => {
207
+ const engine = new PseoMatrixEngine();
208
+ const data = {
209
+ services: [{ slug: "autopost", name: "Autopost", category: "Social" }],
210
+ locations: [{ slug: "paris", name: "Paris", country: "France", population: 2_000_000 }],
211
+ };
212
+ const sitemap = engine.generateSitemapXml("https://acme.com", data, { brandName: "Acme" });
213
+ expect(sitemap).toContain("<urlset");
214
+ expect(sitemap).toContain("<loc>https://acme.com/autopost/paris</loc>");
215
+
216
+ const llmsTxt = engine.generateLlmsTxt("https://acme.com", data, { brandName: "Acme" });
217
+ expect(llmsTxt).toContain("# Acme — AI Knowledge Graph");
218
+ expect(llmsTxt).toContain("https://acme.com/autopost/paris");
219
+ });
220
+
221
+ it("Built-in Locations: Auto-provisions cities without manual entry", () => {
222
+ const engine = new PseoMatrixEngine();
223
+ const pages = engine.generateAllMatrices(
224
+ "https://acme.com",
225
+ {
226
+ services: [{ slug: "autopost-facebook", name: "Autopost Facebook", category: "Social" }],
227
+ },
228
+ { brandName: "Acme", countries: ["france", "spain"] },
229
+ );
230
+
231
+ const paths = pages.map((p) => p.urlPath);
232
+ expect(paths).toContain("/autopost-facebook/paris");
233
+ expect(paths).toContain("/autopost-facebook/lyon");
234
+ expect(paths).toContain("/autopost-facebook/madrid");
235
+ expect(paths).toContain("/autopost-facebook/barcelona");
236
+ });
189
237
  });
package/src/index.ts CHANGED
@@ -14,6 +14,7 @@ export * from "./engine";
14
14
  export * from "./slug-engine";
15
15
  export * from "./legal-disclaimers";
16
16
  export * from "./matrix-engine";
17
+ export * from "./built-in-locations";
17
18
  export * from "./brand-icons";
18
19
  export * from "./urlytics-engine";
19
20
  export * from "./keyword-permutator";
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
 
@@ -72,27 +86,30 @@ export const matrixEngine = new PseoMatrixEngine();
72
86
 
73
87
  export const SEO_CONFIG = {
74
88
  brandName: "Acme",
75
- language: "en",
89
+ language: "fr",
90
+ countries: ["france", "belgique", "suisse"], // Or "europe" / "international". The SDK automatically provides verified cities!
91
+ cleanDirectRoutes: true, // Eliminates parasite words (/solutions/, etc.)
76
92
  minPopulationToIndex: 15_000,
77
93
  };
78
94
 
79
95
  // Discovered EXCLUSIVELY from Frontend UI components
96
+ // NOTE: DO NOT list cities manually! The SDK auto-provisions them based on your target countries/scope.
80
97
  export const PSEO_DATASET = {
81
98
  services: [
82
99
  // 1 per major frontend feature branch
83
- { slug: "crm-pipeline", name: "Visual CRM Pipeline", category: "Sales", keyFeatures: ["Kanban", "Lead scoring"], description: "Visual deal tracking" },
100
+ { slug: "autopost-facebook", name: "Autopost Facebook & Instagram", category: "Social Media", keyFeatures: ["AI Scheduling", "Visual Calendar", "Auto-Hashtags"], description: "Automated social media posting engine" },
84
101
  ],
85
102
  targets: [
86
- { slug: "law-firms", name: "Law Firms", type: "industry", painPoints: ["Billing"], benefits: ["Compliance"] },
103
+ { slug: "agences", name: "Marketing Agencies", type: "industry", painPoints: ["Manual Posting"], benefits: ["10x Output"] },
87
104
  ],
88
105
  integrations: [
89
- { slug: "shopify", name: "Shopify", category: "E-Commerce", syncFeatures: ["Order sync"] },
106
+ { slug: "shopify", name: "Shopify", category: "E-Commerce", syncFeatures: ["Catalog sync"] },
90
107
  ],
91
108
  templates: [
92
- { slug: "sales-tracker-excel", title: "Sales Tracker", topic: "Sales", format: "excel", benefits: ["Instant setup"] },
109
+ { slug: "social-calendar-excel", title: "Social Media Calendar", topic: "Social Media", format: "excel", benefits: ["Instant setup"] },
93
110
  ],
94
111
  glossaryTerms: [
95
- { slug: "mrr", term: "MRR", shortDefinition: "Monthly Recurring Revenue" },
112
+ { slug: "engagement-rate", term: "Engagement Rate", shortDefinition: "Percentage of audience interacting with content" },
96
113
  ],
97
114
  };
98
115
  \`\`\`
@@ -105,19 +122,15 @@ import { matrixEngine, SEO_CONFIG, PSEO_DATASET } from "@/lib/seo";
105
122
  import { renderBrandIconSvg } from "@lynxflow/seo-engine";
106
123
 
107
124
  export async function generateMetadata({ params }: { params: { slug: string[] } }) {
108
- const path = "/" + params.slug.join("/");
109
125
  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);
126
+ const page = matrixEngine.resolvePage(params.slug, domain, PSEO_DATASET, SEO_CONFIG);
112
127
  if (!page) return {};
113
128
  return { title: page.title, description: page.description, alternates: { canonical: page.canonicalUrl }, robots: page.robots };
114
129
  }
115
130
 
116
131
  export default async function ProgrammaticPage({ params }: { params: { slug: string[] } }) {
117
- const path = "/" + params.slug.join("/");
118
132
  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);
133
+ const page = matrixEngine.resolvePage(params.slug, domain, PSEO_DATASET, SEO_CONFIG);
121
134
  if (!page) notFound();
122
135
 
123
136
  // Automatic Brand Icon Detection
@@ -13,6 +13,7 @@
13
13
  import { cleanSeoSlug } from "./slug-engine";
14
14
  import { LegalDisclaimerEngine } from "./legal-disclaimers";
15
15
  import { ExtendedSchemaGraphBuilder } from "./extended-schemas";
16
+ import { resolveBuiltInLocations } from "./built-in-locations";
16
17
 
17
18
  export type MatrixFamily =
18
19
  | "local-geo"
@@ -155,6 +156,9 @@ export interface GeneratedPageMeta {
155
156
  export interface MatrixOptions {
156
157
  brandName: string;
157
158
  language?: string; // Default: "en", supports "fr", "de", "es", "pt", "it", etc.
159
+ countries?: string[] | string; // e.g. ["france", "spain"] or "europe" or "international"
160
+ territories?: string[] | string; // Alias for countries
161
+ cleanDirectRoutes?: boolean; // When true (default), eliminates parasite words (/solutions/, etc.) for direct /{service}/{city}
158
162
  minPopulationToIndex?: number;
159
163
  defaultCurrency?: string;
160
164
  defaultCurrencySymbol?: string;
@@ -263,7 +267,10 @@ export class PseoMatrixEngine {
263
267
  const citySlug = cleanSeoSlug(loc.slug, { language: lang });
264
268
  const serviceSlug = cleanSeoSlug(s.slug, { language: lang });
265
269
 
266
- const urlPath = `${prefix}/${serviceSlug}/${countryCode}/${citySlug}`;
270
+ // Zero parasite words: Direct /{service}/{city} or /{service}/{country}/{city} if specified
271
+ const urlPath = options.cleanDirectRoutes !== false
272
+ ? `/${serviceSlug}/${citySlug}`
273
+ : `${prefix}/${serviceSlug}/${countryCode}/${citySlug}`;
267
274
  const fullUrl = `${cleanDomain}${urlPath}`;
268
275
 
269
276
  const isIndexed = (loc.population ?? 20_000) >= minPop;
@@ -786,8 +793,11 @@ export class PseoMatrixEngine {
786
793
  ): GeneratedPageMeta[] {
787
794
  const allPages: GeneratedPageMeta[] = [];
788
795
 
789
- if (data.services && data.locations) {
790
- allPages.push(...this.generateLocalGeoMatrix(domain, data.services, data.locations, options));
796
+ if (data.services) {
797
+ const locs = data.locations && data.locations.length > 0
798
+ ? data.locations
799
+ : resolveBuiltInLocations(options.countries || options.territories || options.language || "fr");
800
+ allPages.push(...this.generateLocalGeoMatrix(domain, data.services, locs, options));
791
801
  }
792
802
  if (data.competitors) {
793
803
  allPages.push(...this.generateVsMatrix(domain, data.competitors, options));
@@ -815,4 +825,83 @@ export class PseoMatrixEngine {
815
825
 
816
826
  return allPages;
817
827
  }
828
+
829
+ /**
830
+ * Resolves a single page route in RAM in < 0.05ms.
831
+ * Matches string path or Next.js slug array.
832
+ */
833
+ resolvePage(
834
+ slugOrPath: string | string[],
835
+ domain: string,
836
+ data: {
837
+ services?: PseoService[];
838
+ locations?: PseoLocation[];
839
+ competitors?: PseoCompetitor[];
840
+ targets?: PseoTarget[];
841
+ integrations?: PseoIntegration[];
842
+ useCases?: PseoUseCase[];
843
+ templates?: PseoTemplate[];
844
+ glossaryTerms?: PseoGlossaryTerm[];
845
+ calculators?: PseoCalculator[];
846
+ },
847
+ options: MatrixOptions,
848
+ ): GeneratedPageMeta | undefined {
849
+ const path = typeof slugOrPath === "string"
850
+ ? (slugOrPath.startsWith("/") ? slugOrPath : `/${slugOrPath}`)
851
+ : `/${slugOrPath.join("/")}`;
852
+
853
+ const pages = this.generateAllMatrices(domain, data, options);
854
+ return pages.find((p) => p.urlPath === path);
855
+ }
856
+
857
+ /**
858
+ * Generates a fully formatted XML Sitemap string.
859
+ */
860
+ generateSitemapXml(
861
+ domain: string,
862
+ data: Parameters<PseoMatrixEngine["generateAllMatrices"]>[1],
863
+ options: MatrixOptions,
864
+ ): string {
865
+ const pages = this.generateAllMatrices(domain, data, options).filter((p) => p.robots.includes("index"));
866
+ const now = new Date().toISOString();
867
+
868
+ const urls = pages
869
+ .map(
870
+ (p) => ` <url>
871
+ <loc>${p.canonicalUrl}</loc>
872
+ <lastmod>${now}</lastmod>
873
+ <changefreq>weekly</changefreq>
874
+ <priority>0.8</priority>
875
+ </url>`,
876
+ )
877
+ .join("\n");
878
+
879
+ return `<?xml version="1.0" encoding="UTF-8"?>
880
+ <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
881
+ ${urls}
882
+ </urlset>`;
883
+ }
884
+
885
+ /**
886
+ * Generates an official /llms.txt Markdown directory for AI search bots.
887
+ */
888
+ generateLlmsTxt(
889
+ domain: string,
890
+ data: Parameters<PseoMatrixEngine["generateAllMatrices"]>[1],
891
+ options: MatrixOptions,
892
+ ): string {
893
+ const pages = this.generateAllMatrices(domain, data, options);
894
+ const lines: string[] = [
895
+ `# ${options.brandName} — AI Knowledge Graph & Page Hub`,
896
+ `> Complete index of services, tools, integrations, and local solution hubs.`,
897
+ "",
898
+ `## Indexed Programmatic Hubs (${pages.length} Pages)`,
899
+ ];
900
+
901
+ for (const p of pages) {
902
+ lines.push(`- [${p.h1}](${p.canonicalUrl}): ${p.description}`);
903
+ }
904
+
905
+ return lines.join("\n");
906
+ }
818
907
  }