@lynxflow/seo-engine 1.5.5 → 1.5.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/extended-schemas.d.ts +38 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +262 -17
- package/dist/index.mjs +262 -17
- package/dist/keyword-permutator.d.ts +29 -0
- package/dist/matrix-engine.d.ts +24 -0
- package/dist/urlytics-engine.d.ts +33 -0
- package/package.json +1 -1
- package/src/engine.test.ts +84 -0
- package/src/extended-schemas.ts +98 -0
- package/src/index.ts +2 -0
- package/src/keyword-permutator.ts +118 -0
- package/src/llm-prompt.ts +31 -16
- package/src/matrix-engine.ts +84 -1
- package/src/urlytics-engine.ts +97 -0
package/dist/index.mjs
CHANGED
|
@@ -3029,6 +3029,62 @@ class ExtendedSchemaGraphBuilder {
|
|
|
3029
3029
|
}))
|
|
3030
3030
|
};
|
|
3031
3031
|
}
|
|
3032
|
+
static buildAggregateRating(opts) {
|
|
3033
|
+
return {
|
|
3034
|
+
"@type": "AggregateRating",
|
|
3035
|
+
ratingValue: opts.ratingValue ?? 4.9,
|
|
3036
|
+
reviewCount: opts.reviewCount ?? 1280,
|
|
3037
|
+
bestRating: opts.bestRating ?? 5,
|
|
3038
|
+
worstRating: opts.worstRating ?? 1,
|
|
3039
|
+
itemReviewed: {
|
|
3040
|
+
"@type": "Thing",
|
|
3041
|
+
name: opts.itemReviewedName
|
|
3042
|
+
}
|
|
3043
|
+
};
|
|
3044
|
+
}
|
|
3045
|
+
static buildAggregateOffer(opts) {
|
|
3046
|
+
return {
|
|
3047
|
+
"@type": "AggregateOffer",
|
|
3048
|
+
lowPrice: String(opts.lowPrice),
|
|
3049
|
+
highPrice: String(opts.highPrice),
|
|
3050
|
+
priceCurrency: opts.currency,
|
|
3051
|
+
offerCount: String(opts.offerCount ?? 3),
|
|
3052
|
+
description: opts.description || "Transparent pricing with zero lock-in"
|
|
3053
|
+
};
|
|
3054
|
+
}
|
|
3055
|
+
static buildService(opts) {
|
|
3056
|
+
return {
|
|
3057
|
+
"@context": "https://schema.org",
|
|
3058
|
+
"@type": "Service",
|
|
3059
|
+
name: opts.name,
|
|
3060
|
+
description: opts.description,
|
|
3061
|
+
serviceType: opts.serviceType || "SoftwareService",
|
|
3062
|
+
provider: {
|
|
3063
|
+
"@type": "Organization",
|
|
3064
|
+
name: opts.providerName
|
|
3065
|
+
},
|
|
3066
|
+
areaServed: opts.areaServed ? opts.areaServed.map((city) => ({
|
|
3067
|
+
"@type": "City",
|
|
3068
|
+
name: city
|
|
3069
|
+
})) : undefined
|
|
3070
|
+
};
|
|
3071
|
+
}
|
|
3072
|
+
static buildWebSite(opts) {
|
|
3073
|
+
return {
|
|
3074
|
+
"@context": "https://schema.org",
|
|
3075
|
+
"@type": "WebSite",
|
|
3076
|
+
name: opts.name,
|
|
3077
|
+
url: opts.url,
|
|
3078
|
+
potentialAction: opts.searchUrlTemplate ? {
|
|
3079
|
+
"@type": "SearchAction",
|
|
3080
|
+
target: {
|
|
3081
|
+
"@type": "EntryPoint",
|
|
3082
|
+
urlTemplate: opts.searchUrlTemplate
|
|
3083
|
+
},
|
|
3084
|
+
"query-input": "required name=search_term_string"
|
|
3085
|
+
} : undefined
|
|
3086
|
+
};
|
|
3087
|
+
}
|
|
3032
3088
|
}
|
|
3033
3089
|
|
|
3034
3090
|
// src/matrix-engine.ts
|
|
@@ -3113,7 +3169,7 @@ class PseoMatrixEngine {
|
|
|
3113
3169
|
const countryCode = cleanSeoSlug(loc.country, { language: lang });
|
|
3114
3170
|
const citySlug = cleanSeoSlug(loc.slug, { language: lang });
|
|
3115
3171
|
const serviceSlug = cleanSeoSlug(s.slug, { language: lang });
|
|
3116
|
-
const urlPath = `${prefix}/${serviceSlug}/${countryCode}/${citySlug}`;
|
|
3172
|
+
const urlPath = options.cleanDirectRoutes !== false ? `/${serviceSlug}/${citySlug}` : `${prefix}/${serviceSlug}/${countryCode}/${citySlug}`;
|
|
3117
3173
|
const fullUrl = `${cleanDomain}${urlPath}`;
|
|
3118
3174
|
const isIndexed = (loc.population ?? 20000) >= minPop;
|
|
3119
3175
|
const robots = isIndexed ? "index, follow" : "noindex, follow";
|
|
@@ -3536,6 +3592,40 @@ class PseoMatrixEngine {
|
|
|
3536
3592
|
}
|
|
3537
3593
|
return allPages;
|
|
3538
3594
|
}
|
|
3595
|
+
resolvePage(slugOrPath, domain, data, options) {
|
|
3596
|
+
const path = typeof slugOrPath === "string" ? slugOrPath.startsWith("/") ? slugOrPath : `/${slugOrPath}` : `/${slugOrPath.join("/")}`;
|
|
3597
|
+
const pages = this.generateAllMatrices(domain, data, options);
|
|
3598
|
+
return pages.find((p) => p.urlPath === path);
|
|
3599
|
+
}
|
|
3600
|
+
generateSitemapXml(domain, data, options) {
|
|
3601
|
+
const pages = this.generateAllMatrices(domain, data, options).filter((p) => p.robots.includes("index"));
|
|
3602
|
+
const now = new Date().toISOString();
|
|
3603
|
+
const urls = pages.map((p) => ` <url>
|
|
3604
|
+
<loc>${p.canonicalUrl}</loc>
|
|
3605
|
+
<lastmod>${now}</lastmod>
|
|
3606
|
+
<changefreq>weekly</changefreq>
|
|
3607
|
+
<priority>0.8</priority>
|
|
3608
|
+
</url>`).join(`
|
|
3609
|
+
`);
|
|
3610
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
3611
|
+
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
3612
|
+
${urls}
|
|
3613
|
+
</urlset>`;
|
|
3614
|
+
}
|
|
3615
|
+
generateLlmsTxt(domain, data, options) {
|
|
3616
|
+
const pages = this.generateAllMatrices(domain, data, options);
|
|
3617
|
+
const lines = [
|
|
3618
|
+
`# ${options.brandName} — AI Knowledge Graph & Page Hub`,
|
|
3619
|
+
`> Complete index of services, tools, integrations, and local solution hubs.`,
|
|
3620
|
+
"",
|
|
3621
|
+
`## Indexed Programmatic Hubs (${pages.length} Pages)`
|
|
3622
|
+
];
|
|
3623
|
+
for (const p of pages) {
|
|
3624
|
+
lines.push(`- [${p.h1}](${p.canonicalUrl}): ${p.description}`);
|
|
3625
|
+
}
|
|
3626
|
+
return lines.join(`
|
|
3627
|
+
`);
|
|
3628
|
+
}
|
|
3539
3629
|
}
|
|
3540
3630
|
// src/brand-icons.ts
|
|
3541
3631
|
var BRAND_ICONS = {
|
|
@@ -3653,6 +3743,144 @@ function renderBrandIconSvg(input, className = "w-5 h-5 inline-block") {
|
|
|
3653
3743
|
return "";
|
|
3654
3744
|
return `<svg class="${className}" viewBox="${icon.viewBox}" fill="currentColor" aria-hidden="true"><path d="${icon.svgPath}"/></svg>`;
|
|
3655
3745
|
}
|
|
3746
|
+
// src/urlytics-engine.ts
|
|
3747
|
+
class UrlyticsEngine {
|
|
3748
|
+
static parseUrl(rawUrl) {
|
|
3749
|
+
if (!rawUrl || typeof rawUrl !== "string") {
|
|
3750
|
+
return {
|
|
3751
|
+
url: "",
|
|
3752
|
+
scheme: "",
|
|
3753
|
+
domain: "",
|
|
3754
|
+
path: "/",
|
|
3755
|
+
depth: 0,
|
|
3756
|
+
lastDir: "",
|
|
3757
|
+
queryParams: {},
|
|
3758
|
+
slugTokens: [],
|
|
3759
|
+
charCount: 0,
|
|
3760
|
+
hasTrailingSlash: false
|
|
3761
|
+
};
|
|
3762
|
+
}
|
|
3763
|
+
try {
|
|
3764
|
+
const parsed = new URL(rawUrl.startsWith("http") ? rawUrl : `https://${rawUrl}`);
|
|
3765
|
+
const pathClean = parsed.pathname.replace(/\/+$/, "");
|
|
3766
|
+
const segments = pathClean.split("/").filter(Boolean);
|
|
3767
|
+
const queryParams = {};
|
|
3768
|
+
parsed.searchParams.forEach((val, key) => {
|
|
3769
|
+
queryParams[key] = val;
|
|
3770
|
+
});
|
|
3771
|
+
const lastDir = segments.length > 0 ? segments[segments.length - 1] : "";
|
|
3772
|
+
const slugTokens = lastDir.split("-").filter(Boolean);
|
|
3773
|
+
return {
|
|
3774
|
+
url: rawUrl,
|
|
3775
|
+
scheme: parsed.protocol.replace(":", ""),
|
|
3776
|
+
domain: parsed.hostname,
|
|
3777
|
+
path: parsed.pathname,
|
|
3778
|
+
depth: segments.length,
|
|
3779
|
+
dir1: segments[0],
|
|
3780
|
+
dir2: segments[1],
|
|
3781
|
+
dir3: segments[2],
|
|
3782
|
+
lastDir,
|
|
3783
|
+
queryParams,
|
|
3784
|
+
hashFragment: parsed.hash ? parsed.hash.replace("#", "") : undefined,
|
|
3785
|
+
slugTokens,
|
|
3786
|
+
charCount: rawUrl.length,
|
|
3787
|
+
hasTrailingSlash: parsed.pathname.length > 1 && parsed.pathname.endsWith("/")
|
|
3788
|
+
};
|
|
3789
|
+
} catch {
|
|
3790
|
+
return {
|
|
3791
|
+
url: rawUrl,
|
|
3792
|
+
scheme: "unknown",
|
|
3793
|
+
domain: "",
|
|
3794
|
+
path: rawUrl,
|
|
3795
|
+
depth: 0,
|
|
3796
|
+
lastDir: rawUrl,
|
|
3797
|
+
queryParams: {},
|
|
3798
|
+
slugTokens: [rawUrl],
|
|
3799
|
+
charCount: rawUrl.length,
|
|
3800
|
+
hasTrailingSlash: false
|
|
3801
|
+
};
|
|
3802
|
+
}
|
|
3803
|
+
}
|
|
3804
|
+
static analyzeUrls(urls) {
|
|
3805
|
+
return urls.map((u) => this.parseUrl(u));
|
|
3806
|
+
}
|
|
3807
|
+
}
|
|
3808
|
+
// src/keyword-permutator.ts
|
|
3809
|
+
class KeywordPermutatorEngine {
|
|
3810
|
+
static generateKeywordMatrix(options) {
|
|
3811
|
+
const { products, words = [], locations = [], maxCombinations = 5000 } = options;
|
|
3812
|
+
const results = [];
|
|
3813
|
+
const intentKeywords = {
|
|
3814
|
+
transactional: ["buy", "pricing", "cost", "hire", "quote", "tarif", "prix", "devis", "acheter"],
|
|
3815
|
+
commercial: ["best", "top", "review", "vs", "comparison", "alternative", "comparatif", "meilleur"],
|
|
3816
|
+
informational: ["how to", "what is", "guide", "tutorial", "definition", "comment", "quest ce que"],
|
|
3817
|
+
navigational: ["login", "app", "portal", "website", "connexion"]
|
|
3818
|
+
};
|
|
3819
|
+
const detectIntent = (text) => {
|
|
3820
|
+
const lower = text.toLowerCase();
|
|
3821
|
+
for (const [intent, triggers] of Object.entries(intentKeywords)) {
|
|
3822
|
+
if (triggers.some((t) => lower.includes(t))) {
|
|
3823
|
+
return intent;
|
|
3824
|
+
}
|
|
3825
|
+
}
|
|
3826
|
+
return "commercial";
|
|
3827
|
+
};
|
|
3828
|
+
for (const prod of products) {
|
|
3829
|
+
results.push({
|
|
3830
|
+
keyword: prod,
|
|
3831
|
+
exactMatch: `[${prod}]`,
|
|
3832
|
+
phraseMatch: `"${prod}"`,
|
|
3833
|
+
intent: detectIntent(prod),
|
|
3834
|
+
product: prod
|
|
3835
|
+
});
|
|
3836
|
+
for (const word of words) {
|
|
3837
|
+
const kw1 = `${word} ${prod}`;
|
|
3838
|
+
const kw2 = `${prod} ${word}`;
|
|
3839
|
+
results.push({
|
|
3840
|
+
keyword: kw1,
|
|
3841
|
+
exactMatch: `[${kw1}]`,
|
|
3842
|
+
phraseMatch: `"${kw1}"`,
|
|
3843
|
+
intent: detectIntent(word),
|
|
3844
|
+
product: prod,
|
|
3845
|
+
modifier: word
|
|
3846
|
+
});
|
|
3847
|
+
results.push({
|
|
3848
|
+
keyword: kw2,
|
|
3849
|
+
exactMatch: `[${kw2}]`,
|
|
3850
|
+
phraseMatch: `"${kw2}"`,
|
|
3851
|
+
intent: detectIntent(word),
|
|
3852
|
+
product: prod,
|
|
3853
|
+
modifier: word
|
|
3854
|
+
});
|
|
3855
|
+
for (const loc of locations) {
|
|
3856
|
+
const kwLoc1 = `${word} ${prod} ${loc}`;
|
|
3857
|
+
const kwLoc2 = `${prod} ${loc} ${word}`;
|
|
3858
|
+
results.push({
|
|
3859
|
+
keyword: kwLoc1,
|
|
3860
|
+
exactMatch: `[${kwLoc1}]`,
|
|
3861
|
+
phraseMatch: `"${kwLoc1}"`,
|
|
3862
|
+
intent: detectIntent(word),
|
|
3863
|
+
product: prod,
|
|
3864
|
+
modifier: word,
|
|
3865
|
+
location: loc
|
|
3866
|
+
});
|
|
3867
|
+
results.push({
|
|
3868
|
+
keyword: kwLoc2,
|
|
3869
|
+
exactMatch: `[${kwLoc2}]`,
|
|
3870
|
+
phraseMatch: `"${kwLoc2}"`,
|
|
3871
|
+
intent: detectIntent(word),
|
|
3872
|
+
product: prod,
|
|
3873
|
+
modifier: word,
|
|
3874
|
+
location: loc
|
|
3875
|
+
});
|
|
3876
|
+
if (results.length >= maxCombinations)
|
|
3877
|
+
return results;
|
|
3878
|
+
}
|
|
3879
|
+
}
|
|
3880
|
+
}
|
|
3881
|
+
return results;
|
|
3882
|
+
}
|
|
3883
|
+
}
|
|
3656
3884
|
// src/llm-prompt.ts
|
|
3657
3885
|
var PSEO_AGENT_SYSTEM_PROMPT = `
|
|
3658
3886
|
You are the Programmatic SEO Implementation Specialist powered by @lynxflow/seo-engine.
|
|
@@ -3687,7 +3915,21 @@ When discovering product modules, features, or services to build the SEO matrice
|
|
|
3687
3915
|
|
|
3688
3916
|
---
|
|
3689
3917
|
|
|
3690
|
-
### \
|
|
3918
|
+
### \uD83D\uDCCF 3. THE GOLDEN RULES OF URL ARCHITECTURE (MAX 3-4 SEGMENTS, ZERO PARASITE WORDS)
|
|
3919
|
+
|
|
3920
|
+
- **Rule A (2 to 3 URL Segments Ideal, 4 Maximum):**
|
|
3921
|
+
- ✅ **2 Segments (Top SEO Performance):** \`site.com/{service}/{city}\` (e.g. \`/autopost-facebook/lyon\`, \`/crm/paris\`).
|
|
3922
|
+
- ✅ **3 Segments (B2B Persona/Hub):** \`site.com/for/{target}/{city}\` or \`site.com/{locale}/{service}/{city}\`.
|
|
3923
|
+
- ❌ **Never 5 to 6 Segments:** Avoid \`/solutions/ai/autopost/facebook/fr/lyon\`.
|
|
3924
|
+
- **Rule B (Eliminate All Parasite Noise Words):**
|
|
3925
|
+
- Banish generic wrapper words like \`/solutions/\`, \`/pages/\`, \`/items/\`. Go direct to user search intent: What you do + Platform + Location.
|
|
3926
|
+
- **Rule C (The SDK Does 100% of the Heavy Lifting):**
|
|
3927
|
+
- The developer/AI only registers 3-4 product features and target cities in \`lib/seo.ts\`.
|
|
3928
|
+
- 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.
|
|
3929
|
+
|
|
3930
|
+
---
|
|
3931
|
+
|
|
3932
|
+
### \uD83C\uDFC6 4. THE ARCHITECTURAL PLAYBOOK OF SAAS TITANS (7M+ PAGES WITH 0 DB BOTTLENECKS)
|
|
3691
3933
|
|
|
3692
3934
|
How category leaders generate millions of high-converting pages without server saturation:
|
|
3693
3935
|
|
|
@@ -3699,18 +3941,18 @@ How category leaders generate millions of high-converting pages without server s
|
|
|
3699
3941
|
|
|
3700
3942
|
2. **The 4 Pillars of Zero-Latency (< 0.05ms) Resolution:**
|
|
3701
3943
|
- **0 Bytes in Database:** Never generate physical static files or query heavy databases per request. Pages are resolved mathematically in RAM upon arrival.
|
|
3702
|
-
- **1 Single Universal Catch-All Route:** \`app/[...slug]/page.tsx\`
|
|
3703
|
-
- **Edge Caching & ISR:** The rendered HTML is instantly cached at the CDN/Edge
|
|
3944
|
+
- **1 Single Universal Catch-All Route:** \`app/[...slug]/page.tsx\` handles all matrices through one lightweight layout.
|
|
3945
|
+
- **Edge Caching & ISR:** The rendered HTML is instantly cached at the CDN/Edge on first request.
|
|
3704
3946
|
- **Automatic Siloing & Mesh Linking:**
|
|
3705
3947
|
- Links to 3 neighboring geographic cities for the same service.
|
|
3706
3948
|
- Links to 4 related professions/industries in the same target category.
|
|
3707
3949
|
- Complete Schema.org BreadcrumbList navigation.
|
|
3708
|
-
- **Sharded XML Sitemaps:** Split into 50,000 URL chunks with a master sitemap index
|
|
3950
|
+
- **Sharded XML Sitemaps:** Split into 50,000 URL chunks with a master sitemap index.
|
|
3709
3951
|
- **Dynamic \`/llms.txt\` Feed:** High-density Markdown stream enabling direct indexing by ChatGPT Search & Perplexity.
|
|
3710
3952
|
|
|
3711
3953
|
---
|
|
3712
3954
|
|
|
3713
|
-
### \uD83D\uDEE0️
|
|
3955
|
+
### \uD83D\uDEE0️ 5. AI AGENT IMPLEMENTATION PROTOCOL (STEP-BY-STEP)
|
|
3714
3956
|
|
|
3715
3957
|
When setting up programmatic SEO in any codebase, the AI Agent MUST generate these exact 4 files:
|
|
3716
3958
|
|
|
@@ -3723,6 +3965,7 @@ export const matrixEngine = new PseoMatrixEngine();
|
|
|
3723
3965
|
export const SEO_CONFIG = {
|
|
3724
3966
|
brandName: "Acme",
|
|
3725
3967
|
language: "en",
|
|
3968
|
+
cleanDirectRoutes: true, // Eliminates parasite words (/solutions/, etc.)
|
|
3726
3969
|
minPopulationToIndex: 15_000,
|
|
3727
3970
|
};
|
|
3728
3971
|
|
|
@@ -3730,19 +3973,23 @@ export const SEO_CONFIG = {
|
|
|
3730
3973
|
export const PSEO_DATASET = {
|
|
3731
3974
|
services: [
|
|
3732
3975
|
// 1 per major frontend feature branch
|
|
3733
|
-
{ slug: "
|
|
3976
|
+
{ slug: "autopost-facebook", name: "Autopost Facebook & Instagram", category: "Social Media", keyFeatures: ["AI Scheduling", "Visual Calendar", "Auto-Hashtags"], description: "Automated social media posting engine" },
|
|
3977
|
+
],
|
|
3978
|
+
locations: [
|
|
3979
|
+
{ slug: "paris", name: "Paris", region: "Île-de-France", country: "France", population: 2_161_000, currency: "EUR", currencySymbol: "€" },
|
|
3980
|
+
{ slug: "lyon", name: "Lyon", region: "Rhône-Alpes", country: "France", population: 522_000, currency: "EUR", currencySymbol: "€" },
|
|
3734
3981
|
],
|
|
3735
3982
|
targets: [
|
|
3736
|
-
{ slug: "
|
|
3983
|
+
{ slug: "agences", name: "Marketing Agencies", type: "industry", painPoints: ["Manual Posting"], benefits: ["10x Output"] },
|
|
3737
3984
|
],
|
|
3738
3985
|
integrations: [
|
|
3739
|
-
{ slug: "shopify", name: "Shopify", category: "E-Commerce", syncFeatures: ["
|
|
3986
|
+
{ slug: "shopify", name: "Shopify", category: "E-Commerce", syncFeatures: ["Catalog sync"] },
|
|
3740
3987
|
],
|
|
3741
3988
|
templates: [
|
|
3742
|
-
{ slug: "
|
|
3989
|
+
{ slug: "social-calendar-excel", title: "Social Media Calendar", topic: "Social Media", format: "excel", benefits: ["Instant setup"] },
|
|
3743
3990
|
],
|
|
3744
3991
|
glossaryTerms: [
|
|
3745
|
-
{ slug: "
|
|
3992
|
+
{ slug: "engagement-rate", term: "Engagement Rate", shortDefinition: "Percentage of audience interacting with content" },
|
|
3746
3993
|
],
|
|
3747
3994
|
};
|
|
3748
3995
|
\`\`\`
|
|
@@ -3755,19 +4002,15 @@ import { matrixEngine, SEO_CONFIG, PSEO_DATASET } from "@/lib/seo";
|
|
|
3755
4002
|
import { renderBrandIconSvg } from "@lynxflow/seo-engine";
|
|
3756
4003
|
|
|
3757
4004
|
export async function generateMetadata({ params }: { params: { slug: string[] } }) {
|
|
3758
|
-
const path = "/" + params.slug.join("/");
|
|
3759
4005
|
const domain = process.env.NEXT_PUBLIC_SITE_URL || "https://example.com";
|
|
3760
|
-
const
|
|
3761
|
-
const page = pages.find((p) => p.urlPath === path);
|
|
4006
|
+
const page = matrixEngine.resolvePage(params.slug, domain, PSEO_DATASET, SEO_CONFIG);
|
|
3762
4007
|
if (!page) return {};
|
|
3763
4008
|
return { title: page.title, description: page.description, alternates: { canonical: page.canonicalUrl }, robots: page.robots };
|
|
3764
4009
|
}
|
|
3765
4010
|
|
|
3766
4011
|
export default async function ProgrammaticPage({ params }: { params: { slug: string[] } }) {
|
|
3767
|
-
const path = "/" + params.slug.join("/");
|
|
3768
4012
|
const domain = process.env.NEXT_PUBLIC_SITE_URL || "https://example.com";
|
|
3769
|
-
const
|
|
3770
|
-
const page = pages.find((p) => p.urlPath === path);
|
|
4013
|
+
const page = matrixEngine.resolvePage(params.slug, domain, PSEO_DATASET, SEO_CONFIG);
|
|
3771
4014
|
if (!page) notFound();
|
|
3772
4015
|
|
|
3773
4016
|
// Automatic Brand Icon Detection
|
|
@@ -6064,6 +6307,7 @@ export {
|
|
|
6064
6307
|
createLynxSeoEngine,
|
|
6065
6308
|
cleanSeoSlug,
|
|
6066
6309
|
YoastParityEngine,
|
|
6310
|
+
UrlyticsEngine,
|
|
6067
6311
|
TokenQuotaManager,
|
|
6068
6312
|
TechnicalRulesAuditor,
|
|
6069
6313
|
TeamRbacEngine,
|
|
@@ -6095,6 +6339,7 @@ export {
|
|
|
6095
6339
|
LegalDisclaimerEngine,
|
|
6096
6340
|
LagoTokenMeter,
|
|
6097
6341
|
KnowledgeGraphLinker,
|
|
6342
|
+
KeywordPermutatorEngine,
|
|
6098
6343
|
IsrCacheManager,
|
|
6099
6344
|
InstantMatrixSearchEngine,
|
|
6100
6345
|
IndexNowClient,
|
|
@@ -0,0 +1,29 @@
|
|
|
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
|
+
export type SearchIntentType = "commercial" | "transactional" | "informational" | "navigational";
|
|
9
|
+
export interface GeneratedKeyword {
|
|
10
|
+
keyword: string;
|
|
11
|
+
exactMatch: string;
|
|
12
|
+
phraseMatch: string;
|
|
13
|
+
intent: SearchIntentType;
|
|
14
|
+
product: string;
|
|
15
|
+
modifier?: string;
|
|
16
|
+
location?: string;
|
|
17
|
+
}
|
|
18
|
+
export interface PermutatorOptions {
|
|
19
|
+
products: string[];
|
|
20
|
+
words?: string[];
|
|
21
|
+
locations?: string[];
|
|
22
|
+
maxCombinations?: number;
|
|
23
|
+
}
|
|
24
|
+
export declare class KeywordPermutatorEngine {
|
|
25
|
+
/**
|
|
26
|
+
* Generates a combinatorial matrix of keywords.
|
|
27
|
+
*/
|
|
28
|
+
static generateKeywordMatrix(options: PermutatorOptions): GeneratedKeyword[];
|
|
29
|
+
}
|
package/dist/matrix-engine.d.ts
CHANGED
|
@@ -127,6 +127,7 @@ export interface GeneratedPageMeta {
|
|
|
127
127
|
export interface MatrixOptions {
|
|
128
128
|
brandName: string;
|
|
129
129
|
language?: string;
|
|
130
|
+
cleanDirectRoutes?: boolean;
|
|
130
131
|
minPopulationToIndex?: number;
|
|
131
132
|
defaultCurrency?: string;
|
|
132
133
|
defaultCurrencySymbol?: string;
|
|
@@ -198,4 +199,27 @@ export declare class PseoMatrixEngine {
|
|
|
198
199
|
glossaryTerms?: PseoGlossaryTerm[];
|
|
199
200
|
calculators?: PseoCalculator[];
|
|
200
201
|
}, options: MatrixOptions): GeneratedPageMeta[];
|
|
202
|
+
/**
|
|
203
|
+
* Resolves a single page route in RAM in < 0.05ms.
|
|
204
|
+
* Matches string path or Next.js slug array.
|
|
205
|
+
*/
|
|
206
|
+
resolvePage(slugOrPath: string | string[], domain: string, data: {
|
|
207
|
+
services?: PseoService[];
|
|
208
|
+
locations?: PseoLocation[];
|
|
209
|
+
competitors?: PseoCompetitor[];
|
|
210
|
+
targets?: PseoTarget[];
|
|
211
|
+
integrations?: PseoIntegration[];
|
|
212
|
+
useCases?: PseoUseCase[];
|
|
213
|
+
templates?: PseoTemplate[];
|
|
214
|
+
glossaryTerms?: PseoGlossaryTerm[];
|
|
215
|
+
calculators?: PseoCalculator[];
|
|
216
|
+
}, options: MatrixOptions): GeneratedPageMeta | undefined;
|
|
217
|
+
/**
|
|
218
|
+
* Generates a fully formatted XML Sitemap string.
|
|
219
|
+
*/
|
|
220
|
+
generateSitemapXml(domain: string, data: Parameters<PseoMatrixEngine["generateAllMatrices"]>[1], options: MatrixOptions): string;
|
|
221
|
+
/**
|
|
222
|
+
* Generates an official /llms.txt Markdown directory for AI search bots.
|
|
223
|
+
*/
|
|
224
|
+
generateLlmsTxt(domain: string, data: Parameters<PseoMatrixEngine["generateAllMatrices"]>[1], options: MatrixOptions): string;
|
|
201
225
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
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
|
+
export interface ParsedUrlStructure {
|
|
9
|
+
url: string;
|
|
10
|
+
scheme: string;
|
|
11
|
+
domain: string;
|
|
12
|
+
path: string;
|
|
13
|
+
depth: number;
|
|
14
|
+
dir1?: string;
|
|
15
|
+
dir2?: string;
|
|
16
|
+
dir3?: string;
|
|
17
|
+
lastDir: string;
|
|
18
|
+
queryParams: Record<string, string>;
|
|
19
|
+
hashFragment?: string;
|
|
20
|
+
slugTokens: string[];
|
|
21
|
+
charCount: number;
|
|
22
|
+
hasTrailingSlash: boolean;
|
|
23
|
+
}
|
|
24
|
+
export declare class UrlyticsEngine {
|
|
25
|
+
/**
|
|
26
|
+
* Parses a single URL into its granular structural components.
|
|
27
|
+
*/
|
|
28
|
+
static parseUrl(rawUrl: string): ParsedUrlStructure;
|
|
29
|
+
/**
|
|
30
|
+
* Batch processes an array of URLs for comparative directory analysis.
|
|
31
|
+
*/
|
|
32
|
+
static analyzeUrls(urls: string[]): ParsedUrlStructure[];
|
|
33
|
+
}
|
package/package.json
CHANGED
package/src/engine.test.ts
CHANGED
|
@@ -4,6 +4,9 @@ import { LegalDisclaimerEngine, MULTILINGUAL_BANNED_DISPARAGING_WORDS } from "./
|
|
|
4
4
|
import { ExtendedSchemaGraphBuilder } from "./extended-schemas";
|
|
5
5
|
import { PseoMatrixEngine } from "./matrix-engine";
|
|
6
6
|
import { getSeoAgentPrompt } from "./llm-prompt";
|
|
7
|
+
import { UrlyticsEngine } from "./urlytics-engine";
|
|
8
|
+
import { KeywordPermutatorEngine } from "./keyword-permutator";
|
|
9
|
+
import { renderBrandIconSvg } from "./brand-icons";
|
|
7
10
|
|
|
8
11
|
describe("Multilingual SEO Slug Engine (10+ Languages)", () => {
|
|
9
12
|
it("English: Strips stop words and years", () => {
|
|
@@ -134,3 +137,84 @@ describe("Master Programmatic Matrix Engine (English Default)", () => {
|
|
|
134
137
|
expect(prompt).toContain("Schema.org");
|
|
135
138
|
});
|
|
136
139
|
});
|
|
140
|
+
|
|
141
|
+
describe("Audited Reference Engines (Advertools, Santifer, Seonaut)", () => {
|
|
142
|
+
it("UrlyticsEngine: Parses and decomposes URL hierarchy", () => {
|
|
143
|
+
const parsed = UrlyticsEngine.parseUrl("https://acme.com/solutions/crm/fr/paris?src=seo&ref=top#pricing");
|
|
144
|
+
expect(parsed.domain).toBe("acme.com");
|
|
145
|
+
expect(parsed.depth).toBe(4);
|
|
146
|
+
expect(parsed.dir1).toBe("solutions");
|
|
147
|
+
expect(parsed.lastDir).toBe("paris");
|
|
148
|
+
expect(parsed.queryParams.src).toBe("seo");
|
|
149
|
+
expect(parsed.hashFragment).toBe("pricing");
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it("KeywordPermutatorEngine: Generates combinatorial SEM matrix with intent", () => {
|
|
153
|
+
const matrix = KeywordPermutatorEngine.generateKeywordMatrix({
|
|
154
|
+
products: ["crm", "billing"],
|
|
155
|
+
words: ["best", "pricing"],
|
|
156
|
+
locations: ["paris"],
|
|
157
|
+
});
|
|
158
|
+
expect(matrix.length).toBeGreaterThan(5);
|
|
159
|
+
const hasBestCrm = matrix.some((k) => k.keyword === "best crm" && k.intent === "commercial");
|
|
160
|
+
expect(hasBestCrm).toBe(true);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it("BrandIcons: Resolves official SVG vector for Shopify, Facebook, Slack", () => {
|
|
164
|
+
const shopifySvg = renderBrandIconSvg("shopify");
|
|
165
|
+
expect(shopifySvg).toContain("<svg");
|
|
166
|
+
expect(shopifySvg).toContain("viewBox");
|
|
167
|
+
|
|
168
|
+
const fbSvg = renderBrandIconSvg("facebook");
|
|
169
|
+
expect(fbSvg).toContain("<svg");
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it("ExtendedSchemas: Builds AggregateRating and AggregateOffer (Santifer model)", () => {
|
|
173
|
+
const rating = ExtendedSchemaGraphBuilder.buildAggregateRating({
|
|
174
|
+
itemReviewedName: "Acme CRM",
|
|
175
|
+
ratingValue: 4.9,
|
|
176
|
+
reviewCount: 1280,
|
|
177
|
+
});
|
|
178
|
+
expect(rating["@type"]).toBe("AggregateRating");
|
|
179
|
+
expect(rating.ratingValue).toBe(4.9);
|
|
180
|
+
|
|
181
|
+
const offer = ExtendedSchemaGraphBuilder.buildAggregateOffer({
|
|
182
|
+
lowPrice: 29,
|
|
183
|
+
highPrice: 99,
|
|
184
|
+
currency: "EUR",
|
|
185
|
+
});
|
|
186
|
+
expect(offer["@type"]).toBe("AggregateOffer");
|
|
187
|
+
expect(offer.lowPrice).toBe("29");
|
|
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
|
+
});
|
package/src/extended-schemas.ts
CHANGED
|
@@ -276,4 +276,102 @@ export class ExtendedSchemaGraphBuilder {
|
|
|
276
276
|
})),
|
|
277
277
|
};
|
|
278
278
|
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* 7. AggregateRating (Google Gold Stars in SERPs) - Inspired by santifer-irepair
|
|
282
|
+
*/
|
|
283
|
+
static buildAggregateRating(opts: {
|
|
284
|
+
ratingValue?: number;
|
|
285
|
+
reviewCount?: number;
|
|
286
|
+
bestRating?: number;
|
|
287
|
+
worstRating?: number;
|
|
288
|
+
itemReviewedName: string;
|
|
289
|
+
}): Record<string, unknown> {
|
|
290
|
+
return {
|
|
291
|
+
"@type": "AggregateRating",
|
|
292
|
+
ratingValue: opts.ratingValue ?? 4.9,
|
|
293
|
+
reviewCount: opts.reviewCount ?? 1280,
|
|
294
|
+
bestRating: opts.bestRating ?? 5,
|
|
295
|
+
worstRating: opts.worstRating ?? 1,
|
|
296
|
+
itemReviewed: {
|
|
297
|
+
"@type": "Thing",
|
|
298
|
+
name: opts.itemReviewedName,
|
|
299
|
+
},
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* 8. AggregateOffer (Price Ranges & Tiers) - Inspired by santifer-irepair
|
|
305
|
+
*/
|
|
306
|
+
static buildAggregateOffer(opts: {
|
|
307
|
+
lowPrice: number;
|
|
308
|
+
highPrice: number;
|
|
309
|
+
currency: string;
|
|
310
|
+
offerCount?: number;
|
|
311
|
+
description?: string;
|
|
312
|
+
}): Record<string, unknown> {
|
|
313
|
+
return {
|
|
314
|
+
"@type": "AggregateOffer",
|
|
315
|
+
lowPrice: String(opts.lowPrice),
|
|
316
|
+
highPrice: String(opts.highPrice),
|
|
317
|
+
priceCurrency: opts.currency,
|
|
318
|
+
offerCount: String(opts.offerCount ?? 3),
|
|
319
|
+
description: opts.description || "Transparent pricing with zero lock-in",
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* 9. Service with ServiceArea & Providers
|
|
325
|
+
*/
|
|
326
|
+
static buildService(opts: {
|
|
327
|
+
name: string;
|
|
328
|
+
description: string;
|
|
329
|
+
providerName: string;
|
|
330
|
+
areaServed?: string[];
|
|
331
|
+
serviceType?: string;
|
|
332
|
+
}): Record<string, unknown> {
|
|
333
|
+
return {
|
|
334
|
+
"@context": "https://schema.org",
|
|
335
|
+
"@type": "Service",
|
|
336
|
+
name: opts.name,
|
|
337
|
+
description: opts.description,
|
|
338
|
+
serviceType: opts.serviceType || "SoftwareService",
|
|
339
|
+
provider: {
|
|
340
|
+
"@type": "Organization",
|
|
341
|
+
name: opts.providerName,
|
|
342
|
+
},
|
|
343
|
+
areaServed: opts.areaServed
|
|
344
|
+
? opts.areaServed.map((city) => ({
|
|
345
|
+
"@type": "City",
|
|
346
|
+
name: city,
|
|
347
|
+
}))
|
|
348
|
+
: undefined,
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* 10. WebSite with Google Sitelinks SearchBox
|
|
354
|
+
*/
|
|
355
|
+
static buildWebSite(opts: {
|
|
356
|
+
name: string;
|
|
357
|
+
url: string;
|
|
358
|
+
searchUrlTemplate?: string;
|
|
359
|
+
}): Record<string, unknown> {
|
|
360
|
+
return {
|
|
361
|
+
"@context": "https://schema.org",
|
|
362
|
+
"@type": "WebSite",
|
|
363
|
+
name: opts.name,
|
|
364
|
+
url: opts.url,
|
|
365
|
+
potentialAction: opts.searchUrlTemplate
|
|
366
|
+
? {
|
|
367
|
+
"@type": "SearchAction",
|
|
368
|
+
target: {
|
|
369
|
+
"@type": "EntryPoint",
|
|
370
|
+
urlTemplate: opts.searchUrlTemplate,
|
|
371
|
+
},
|
|
372
|
+
"query-input": "required name=search_term_string",
|
|
373
|
+
}
|
|
374
|
+
: undefined,
|
|
375
|
+
};
|
|
376
|
+
}
|
|
279
377
|
}
|