@rankcli/agent-runtime 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +242 -0
- package/dist/analyzer-2CSWIQGD.mjs +6 -0
- package/dist/chunk-YNZYHEYM.mjs +774 -0
- package/dist/index.d.mts +4012 -0
- package/dist/index.d.ts +4012 -0
- package/dist/index.js +29672 -0
- package/dist/index.mjs +28602 -0
- package/package.json +53 -0
- package/scripts/build-deno.ts +134 -0
- package/src/audit/ai/analyzer.ts +347 -0
- package/src/audit/ai/index.ts +29 -0
- package/src/audit/ai/prompts/content-analysis.ts +271 -0
- package/src/audit/ai/types.ts +179 -0
- package/src/audit/checks/additional-checks.ts +439 -0
- package/src/audit/checks/ai-citation-worthiness.ts +399 -0
- package/src/audit/checks/ai-content-structure.ts +325 -0
- package/src/audit/checks/ai-readiness.ts +339 -0
- package/src/audit/checks/anchor-text.ts +179 -0
- package/src/audit/checks/answer-conciseness.ts +322 -0
- package/src/audit/checks/asset-minification.ts +270 -0
- package/src/audit/checks/bing-optimization.ts +206 -0
- package/src/audit/checks/brand-mention-optimization.ts +349 -0
- package/src/audit/checks/caching-headers.ts +305 -0
- package/src/audit/checks/canonical-advanced.ts +150 -0
- package/src/audit/checks/canonical-domain.ts +196 -0
- package/src/audit/checks/citation-quality.ts +358 -0
- package/src/audit/checks/client-rendering.ts +542 -0
- package/src/audit/checks/color-contrast.ts +342 -0
- package/src/audit/checks/content-freshness.ts +170 -0
- package/src/audit/checks/content-science.ts +589 -0
- package/src/audit/checks/conversion-elements.ts +526 -0
- package/src/audit/checks/crawlability.ts +220 -0
- package/src/audit/checks/directory-listing.ts +172 -0
- package/src/audit/checks/dom-analysis.ts +191 -0
- package/src/audit/checks/dom-size.ts +246 -0
- package/src/audit/checks/duplicate-content.ts +194 -0
- package/src/audit/checks/eeat-signals.ts +990 -0
- package/src/audit/checks/entity-seo.ts +396 -0
- package/src/audit/checks/featured-snippet.ts +473 -0
- package/src/audit/checks/freshness-signals.ts +443 -0
- package/src/audit/checks/funnel-intent.ts +463 -0
- package/src/audit/checks/hreflang.ts +174 -0
- package/src/audit/checks/html-compliance.ts +302 -0
- package/src/audit/checks/image-dimensions.ts +167 -0
- package/src/audit/checks/images.ts +160 -0
- package/src/audit/checks/indexnow.ts +275 -0
- package/src/audit/checks/interactive-tools.ts +475 -0
- package/src/audit/checks/internal-link-graph.ts +436 -0
- package/src/audit/checks/keyword-analysis.ts +239 -0
- package/src/audit/checks/keyword-cannibalization.ts +385 -0
- package/src/audit/checks/keyword-placement.ts +471 -0
- package/src/audit/checks/links.ts +203 -0
- package/src/audit/checks/llms-txt.ts +224 -0
- package/src/audit/checks/local-seo.ts +296 -0
- package/src/audit/checks/mobile.ts +167 -0
- package/src/audit/checks/modern-images.ts +226 -0
- package/src/audit/checks/navboost-signals.ts +395 -0
- package/src/audit/checks/on-page.ts +209 -0
- package/src/audit/checks/page-resources.ts +285 -0
- package/src/audit/checks/pagination.ts +180 -0
- package/src/audit/checks/performance.ts +153 -0
- package/src/audit/checks/platform-presence.ts +580 -0
- package/src/audit/checks/redirect-analysis.ts +153 -0
- package/src/audit/checks/redirect-chain.ts +389 -0
- package/src/audit/checks/resource-hints.ts +420 -0
- package/src/audit/checks/responsive-css.ts +247 -0
- package/src/audit/checks/responsive-images.ts +396 -0
- package/src/audit/checks/review-ecosystem.ts +415 -0
- package/src/audit/checks/robots-validation.ts +373 -0
- package/src/audit/checks/security-headers.ts +172 -0
- package/src/audit/checks/security.ts +144 -0
- package/src/audit/checks/serp-preview.ts +251 -0
- package/src/audit/checks/site-maturity.ts +444 -0
- package/src/audit/checks/social-meta.test.ts +275 -0
- package/src/audit/checks/social-meta.ts +134 -0
- package/src/audit/checks/soft-404.ts +151 -0
- package/src/audit/checks/structured-data.ts +238 -0
- package/src/audit/checks/tech-detection.ts +496 -0
- package/src/audit/checks/topical-clusters.ts +435 -0
- package/src/audit/checks/tracker-bloat.ts +462 -0
- package/src/audit/checks/tracking-verification.test.ts +371 -0
- package/src/audit/checks/tracking-verification.ts +636 -0
- package/src/audit/checks/url-safety.ts +682 -0
- package/src/audit/deno-entry.ts +66 -0
- package/src/audit/discovery/index.ts +15 -0
- package/src/audit/discovery/link-crawler.ts +232 -0
- package/src/audit/discovery/repo-routes.ts +347 -0
- package/src/audit/engine.ts +620 -0
- package/src/audit/fixes/index.ts +209 -0
- package/src/audit/fixes/social-meta-fixes.test.ts +329 -0
- package/src/audit/fixes/social-meta-fixes.ts +463 -0
- package/src/audit/index.ts +74 -0
- package/src/audit/runner.test.ts +299 -0
- package/src/audit/runner.ts +130 -0
- package/src/audit/types.ts +1953 -0
- package/src/content/featured-snippet.ts +367 -0
- package/src/content/generator.test.ts +534 -0
- package/src/content/generator.ts +501 -0
- package/src/content/headline.ts +317 -0
- package/src/content/index.ts +62 -0
- package/src/content/intent.ts +258 -0
- package/src/content/keyword-density.ts +349 -0
- package/src/content/readability.ts +262 -0
- package/src/executor.ts +336 -0
- package/src/fixer.ts +416 -0
- package/src/frameworks/detector.test.ts +248 -0
- package/src/frameworks/detector.ts +371 -0
- package/src/frameworks/index.ts +68 -0
- package/src/frameworks/recipes/angular.yaml +171 -0
- package/src/frameworks/recipes/astro.yaml +206 -0
- package/src/frameworks/recipes/django.yaml +180 -0
- package/src/frameworks/recipes/laravel.yaml +137 -0
- package/src/frameworks/recipes/nextjs.yaml +268 -0
- package/src/frameworks/recipes/nuxt.yaml +175 -0
- package/src/frameworks/recipes/rails.yaml +188 -0
- package/src/frameworks/recipes/react.yaml +202 -0
- package/src/frameworks/recipes/sveltekit.yaml +154 -0
- package/src/frameworks/recipes/vue.yaml +137 -0
- package/src/frameworks/recipes/wordpress.yaml +209 -0
- package/src/frameworks/suggestion-engine.ts +320 -0
- package/src/geo/geo-content.test.ts +305 -0
- package/src/geo/geo-content.ts +266 -0
- package/src/geo/geo-history.test.ts +473 -0
- package/src/geo/geo-history.ts +433 -0
- package/src/geo/geo-tracker.test.ts +359 -0
- package/src/geo/geo-tracker.ts +411 -0
- package/src/geo/index.ts +10 -0
- package/src/git/commit-helper.test.ts +261 -0
- package/src/git/commit-helper.ts +329 -0
- package/src/git/index.ts +12 -0
- package/src/git/pr-helper.test.ts +284 -0
- package/src/git/pr-helper.ts +307 -0
- package/src/index.ts +66 -0
- package/src/keywords/ai-keyword-engine.ts +1062 -0
- package/src/keywords/ai-summarizer.ts +387 -0
- package/src/keywords/ci-mode.ts +555 -0
- package/src/keywords/engine.ts +359 -0
- package/src/keywords/index.ts +151 -0
- package/src/keywords/llm-judge.ts +357 -0
- package/src/keywords/nlp-analysis.ts +706 -0
- package/src/keywords/prioritizer.ts +295 -0
- package/src/keywords/site-crawler.ts +342 -0
- package/src/keywords/sources/autocomplete.ts +139 -0
- package/src/keywords/sources/competitive-search.ts +450 -0
- package/src/keywords/sources/competitor-analysis.ts +374 -0
- package/src/keywords/sources/dataforseo.ts +206 -0
- package/src/keywords/sources/free-sources.ts +294 -0
- package/src/keywords/sources/gsc.ts +123 -0
- package/src/keywords/topic-grouping.ts +327 -0
- package/src/keywords/types.ts +144 -0
- package/src/keywords/wizard.ts +457 -0
- package/src/loader.ts +40 -0
- package/src/reports/index.ts +7 -0
- package/src/reports/report-generator.test.ts +293 -0
- package/src/reports/report-generator.ts +713 -0
- package/src/scheduler/alerts.test.ts +458 -0
- package/src/scheduler/alerts.ts +328 -0
- package/src/scheduler/index.ts +8 -0
- package/src/scheduler/scheduled-audit.test.ts +377 -0
- package/src/scheduler/scheduled-audit.ts +149 -0
- package/src/test/integration-test.ts +325 -0
- package/src/tools/analyzer.ts +373 -0
- package/src/tools/crawl.ts +293 -0
- package/src/tools/files.ts +301 -0
- package/src/tools/h1-fixer.ts +249 -0
- package/src/tools/index.ts +67 -0
- package/src/tracking/github-action.ts +326 -0
- package/src/tracking/google-analytics.ts +265 -0
- package/src/tracking/index.ts +45 -0
- package/src/tracking/report-generator.ts +386 -0
- package/src/tracking/search-console.ts +335 -0
- package/src/types.ts +134 -0
- package/src/utils/http.ts +302 -0
- package/src/wasm-adapter.ts +297 -0
- package/src/wasm-entry.ts +14 -0
- package/tsconfig.json +17 -0
- package/tsup.wasm.config.ts +26 -0
- package/vitest.config.ts +15 -0
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
// Tracker Bloat Detection - Excessive third-party scripts hurt page performance
|
|
2
|
+
// Reference: "Technical SEO for Developers - 17 Tips to Rank Higher"
|
|
3
|
+
// "Every single tracker that you put in hurts your page speed, hurts that first time to interactive"
|
|
4
|
+
|
|
5
|
+
import * as cheerio from 'cheerio';
|
|
6
|
+
import type { AuditIssue } from '../types.js';
|
|
7
|
+
|
|
8
|
+
export interface TrackerInfo {
|
|
9
|
+
name: string;
|
|
10
|
+
type: 'analytics' | 'advertising' | 'social' | 'marketing' | 'heatmap' | 'chat' | 'other';
|
|
11
|
+
domain: string;
|
|
12
|
+
impact: 'high' | 'medium' | 'low';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Known trackers and their performance impact
|
|
16
|
+
const KNOWN_TRACKERS: TrackerInfo[] = [
|
|
17
|
+
// Analytics
|
|
18
|
+
{ name: 'Google Analytics (GA4)', domain: 'googletagmanager.com', type: 'analytics', impact: 'medium' },
|
|
19
|
+
{ name: 'Google Analytics (Universal)', domain: 'google-analytics.com', type: 'analytics', impact: 'medium' },
|
|
20
|
+
{ name: 'Google Tag Manager', domain: 'googletagmanager.com', type: 'analytics', impact: 'medium' },
|
|
21
|
+
{ name: 'Segment', domain: 'segment.com', type: 'analytics', impact: 'medium' },
|
|
22
|
+
{ name: 'Mixpanel', domain: 'mixpanel.com', type: 'analytics', impact: 'medium' },
|
|
23
|
+
{ name: 'Amplitude', domain: 'amplitude.com', type: 'analytics', impact: 'medium' },
|
|
24
|
+
{ name: 'Heap', domain: 'heap.io', type: 'analytics', impact: 'medium' },
|
|
25
|
+
{ name: 'Plausible', domain: 'plausible.io', type: 'analytics', impact: 'low' },
|
|
26
|
+
{ name: 'Fathom', domain: 'usefathom.com', type: 'analytics', impact: 'low' },
|
|
27
|
+
{ name: 'Simple Analytics', domain: 'simpleanalytics.com', type: 'analytics', impact: 'low' },
|
|
28
|
+
{ name: 'PostHog', domain: 'posthog.com', type: 'analytics', impact: 'medium' },
|
|
29
|
+
|
|
30
|
+
// Advertising pixels
|
|
31
|
+
{ name: 'Facebook Pixel', domain: 'connect.facebook.net', type: 'advertising', impact: 'high' },
|
|
32
|
+
{ name: 'Meta Pixel', domain: 'facebook.com/tr', type: 'advertising', impact: 'high' },
|
|
33
|
+
{ name: 'TikTok Pixel', domain: 'analytics.tiktok.com', type: 'advertising', impact: 'high' },
|
|
34
|
+
{ name: 'Twitter/X Pixel', domain: 'static.ads-twitter.com', type: 'advertising', impact: 'high' },
|
|
35
|
+
{ name: 'LinkedIn Insight', domain: 'snap.licdn.com', type: 'advertising', impact: 'high' },
|
|
36
|
+
{ name: 'Pinterest Tag', domain: 'pintrk', type: 'advertising', impact: 'high' },
|
|
37
|
+
{ name: 'Snapchat Pixel', domain: 'sc-static.net', type: 'advertising', impact: 'high' },
|
|
38
|
+
{ name: 'Google Ads', domain: 'googleadservices.com', type: 'advertising', impact: 'high' },
|
|
39
|
+
{ name: 'Google Ads Conversion', domain: 'googleads.g.doubleclick.net', type: 'advertising', impact: 'high' },
|
|
40
|
+
{ name: 'Microsoft/Bing Ads', domain: 'bat.bing.com', type: 'advertising', impact: 'high' },
|
|
41
|
+
{ name: 'Criteo', domain: 'criteo.com', type: 'advertising', impact: 'high' },
|
|
42
|
+
{ name: 'Taboola', domain: 'taboola.com', type: 'advertising', impact: 'high' },
|
|
43
|
+
{ name: 'Outbrain', domain: 'outbrain.com', type: 'advertising', impact: 'high' },
|
|
44
|
+
|
|
45
|
+
// Marketing automation
|
|
46
|
+
{ name: 'HubSpot', domain: 'js.hs-scripts.com', type: 'marketing', impact: 'high' },
|
|
47
|
+
{ name: 'HubSpot Analytics', domain: 'js.hs-analytics.net', type: 'marketing', impact: 'high' },
|
|
48
|
+
{ name: 'Marketo', domain: 'munchkin.marketo.net', type: 'marketing', impact: 'high' },
|
|
49
|
+
{ name: 'Pardot', domain: 'pardot.com', type: 'marketing', impact: 'high' },
|
|
50
|
+
{ name: 'ActiveCampaign', domain: 'trackcmp.net', type: 'marketing', impact: 'medium' },
|
|
51
|
+
{ name: 'Klaviyo', domain: 'klaviyo.com', type: 'marketing', impact: 'medium' },
|
|
52
|
+
{ name: 'Mailchimp', domain: 'chimpstatic.com', type: 'marketing', impact: 'medium' },
|
|
53
|
+
{ name: 'Drip', domain: 'getdrip.com', type: 'marketing', impact: 'medium' },
|
|
54
|
+
|
|
55
|
+
// Heatmaps & Session Recording
|
|
56
|
+
{ name: 'Hotjar', domain: 'hotjar.com', type: 'heatmap', impact: 'high' },
|
|
57
|
+
{ name: 'Crazy Egg', domain: 'crazyegg.com', type: 'heatmap', impact: 'high' },
|
|
58
|
+
{ name: 'FullStory', domain: 'fullstory.com', type: 'heatmap', impact: 'high' },
|
|
59
|
+
{ name: 'Lucky Orange', domain: 'luckyorange.com', type: 'heatmap', impact: 'high' },
|
|
60
|
+
{ name: 'Mouseflow', domain: 'mouseflow.com', type: 'heatmap', impact: 'high' },
|
|
61
|
+
{ name: 'Clarity', domain: 'clarity.ms', type: 'heatmap', impact: 'medium' },
|
|
62
|
+
{ name: 'LogRocket', domain: 'logrocket.com', type: 'heatmap', impact: 'high' },
|
|
63
|
+
|
|
64
|
+
// Chat widgets
|
|
65
|
+
{ name: 'Intercom', domain: 'intercom.io', type: 'chat', impact: 'high' },
|
|
66
|
+
{ name: 'Drift', domain: 'drift.com', type: 'chat', impact: 'high' },
|
|
67
|
+
{ name: 'Zendesk Chat', domain: 'zopim.com', type: 'chat', impact: 'high' },
|
|
68
|
+
{ name: 'Crisp', domain: 'crisp.chat', type: 'chat', impact: 'medium' },
|
|
69
|
+
{ name: 'Tidio', domain: 'tidio.co', type: 'chat', impact: 'medium' },
|
|
70
|
+
{ name: 'LiveChat', domain: 'livechatinc.com', type: 'chat', impact: 'high' },
|
|
71
|
+
{ name: 'Freshchat', domain: 'freshchat.com', type: 'chat', impact: 'high' },
|
|
72
|
+
|
|
73
|
+
// Social widgets
|
|
74
|
+
{ name: 'Twitter Widget', domain: 'platform.twitter.com', type: 'social', impact: 'medium' },
|
|
75
|
+
{ name: 'Facebook SDK', domain: 'connect.facebook.net/en_US/sdk.js', type: 'social', impact: 'high' },
|
|
76
|
+
{ name: 'Instagram Embed', domain: 'instagram.com/embed.js', type: 'social', impact: 'medium' },
|
|
77
|
+
{ name: 'LinkedIn Badge', domain: 'platform.linkedin.com', type: 'social', impact: 'medium' },
|
|
78
|
+
{ name: 'AddThis', domain: 'addthis.com', type: 'social', impact: 'high' },
|
|
79
|
+
{ name: 'ShareThis', domain: 'sharethis.com', type: 'social', impact: 'high' },
|
|
80
|
+
|
|
81
|
+
// Other
|
|
82
|
+
{ name: 'Cloudflare Web Analytics', domain: 'cloudflareinsights.com', type: 'analytics', impact: 'low' },
|
|
83
|
+
{ name: 'Sentry', domain: 'sentry.io', type: 'other', impact: 'low' },
|
|
84
|
+
{ name: 'DataDog RUM', domain: 'datadoghq.com', type: 'other', impact: 'medium' },
|
|
85
|
+
{ name: 'New Relic', domain: 'newrelic.com', type: 'other', impact: 'medium' },
|
|
86
|
+
{ name: 'Optimizely', domain: 'optimizely.com', type: 'other', impact: 'high' },
|
|
87
|
+
{ name: 'VWO', domain: 'visualwebsiteoptimizer.com', type: 'other', impact: 'high' },
|
|
88
|
+
{ name: 'Google Optimize', domain: 'googleoptimize.com', type: 'other', impact: 'medium' },
|
|
89
|
+
];
|
|
90
|
+
|
|
91
|
+
export interface DetectedTracker extends TrackerInfo {
|
|
92
|
+
scriptUrl?: string;
|
|
93
|
+
loadMethod: 'sync' | 'async' | 'defer' | 'inline';
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export interface TrackerAnalysis {
|
|
97
|
+
trackers: DetectedTracker[];
|
|
98
|
+
totalCount: number;
|
|
99
|
+
byType: Record<string, number>;
|
|
100
|
+
byImpact: Record<string, number>;
|
|
101
|
+
estimatedImpactScore: number; // 0-100, higher is worse
|
|
102
|
+
recommendations: string[];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Detect third-party trackers and scripts in HTML
|
|
107
|
+
*/
|
|
108
|
+
export function detectTrackers(html: string): TrackerAnalysis {
|
|
109
|
+
const $ = cheerio.load(html);
|
|
110
|
+
const trackers: DetectedTracker[] = [];
|
|
111
|
+
const scripts = $('script');
|
|
112
|
+
|
|
113
|
+
scripts.each((_, el) => {
|
|
114
|
+
const $script = $(el);
|
|
115
|
+
const src = $script.attr('src') || '';
|
|
116
|
+
const inlineContent = $script.html() || '';
|
|
117
|
+
|
|
118
|
+
// Check external scripts
|
|
119
|
+
if (src) {
|
|
120
|
+
const tracker = identifyTracker(src);
|
|
121
|
+
if (tracker) {
|
|
122
|
+
trackers.push({
|
|
123
|
+
...tracker,
|
|
124
|
+
scriptUrl: src,
|
|
125
|
+
loadMethod: $script.attr('async') !== undefined ? 'async'
|
|
126
|
+
: $script.attr('defer') !== undefined ? 'defer'
|
|
127
|
+
: 'sync',
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Check inline scripts for tracker initialization
|
|
133
|
+
if (inlineContent) {
|
|
134
|
+
const inlineTrackers = identifyInlineTrackers(inlineContent);
|
|
135
|
+
trackers.push(...inlineTrackers);
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// Also check for tracking pixels in img/iframe
|
|
140
|
+
$('img[src], iframe[src]').each((_, el) => {
|
|
141
|
+
const src = $(el).attr('src') || '';
|
|
142
|
+
const tracker = identifyTracker(src);
|
|
143
|
+
if (tracker) {
|
|
144
|
+
trackers.push({
|
|
145
|
+
...tracker,
|
|
146
|
+
scriptUrl: src,
|
|
147
|
+
loadMethod: 'sync',
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// Deduplicate trackers by name
|
|
153
|
+
const uniqueTrackers = Array.from(
|
|
154
|
+
new Map(trackers.map(t => [t.name, t])).values()
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
// Calculate stats
|
|
158
|
+
const byType: Record<string, number> = {};
|
|
159
|
+
const byImpact: Record<string, number> = {};
|
|
160
|
+
|
|
161
|
+
for (const tracker of uniqueTrackers) {
|
|
162
|
+
byType[tracker.type] = (byType[tracker.type] || 0) + 1;
|
|
163
|
+
byImpact[tracker.impact] = (byImpact[tracker.impact] || 0) + 1;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Estimate performance impact (0-100)
|
|
167
|
+
const impactScore = calculateImpactScore(uniqueTrackers);
|
|
168
|
+
|
|
169
|
+
// Generate recommendations
|
|
170
|
+
const recommendations = generateRecommendations(uniqueTrackers, byType);
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
trackers: uniqueTrackers,
|
|
174
|
+
totalCount: uniqueTrackers.length,
|
|
175
|
+
byType,
|
|
176
|
+
byImpact,
|
|
177
|
+
estimatedImpactScore: impactScore,
|
|
178
|
+
recommendations,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function identifyTracker(url: string): TrackerInfo | null {
|
|
183
|
+
const urlLower = url.toLowerCase();
|
|
184
|
+
|
|
185
|
+
for (const tracker of KNOWN_TRACKERS) {
|
|
186
|
+
if (urlLower.includes(tracker.domain.toLowerCase())) {
|
|
187
|
+
return tracker;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function identifyInlineTrackers(content: string): DetectedTracker[] {
|
|
195
|
+
const trackers: DetectedTracker[] = [];
|
|
196
|
+
const contentLower = content.toLowerCase();
|
|
197
|
+
|
|
198
|
+
// Facebook Pixel
|
|
199
|
+
if (contentLower.includes('fbq(') || contentLower.includes('facebook-jssdk')) {
|
|
200
|
+
trackers.push({
|
|
201
|
+
name: 'Facebook Pixel (inline)',
|
|
202
|
+
domain: 'facebook.com',
|
|
203
|
+
type: 'advertising',
|
|
204
|
+
impact: 'high',
|
|
205
|
+
loadMethod: 'inline',
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Google Analytics
|
|
210
|
+
if (contentLower.includes('gtag(') && contentLower.includes('config')) {
|
|
211
|
+
trackers.push({
|
|
212
|
+
name: 'Google Analytics (inline)',
|
|
213
|
+
domain: 'google-analytics.com',
|
|
214
|
+
type: 'analytics',
|
|
215
|
+
impact: 'medium',
|
|
216
|
+
loadMethod: 'inline',
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// TikTok Pixel
|
|
221
|
+
if (contentLower.includes('ttq.') || contentLower.includes('tiktok')) {
|
|
222
|
+
trackers.push({
|
|
223
|
+
name: 'TikTok Pixel (inline)',
|
|
224
|
+
domain: 'tiktok.com',
|
|
225
|
+
type: 'advertising',
|
|
226
|
+
impact: 'high',
|
|
227
|
+
loadMethod: 'inline',
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// LinkedIn
|
|
232
|
+
if (contentLower.includes('_linkedin_partner_id') || contentLower.includes('linkedin')) {
|
|
233
|
+
trackers.push({
|
|
234
|
+
name: 'LinkedIn Insight (inline)',
|
|
235
|
+
domain: 'linkedin.com',
|
|
236
|
+
type: 'advertising',
|
|
237
|
+
impact: 'high',
|
|
238
|
+
loadMethod: 'inline',
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Hotjar
|
|
243
|
+
if (contentLower.includes('hotjar') || contentLower.includes('hjid')) {
|
|
244
|
+
trackers.push({
|
|
245
|
+
name: 'Hotjar (inline)',
|
|
246
|
+
domain: 'hotjar.com',
|
|
247
|
+
type: 'heatmap',
|
|
248
|
+
impact: 'high',
|
|
249
|
+
loadMethod: 'inline',
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Intercom
|
|
254
|
+
if (contentLower.includes('intercom') && contentLower.includes('app_id')) {
|
|
255
|
+
trackers.push({
|
|
256
|
+
name: 'Intercom (inline)',
|
|
257
|
+
domain: 'intercom.io',
|
|
258
|
+
type: 'chat',
|
|
259
|
+
impact: 'high',
|
|
260
|
+
loadMethod: 'inline',
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// HubSpot
|
|
265
|
+
if (contentLower.includes('_hsq') || contentLower.includes('hubspot')) {
|
|
266
|
+
trackers.push({
|
|
267
|
+
name: 'HubSpot (inline)',
|
|
268
|
+
domain: 'hubspot.com',
|
|
269
|
+
type: 'marketing',
|
|
270
|
+
impact: 'high',
|
|
271
|
+
loadMethod: 'inline',
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return trackers;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function calculateImpactScore(trackers: DetectedTracker[]): number {
|
|
279
|
+
let score = 0;
|
|
280
|
+
|
|
281
|
+
for (const tracker of trackers) {
|
|
282
|
+
// Base impact
|
|
283
|
+
const baseImpact = tracker.impact === 'high' ? 15
|
|
284
|
+
: tracker.impact === 'medium' ? 8
|
|
285
|
+
: 3;
|
|
286
|
+
|
|
287
|
+
// Load method penalty (sync is worst)
|
|
288
|
+
const loadPenalty = tracker.loadMethod === 'sync' ? 1.5
|
|
289
|
+
: tracker.loadMethod === 'inline' ? 1.2
|
|
290
|
+
: 1.0;
|
|
291
|
+
|
|
292
|
+
score += baseImpact * loadPenalty;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Cap at 100
|
|
296
|
+
return Math.min(100, Math.round(score));
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function generateRecommendations(
|
|
300
|
+
trackers: DetectedTracker[],
|
|
301
|
+
byType: Record<string, number>
|
|
302
|
+
): string[] {
|
|
303
|
+
const recommendations: string[] = [];
|
|
304
|
+
|
|
305
|
+
// Too many trackers overall
|
|
306
|
+
if (trackers.length > 5) {
|
|
307
|
+
recommendations.push(
|
|
308
|
+
`Reduce tracker count from ${trackers.length} to 5 or fewer for better page speed`
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Too many advertising pixels
|
|
313
|
+
if ((byType.advertising || 0) > 2) {
|
|
314
|
+
recommendations.push(
|
|
315
|
+
`Consider consolidating ${byType.advertising} advertising pixels - each one significantly impacts First Input Delay`
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Multiple analytics tools
|
|
320
|
+
if ((byType.analytics || 0) > 1) {
|
|
321
|
+
recommendations.push(
|
|
322
|
+
`You have ${byType.analytics} analytics tools - consider using just one comprehensive solution`
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Session recording tools
|
|
327
|
+
if ((byType.heatmap || 0) > 0) {
|
|
328
|
+
recommendations.push(
|
|
329
|
+
`Session recording tools (${byType.heatmap} found) significantly impact performance - use sparingly and only on specific pages`
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Chat widgets
|
|
334
|
+
if ((byType.chat || 0) > 1) {
|
|
335
|
+
recommendations.push(
|
|
336
|
+
`Multiple chat widgets detected (${byType.chat}) - consolidate to one solution`
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Sync loading scripts
|
|
341
|
+
const syncTrackers = trackers.filter(t => t.loadMethod === 'sync');
|
|
342
|
+
if (syncTrackers.length > 0) {
|
|
343
|
+
recommendations.push(
|
|
344
|
+
`${syncTrackers.length} tracker(s) loaded synchronously - add async/defer to improve page load`
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Suggest lightweight alternatives
|
|
349
|
+
const heavyAnalytics = trackers.filter(
|
|
350
|
+
t => t.type === 'analytics' && t.impact !== 'low'
|
|
351
|
+
);
|
|
352
|
+
if (heavyAnalytics.length > 0 && !trackers.some(t => t.impact === 'low')) {
|
|
353
|
+
recommendations.push(
|
|
354
|
+
'Consider privacy-friendly, lightweight analytics like Plausible, Fathom, or Simple Analytics'
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
return recommendations;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Main function: Analyze page for tracker bloat
|
|
363
|
+
*/
|
|
364
|
+
export function analyzeTrackerBloat(
|
|
365
|
+
html: string,
|
|
366
|
+
url: string
|
|
367
|
+
): { issues: AuditIssue[]; data: TrackerAnalysis } {
|
|
368
|
+
const issues: AuditIssue[] = [];
|
|
369
|
+
const analysis = detectTrackers(html);
|
|
370
|
+
|
|
371
|
+
// Critical: Excessive trackers (>8)
|
|
372
|
+
if (analysis.totalCount > 8) {
|
|
373
|
+
issues.push({
|
|
374
|
+
code: 'EXCESSIVE_TRACKERS',
|
|
375
|
+
severity: 'error',
|
|
376
|
+
category: 'performance',
|
|
377
|
+
title: `Excessive third-party trackers (${analysis.totalCount} detected)`,
|
|
378
|
+
description: `Found ${analysis.totalCount} third-party trackers/scripts. Each one adds to page weight and processing time.`,
|
|
379
|
+
impact: `Estimated ${analysis.estimatedImpactScore}% negative impact on Core Web Vitals (First Input Delay, LCP).`,
|
|
380
|
+
howToFix: 'Audit your trackers and remove any that are not providing value. Consolidate analytics tools and limit advertising pixels.',
|
|
381
|
+
affectedUrls: [url],
|
|
382
|
+
details: {
|
|
383
|
+
trackers: analysis.trackers.map(t => t.name),
|
|
384
|
+
byType: analysis.byType,
|
|
385
|
+
impactScore: analysis.estimatedImpactScore,
|
|
386
|
+
},
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
// Warning: Many trackers (5-8)
|
|
390
|
+
else if (analysis.totalCount > 5) {
|
|
391
|
+
issues.push({
|
|
392
|
+
code: 'MANY_TRACKERS',
|
|
393
|
+
severity: 'warning',
|
|
394
|
+
category: 'performance',
|
|
395
|
+
title: `Many third-party trackers (${analysis.totalCount} detected)`,
|
|
396
|
+
description: `Found ${analysis.totalCount} third-party trackers. This may be impacting page performance.`,
|
|
397
|
+
impact: `Estimated ${analysis.estimatedImpactScore}% negative impact on page interactivity.`,
|
|
398
|
+
howToFix: 'Review tracker necessity and consider removing underused ones. Prioritize essential analytics and business tools.',
|
|
399
|
+
affectedUrls: [url],
|
|
400
|
+
details: {
|
|
401
|
+
trackers: analysis.trackers.map(t => t.name),
|
|
402
|
+
byType: analysis.byType,
|
|
403
|
+
},
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// High-impact trackers loading synchronously
|
|
408
|
+
const syncHighImpact = analysis.trackers.filter(
|
|
409
|
+
t => t.loadMethod === 'sync' && t.impact === 'high'
|
|
410
|
+
);
|
|
411
|
+
if (syncHighImpact.length > 0) {
|
|
412
|
+
issues.push({
|
|
413
|
+
code: 'SYNC_TRACKER_LOADING',
|
|
414
|
+
severity: 'warning',
|
|
415
|
+
category: 'performance',
|
|
416
|
+
title: `High-impact trackers loading synchronously`,
|
|
417
|
+
description: `${syncHighImpact.length} high-impact tracker(s) are loading synchronously, blocking page rendering.`,
|
|
418
|
+
impact: 'Synchronous scripts block the parser and significantly delay First Contentful Paint.',
|
|
419
|
+
howToFix: 'Add async or defer attributes to tracker scripts, or load them after user interaction.',
|
|
420
|
+
affectedUrls: [url],
|
|
421
|
+
details: {
|
|
422
|
+
scripts: syncHighImpact.map(t => t.name),
|
|
423
|
+
},
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// Multiple session recording tools
|
|
428
|
+
if ((analysis.byType.heatmap || 0) > 1) {
|
|
429
|
+
issues.push({
|
|
430
|
+
code: 'MULTIPLE_SESSION_RECORDERS',
|
|
431
|
+
severity: 'warning',
|
|
432
|
+
category: 'performance',
|
|
433
|
+
title: `Multiple session recording tools detected`,
|
|
434
|
+
description: `Found ${analysis.byType.heatmap} session recording/heatmap tools. These are among the heaviest trackers.`,
|
|
435
|
+
impact: 'Session recorders capture every user interaction and can add 100-500ms to page load time each.',
|
|
436
|
+
howToFix: 'Use only one session recording tool, and consider enabling it only for specific pages or user segments.',
|
|
437
|
+
affectedUrls: [url],
|
|
438
|
+
details: {
|
|
439
|
+
tools: analysis.trackers.filter(t => t.type === 'heatmap').map(t => t.name),
|
|
440
|
+
},
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// Too many advertising pixels
|
|
445
|
+
if ((analysis.byType.advertising || 0) > 3) {
|
|
446
|
+
issues.push({
|
|
447
|
+
code: 'TOO_MANY_AD_PIXELS',
|
|
448
|
+
severity: 'warning',
|
|
449
|
+
category: 'performance',
|
|
450
|
+
title: `Too many advertising pixels (${analysis.byType.advertising} found)`,
|
|
451
|
+
description: `Found ${analysis.byType.advertising} advertising/conversion pixels. Each one makes network requests and runs JavaScript.`,
|
|
452
|
+
impact: 'Ad pixels are often the heaviest trackers. More than 3 can add 1-2 seconds to page load time.',
|
|
453
|
+
howToFix: 'Consolidate pixels using a tag manager with server-side tracking, or prioritize only your most important ad platforms.',
|
|
454
|
+
affectedUrls: [url],
|
|
455
|
+
details: {
|
|
456
|
+
pixels: analysis.trackers.filter(t => t.type === 'advertising').map(t => t.name),
|
|
457
|
+
},
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
return { issues, data: analysis };
|
|
462
|
+
}
|