recker 1.0.43 → 1.0.44
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 +47 -0
- package/dist/bin/recker-linux-x64 +0 -0
- package/dist/bin/recker-macos-x64 +0 -0
- package/dist/bin/recker-win-x64.exe +0 -0
- package/dist/bin/rek.cjs +85152 -100207
- package/dist/browser/ai/adaptive-timeout.d.ts +50 -0
- package/dist/browser/ai/adaptive-timeout.js +208 -0
- package/dist/browser/ai/client.d.ts +22 -0
- package/dist/browser/ai/client.js +294 -0
- package/dist/browser/ai/index.d.ts +14 -0
- package/dist/browser/ai/index.js +11 -0
- package/dist/browser/ai/providers/anthropic.d.ts +63 -0
- package/dist/browser/ai/providers/anthropic.js +370 -0
- package/dist/browser/ai/providers/base.d.ts +48 -0
- package/dist/browser/ai/providers/base.js +150 -0
- package/dist/browser/ai/providers/google.d.ts +59 -0
- package/dist/browser/ai/providers/google.js +305 -0
- package/dist/browser/ai/providers/ollama.d.ts +44 -0
- package/dist/browser/ai/providers/ollama.js +240 -0
- package/dist/browser/ai/providers/openai.d.ts +64 -0
- package/dist/browser/ai/providers/openai.js +298 -0
- package/dist/browser/ai/rate-limiter.d.ts +43 -0
- package/dist/browser/ai/rate-limiter.js +215 -0
- package/dist/browser/ai/vector/index.d.ts +2 -0
- package/dist/browser/ai/vector/index.js +2 -0
- package/dist/browser/ai/vector/similarity.d.ts +2 -0
- package/dist/browser/ai/vector/similarity.js +27 -0
- package/dist/browser/ai/vector/store.d.ts +27 -0
- package/dist/browser/ai/vector/store.js +82 -0
- package/dist/browser/browser/cache.d.ts +2 -40
- package/dist/browser/browser/cache.js +2 -199
- package/dist/browser/browser/index.d.ts +8 -0
- package/dist/browser/browser/index.js +8 -0
- package/dist/browser/browser/recker.d.ts +8 -1
- package/dist/browser/browser/recker.js +8 -2
- package/dist/browser/cache/indexed-db.d.ts +10 -0
- package/dist/browser/cache/indexed-db.js +88 -0
- package/dist/browser/cache/service-worker-cache.d.ts +18 -0
- package/dist/browser/cache/service-worker-cache.js +103 -0
- package/dist/browser/cache.d.ts +2 -40
- package/dist/browser/cache.js +2 -199
- package/dist/browser/constants/user-agents.d.ts +7 -0
- package/dist/browser/constants/user-agents.js +7 -0
- package/dist/browser/core/client.d.ts +2 -0
- package/dist/browser/core/client.js +19 -1
- package/dist/browser/index.d.ts +8 -0
- package/dist/browser/index.js +8 -0
- package/dist/browser/plugins/har-recorder.d.ts +40 -0
- package/dist/browser/plugins/har-recorder.js +120 -0
- package/dist/browser/plugins/network-simulation.d.ts +7 -0
- package/dist/browser/plugins/network-simulation.js +13 -0
- package/dist/browser/presets/android.d.ts +2 -0
- package/dist/browser/presets/android.js +16 -0
- package/dist/browser/presets/anthropic.d.ts +8 -0
- package/dist/browser/presets/anthropic.js +27 -0
- package/dist/browser/presets/aws.d.ts +19 -0
- package/dist/browser/presets/aws.js +68 -0
- package/dist/browser/presets/azure-openai.d.ts +10 -0
- package/dist/browser/presets/azure-openai.js +35 -0
- package/dist/browser/presets/azure.d.ts +41 -0
- package/dist/browser/presets/azure.js +104 -0
- package/dist/browser/presets/chaturbate.d.ts +2 -0
- package/dist/browser/presets/chaturbate.js +17 -0
- package/dist/browser/presets/cloudflare.d.ts +12 -0
- package/dist/browser/presets/cloudflare.js +39 -0
- package/dist/browser/presets/cohere.d.ts +7 -0
- package/dist/browser/presets/cohere.js +22 -0
- package/dist/browser/presets/deepseek.d.ts +7 -0
- package/dist/browser/presets/deepseek.js +22 -0
- package/dist/browser/presets/digitalocean.d.ts +5 -0
- package/dist/browser/presets/digitalocean.js +16 -0
- package/dist/browser/presets/discord.d.ts +6 -0
- package/dist/browser/presets/discord.js +17 -0
- package/dist/browser/presets/elevenlabs.d.ts +6 -0
- package/dist/browser/presets/elevenlabs.js +20 -0
- package/dist/browser/presets/enhancers.d.ts +20 -0
- package/dist/browser/presets/enhancers.js +85 -0
- package/dist/browser/presets/fireworks.d.ts +7 -0
- package/dist/browser/presets/fireworks.js +22 -0
- package/dist/browser/presets/gcp.d.ts +34 -0
- package/dist/browser/presets/gcp.js +91 -0
- package/dist/browser/presets/gemini.d.ts +7 -0
- package/dist/browser/presets/gemini.js +23 -0
- package/dist/browser/presets/github.d.ts +6 -0
- package/dist/browser/presets/github.js +17 -0
- package/dist/browser/presets/gitlab.d.ts +6 -0
- package/dist/browser/presets/gitlab.js +16 -0
- package/dist/browser/presets/groq.d.ts +7 -0
- package/dist/browser/presets/groq.js +22 -0
- package/dist/browser/presets/hubspot.d.ts +9 -0
- package/dist/browser/presets/hubspot.js +28 -0
- package/dist/browser/presets/huggingface.d.ts +7 -0
- package/dist/browser/presets/huggingface.js +23 -0
- package/dist/browser/presets/index.d.ts +47 -0
- package/dist/browser/presets/index.js +47 -0
- package/dist/browser/presets/ios.d.ts +2 -0
- package/dist/browser/presets/ios.js +13 -0
- package/dist/browser/presets/linear.d.ts +5 -0
- package/dist/browser/presets/linear.js +16 -0
- package/dist/browser/presets/mailgun.d.ts +7 -0
- package/dist/browser/presets/mailgun.js +20 -0
- package/dist/browser/presets/meta.d.ts +10 -0
- package/dist/browser/presets/meta.js +33 -0
- package/dist/browser/presets/mistral.d.ts +7 -0
- package/dist/browser/presets/mistral.js +22 -0
- package/dist/browser/presets/notion.d.ts +6 -0
- package/dist/browser/presets/notion.js +17 -0
- package/dist/browser/presets/openai.d.ts +9 -0
- package/dist/browser/presets/openai.js +30 -0
- package/dist/browser/presets/oracle.d.ts +19 -0
- package/dist/browser/presets/oracle.js +117 -0
- package/dist/browser/presets/perplexity.d.ts +7 -0
- package/dist/browser/presets/perplexity.js +22 -0
- package/dist/browser/presets/pinecone.d.ts +8 -0
- package/dist/browser/presets/pinecone.js +42 -0
- package/dist/browser/presets/registry.d.ts +23 -0
- package/dist/browser/presets/registry.js +519 -0
- package/dist/browser/presets/replicate.d.ts +7 -0
- package/dist/browser/presets/replicate.js +23 -0
- package/dist/browser/presets/sendgrid.d.ts +6 -0
- package/dist/browser/presets/sendgrid.js +20 -0
- package/dist/browser/presets/sentry.d.ts +11 -0
- package/dist/browser/presets/sentry.js +48 -0
- package/dist/browser/presets/sinch.d.ts +9 -0
- package/dist/browser/presets/sinch.js +39 -0
- package/dist/browser/presets/slack.d.ts +5 -0
- package/dist/browser/presets/slack.js +16 -0
- package/dist/browser/presets/square.d.ts +10 -0
- package/dist/browser/presets/square.js +33 -0
- package/dist/browser/presets/stripe.d.ts +7 -0
- package/dist/browser/presets/stripe.js +23 -0
- package/dist/browser/presets/supabase.d.ts +6 -0
- package/dist/browser/presets/supabase.js +18 -0
- package/dist/browser/presets/tiktok.d.ts +10 -0
- package/dist/browser/presets/tiktok.js +38 -0
- package/dist/browser/presets/together.d.ts +7 -0
- package/dist/browser/presets/together.js +22 -0
- package/dist/browser/presets/twilio.d.ts +6 -0
- package/dist/browser/presets/twilio.js +17 -0
- package/dist/browser/presets/vercel.d.ts +6 -0
- package/dist/browser/presets/vercel.js +23 -0
- package/dist/browser/presets/vultr.d.ts +5 -0
- package/dist/browser/presets/vultr.js +16 -0
- package/dist/browser/presets/xai.d.ts +8 -0
- package/dist/browser/presets/xai.js +23 -0
- package/dist/browser/presets/youtube.d.ts +5 -0
- package/dist/browser/presets/youtube.js +20 -0
- package/dist/browser/recker.d.ts +8 -1
- package/dist/browser/recker.js +8 -2
- package/dist/browser/scrape/document.d.ts +5 -4
- package/dist/browser/scrape/document.js +89 -76
- package/dist/browser/scrape/element.d.ts +10 -8
- package/dist/browser/scrape/element.js +295 -81
- package/dist/browser/scrape/extractors.d.ts +11 -11
- package/dist/browser/scrape/extractors.js +145 -113
- package/dist/browser/scrape/parser/back.d.ts +1 -0
- package/dist/browser/scrape/parser/back.js +3 -0
- package/dist/browser/scrape/parser/index.d.ts +20 -0
- package/dist/browser/scrape/parser/index.js +19 -0
- package/dist/browser/scrape/parser/matcher.d.ts +30 -0
- package/dist/browser/scrape/parser/matcher.js +99 -0
- package/dist/browser/scrape/parser/nodes/comment.d.ts +12 -0
- package/dist/browser/scrape/parser/nodes/comment.js +21 -0
- package/dist/browser/scrape/parser/nodes/html.d.ts +110 -0
- package/dist/browser/scrape/parser/nodes/html.js +978 -0
- package/dist/browser/scrape/parser/nodes/node.d.ts +18 -0
- package/dist/browser/scrape/parser/nodes/node.js +31 -0
- package/dist/browser/scrape/parser/nodes/text.d.ts +14 -0
- package/dist/browser/scrape/parser/nodes/text.js +30 -0
- package/dist/browser/scrape/parser/nodes/type.d.ts +6 -0
- package/dist/browser/scrape/parser/nodes/type.js +7 -0
- package/dist/browser/scrape/parser/parse.d.ts +1 -0
- package/dist/browser/scrape/parser/parse.js +1 -0
- package/dist/browser/scrape/parser/valid.d.ts +2 -0
- package/dist/browser/scrape/parser/valid.js +5 -0
- package/dist/browser/scrape/parser/void-tag.d.ts +7 -0
- package/dist/browser/scrape/parser/void-tag.js +43 -0
- package/dist/browser/scrape/types.d.ts +7 -0
- package/dist/browser/seo/analyzer.d.ts +59 -0
- package/dist/browser/seo/analyzer.js +1399 -0
- package/dist/browser/seo/keywords.d.ts +16 -0
- package/dist/browser/seo/keywords.js +55 -0
- package/dist/browser/seo/rules/accessibility.d.ts +2 -0
- package/dist/browser/seo/rules/accessibility.js +733 -0
- package/dist/browser/seo/rules/ai-search.d.ts +2 -0
- package/dist/browser/seo/rules/ai-search.js +436 -0
- package/dist/browser/seo/rules/analytics.d.ts +2 -0
- package/dist/browser/seo/rules/analytics.js +306 -0
- package/dist/browser/seo/rules/best-practices.d.ts +2 -0
- package/dist/browser/seo/rules/best-practices.js +195 -0
- package/dist/browser/seo/rules/canonical.d.ts +12 -0
- package/dist/browser/seo/rules/canonical.js +270 -0
- package/dist/browser/seo/rules/content.d.ts +2 -0
- package/dist/browser/seo/rules/content.js +522 -0
- package/dist/browser/seo/rules/crawl.d.ts +2 -0
- package/dist/browser/seo/rules/crawl.js +435 -0
- package/dist/browser/seo/rules/cwv.d.ts +2 -0
- package/dist/browser/seo/rules/cwv.js +248 -0
- package/dist/browser/seo/rules/ecommerce.d.ts +2 -0
- package/dist/browser/seo/rules/ecommerce.js +312 -0
- package/dist/browser/seo/rules/i18n.d.ts +2 -0
- package/dist/browser/seo/rules/i18n.js +288 -0
- package/dist/browser/seo/rules/images.d.ts +2 -0
- package/dist/browser/seo/rules/images.js +255 -0
- package/dist/browser/seo/rules/index.d.ts +52 -0
- package/dist/browser/seo/rules/index.js +159 -0
- package/dist/browser/seo/rules/internal-linking.d.ts +2 -0
- package/dist/browser/seo/rules/internal-linking.js +394 -0
- package/dist/browser/seo/rules/links.d.ts +2 -0
- package/dist/browser/seo/rules/links.js +498 -0
- package/dist/browser/seo/rules/local.d.ts +2 -0
- package/dist/browser/seo/rules/local.js +289 -0
- package/dist/browser/seo/rules/meta.d.ts +2 -0
- package/dist/browser/seo/rules/meta.js +805 -0
- package/dist/browser/seo/rules/mobile.d.ts +2 -0
- package/dist/browser/seo/rules/mobile.js +161 -0
- package/dist/browser/seo/rules/performance.d.ts +2 -0
- package/dist/browser/seo/rules/performance.js +738 -0
- package/dist/browser/seo/rules/pwa.d.ts +2 -0
- package/dist/browser/seo/rules/pwa.js +299 -0
- package/dist/browser/seo/rules/readability.d.ts +2 -0
- package/dist/browser/seo/rules/readability.js +264 -0
- package/dist/browser/seo/rules/redirects.d.ts +16 -0
- package/dist/browser/seo/rules/redirects.js +199 -0
- package/dist/browser/seo/rules/resources.d.ts +2 -0
- package/dist/browser/seo/rules/resources.js +390 -0
- package/dist/browser/seo/rules/schema.d.ts +2 -0
- package/dist/browser/seo/rules/schema.js +379 -0
- package/dist/browser/seo/rules/security.d.ts +2 -0
- package/dist/browser/seo/rules/security.js +877 -0
- package/dist/browser/seo/rules/social.d.ts +2 -0
- package/dist/browser/seo/rules/social.js +603 -0
- package/dist/browser/seo/rules/structural.d.ts +2 -0
- package/dist/browser/seo/rules/structural.js +223 -0
- package/dist/browser/seo/rules/technical-advanced.d.ts +10 -0
- package/dist/browser/seo/rules/technical-advanced.js +289 -0
- package/dist/browser/seo/rules/technical.d.ts +2 -0
- package/dist/browser/seo/rules/technical.js +480 -0
- package/dist/browser/seo/rules/thresholds.d.ts +196 -0
- package/dist/browser/seo/rules/thresholds.js +118 -0
- package/dist/browser/seo/rules/types.d.ts +498 -0
- package/dist/browser/seo/rules/types.js +11 -0
- package/dist/browser/seo/types.d.ts +211 -0
- package/dist/browser/seo/types.js +1 -0
- package/dist/browser/transport/curl.d.ts +4 -0
- package/dist/browser/transport/curl.js +101 -0
- package/dist/browser/transport/undici.js +1 -2
- package/dist/browser/transport/worker.d.ts +18 -0
- package/dist/browser/transport/worker.js +278 -0
- package/dist/browser/types/index.d.ts +4 -1
- package/dist/browser/utils/binary-manager.d.ts +4 -0
- package/dist/browser/utils/binary-manager.js +72 -0
- package/dist/browser/utils/user-agent.js +2 -13
- package/dist/cache/indexed-db.d.ts +10 -0
- package/dist/cache/indexed-db.js +88 -0
- package/dist/cache/service-worker-cache.d.ts +18 -0
- package/dist/cache/service-worker-cache.js +103 -0
- package/dist/cli/commands/ai.d.ts +2 -0
- package/dist/cli/commands/ai.js +162 -0
- package/dist/cli/commands/bench.d.ts +2 -0
- package/dist/cli/commands/bench.js +51 -0
- package/dist/cli/commands/dns.d.ts +2 -0
- package/dist/cli/commands/dns.js +295 -0
- package/dist/cli/commands/har.d.ts +2 -0
- package/dist/cli/commands/har.js +171 -0
- package/dist/cli/commands/hls.d.ts +2 -0
- package/dist/cli/commands/hls.js +192 -0
- package/dist/cli/commands/network.d.ts +2 -0
- package/dist/cli/commands/network.js +288 -0
- package/dist/cli/commands/protocols.d.ts +2 -0
- package/dist/cli/commands/protocols.js +344 -0
- package/dist/cli/commands/scrape.d.ts +2 -0
- package/dist/cli/commands/scrape.js +176 -0
- package/dist/cli/commands/security.d.ts +2 -0
- package/dist/cli/commands/security.js +57 -0
- package/dist/cli/commands/seo.d.ts +2 -0
- package/dist/cli/commands/seo.js +125 -0
- package/dist/cli/commands/serve.d.ts +2 -0
- package/dist/cli/commands/serve.js +531 -0
- package/dist/cli/commands/spider.d.ts +3 -0
- package/dist/cli/commands/spider.js +456 -0
- package/dist/cli/commands/utils.d.ts +2 -0
- package/dist/cli/commands/utils.js +176 -0
- package/dist/cli/commands/vector.d.ts +2 -0
- package/dist/cli/commands/vector.js +158 -0
- package/dist/cli/handler.d.ts +2 -2
- package/dist/cli/handler.js +6 -6
- package/dist/cli/helpers.d.ts +7 -0
- package/dist/cli/helpers.js +128 -0
- package/dist/cli/index.js +96 -5228
- package/dist/cli/parser/help.d.ts +2 -0
- package/dist/cli/parser/help.js +52 -0
- package/dist/cli/parser/index.d.ts +3 -0
- package/dist/cli/parser/index.js +3 -0
- package/dist/cli/parser/parser.d.ts +4 -0
- package/dist/cli/parser/parser.js +146 -0
- package/dist/cli/parser/types.d.ts +41 -0
- package/dist/cli/parser/types.js +1 -0
- package/dist/cli/presets.d.ts +1 -1
- package/dist/cli/presets.js +1 -1
- package/dist/cli/router.d.ts +36 -0
- package/dist/cli/router.js +195 -0
- package/dist/cli/tui/ai-chat.js +1 -1
- package/dist/cli/tui/commands/context.d.ts +9 -0
- package/dist/cli/tui/commands/context.js +1 -0
- package/dist/cli/tui/commands/dns.d.ts +10 -0
- package/dist/cli/tui/commands/dns.js +461 -0
- package/dist/cli/tui/commands/hls.d.ts +2 -0
- package/dist/cli/tui/commands/hls.js +162 -0
- package/dist/cli/tui/commands/ip.d.ts +2 -0
- package/dist/cli/tui/commands/ip.js +45 -0
- package/dist/cli/tui/commands/network.d.ts +3 -0
- package/dist/cli/tui/commands/network.js +81 -0
- package/dist/cli/tui/commands/protocols.d.ts +6 -0
- package/dist/cli/tui/commands/protocols.js +531 -0
- package/dist/cli/tui/commands/security.d.ts +2 -0
- package/dist/cli/tui/commands/security.js +48 -0
- package/dist/cli/tui/commands/seo.d.ts +2 -0
- package/dist/cli/tui/commands/seo.js +74 -0
- package/dist/cli/tui/context.d.ts +12 -0
- package/dist/cli/tui/context.js +1 -0
- package/dist/cli/tui/shell.d.ts +11 -20
- package/dist/cli/tui/shell.js +216 -1873
- package/dist/constants/user-agents.d.ts +7 -0
- package/dist/constants/user-agents.js +7 -0
- package/dist/core/client.d.ts +2 -0
- package/dist/core/client.js +19 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/mcp/cli.js +2 -3
- package/dist/mcp/data/embeddings.json +1 -1
- package/dist/mcp/tools/network.js +298 -158
- package/dist/plugins/har-player.d.ts +23 -0
- package/dist/plugins/har-player.js +49 -0
- package/dist/plugins/har-recorder.d.ts +37 -3
- package/dist/plugins/har-recorder.js +116 -63
- package/dist/plugins/network-simulation.d.ts +7 -0
- package/dist/plugins/network-simulation.js +13 -0
- package/dist/presets/android.d.ts +2 -0
- package/dist/presets/android.js +16 -0
- package/dist/presets/chaturbate.d.ts +2 -0
- package/dist/presets/chaturbate.js +17 -0
- package/dist/presets/elevenlabs.d.ts +6 -0
- package/dist/presets/elevenlabs.js +20 -0
- package/dist/presets/enhancers.d.ts +20 -0
- package/dist/presets/enhancers.js +85 -0
- package/dist/presets/hubspot.d.ts +9 -0
- package/dist/presets/hubspot.js +28 -0
- package/dist/presets/index.d.ts +10 -0
- package/dist/presets/index.js +10 -0
- package/dist/presets/ios.d.ts +2 -0
- package/dist/presets/ios.js +13 -0
- package/dist/presets/pinecone.d.ts +8 -0
- package/dist/presets/pinecone.js +42 -0
- package/dist/presets/registry.js +60 -0
- package/dist/presets/sendgrid.d.ts +6 -0
- package/dist/presets/sendgrid.js +20 -0
- package/dist/presets/sentry.d.ts +11 -0
- package/dist/presets/sentry.js +48 -0
- package/dist/presets/square.d.ts +10 -0
- package/dist/presets/square.js +33 -0
- package/dist/recker.d.ts +3 -0
- package/dist/recker.js +4 -0
- package/dist/scrape/document.d.ts +5 -4
- package/dist/scrape/document.js +89 -76
- package/dist/scrape/element.d.ts +10 -8
- package/dist/scrape/element.js +295 -81
- package/dist/scrape/extractors.d.ts +11 -11
- package/dist/scrape/extractors.js +145 -113
- package/dist/scrape/index.d.ts +2 -0
- package/dist/scrape/index.js +1 -0
- package/dist/scrape/parser/back.d.ts +1 -0
- package/dist/scrape/parser/back.js +3 -0
- package/dist/scrape/parser/index.d.ts +20 -0
- package/dist/scrape/parser/index.js +19 -0
- package/dist/scrape/parser/matcher.d.ts +30 -0
- package/dist/scrape/parser/matcher.js +99 -0
- package/dist/scrape/parser/nodes/comment.d.ts +12 -0
- package/dist/scrape/parser/nodes/comment.js +21 -0
- package/dist/scrape/parser/nodes/html.d.ts +110 -0
- package/dist/scrape/parser/nodes/html.js +978 -0
- package/dist/scrape/parser/nodes/node.d.ts +18 -0
- package/dist/scrape/parser/nodes/node.js +31 -0
- package/dist/scrape/parser/nodes/text.d.ts +14 -0
- package/dist/scrape/parser/nodes/text.js +30 -0
- package/dist/scrape/parser/nodes/type.d.ts +6 -0
- package/dist/scrape/parser/nodes/type.js +7 -0
- package/dist/scrape/parser/parse.d.ts +1 -0
- package/dist/scrape/parser/parse.js +1 -0
- package/dist/scrape/parser/valid.d.ts +2 -0
- package/dist/scrape/parser/valid.js +5 -0
- package/dist/scrape/parser/void-tag.d.ts +7 -0
- package/dist/scrape/parser/void-tag.js +43 -0
- package/dist/scrape/spider.d.ts +19 -0
- package/dist/scrape/spider.js +28 -3
- package/dist/scrape/types.d.ts +7 -0
- package/dist/seo/analyzer.d.ts +15 -5
- package/dist/seo/analyzer.js +636 -175
- package/dist/seo/formatter.d.ts +16 -0
- package/dist/seo/formatter.js +228 -0
- package/dist/seo/index.d.ts +2 -0
- package/dist/seo/index.js +1 -0
- package/dist/seo/keywords.d.ts +16 -0
- package/dist/seo/keywords.js +55 -0
- package/dist/seo/rules/accessibility.js +96 -57
- package/dist/seo/rules/ai-search.js +44 -31
- package/dist/seo/rules/analytics.d.ts +2 -0
- package/dist/seo/rules/analytics.js +306 -0
- package/dist/seo/rules/best-practices.js +21 -14
- package/dist/seo/rules/canonical.js +53 -32
- package/dist/seo/rules/content.js +317 -31
- package/dist/seo/rules/crawl.js +55 -40
- package/dist/seo/rules/cwv.js +21 -15
- package/dist/seo/rules/ecommerce.js +82 -22
- package/dist/seo/rules/i18n.js +75 -36
- package/dist/seo/rules/images.js +109 -30
- package/dist/seo/rules/index.js +2 -0
- package/dist/seo/rules/internal-linking.js +58 -39
- package/dist/seo/rules/links.js +79 -52
- package/dist/seo/rules/local.js +49 -25
- package/dist/seo/rules/meta.js +339 -81
- package/dist/seo/rules/mobile.js +112 -2
- package/dist/seo/rules/performance.js +434 -66
- package/dist/seo/rules/pwa.js +36 -39
- package/dist/seo/rules/readability.js +31 -22
- package/dist/seo/rules/redirects.js +21 -15
- package/dist/seo/rules/resources.js +59 -42
- package/dist/seo/rules/schema.js +333 -8
- package/dist/seo/rules/security.js +142 -80
- package/dist/seo/rules/social.js +277 -47
- package/dist/seo/rules/structural.js +87 -19
- package/dist/seo/rules/technical-advanced.js +30 -24
- package/dist/seo/rules/technical.js +243 -42
- package/dist/seo/rules/types.d.ts +53 -1
- package/dist/seo/seo-spider.d.ts +22 -0
- package/dist/seo/seo-spider.js +77 -13
- package/dist/seo/types.d.ts +8 -1
- package/dist/seo/validators/llms-txt.js +19 -0
- package/dist/seo/validators/rss.d.ts +11 -0
- package/dist/seo/validators/rss.js +93 -0
- package/dist/seo/validators/sitemap.js +36 -26
- package/dist/transport/curl.d.ts +4 -0
- package/dist/transport/curl.js +101 -0
- package/dist/transport/udp.js +0 -1
- package/dist/transport/undici.js +1 -2
- package/dist/transport/worker.d.ts +18 -0
- package/dist/transport/worker.js +278 -0
- package/dist/types/index.d.ts +4 -1
- package/dist/utils/binary-manager.d.ts +4 -0
- package/dist/utils/binary-manager.js +72 -0
- package/dist/utils/optional-require.d.ts +7 -8
- package/dist/utils/optional-require.js +2 -21
- package/dist/utils/upload.d.ts +6 -0
- package/dist/utils/upload.js +11 -0
- package/dist/utils/user-agent.js +2 -13
- package/dist/version.js +1 -1
- package/package.json +12 -6
- package/dist/browser/utils/optional-require.d.ts +0 -19
- package/dist/browser/utils/optional-require.js +0 -105
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
import { createResult } from './types.js';
|
|
2
|
+
export const analyticsRules = [
|
|
3
|
+
{
|
|
4
|
+
id: 'analytics-installed',
|
|
5
|
+
name: 'Analytics Installed',
|
|
6
|
+
category: 'technical',
|
|
7
|
+
severity: 'warning',
|
|
8
|
+
description: 'Page should have analytics tracking installed',
|
|
9
|
+
check: (ctx) => {
|
|
10
|
+
if (ctx.analyticsDetected === undefined) {
|
|
11
|
+
return createResult({ id: 'analytics-installed', name: 'Analytics Installed', category: 'technical', severity: 'warning' }, 'info', 'Not applicable (analytics detection data unavailable)', { recommendation: 'This rule checks for analytics tracking to measure user behavior' });
|
|
12
|
+
}
|
|
13
|
+
if (!ctx.analyticsDetected || ctx.analyticsProviders?.length === 0) {
|
|
14
|
+
return createResult({ id: 'analytics-installed', name: 'Analytics Installed', category: 'technical', severity: 'warning' }, 'warn', 'No analytics tracking detected', {
|
|
15
|
+
recommendation: 'Install analytics to understand user behavior and optimize conversions.',
|
|
16
|
+
evidence: {
|
|
17
|
+
found: 'No analytics scripts detected',
|
|
18
|
+
expected: 'Google Analytics, GTM, or other analytics provider',
|
|
19
|
+
impact: 'Without analytics, you cannot measure traffic sources, user behavior, conversion rates, or content performance. The average conversion rate is only 2.35% - optimization requires data.',
|
|
20
|
+
example: '<!-- Google Analytics 4 -->\n<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXX"></script>',
|
|
21
|
+
learnMore: 'https://marketingplatform.google.com/about/analytics/',
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
return createResult({ id: 'analytics-installed', name: 'Analytics Installed', category: 'technical', severity: 'warning' }, 'pass', `Analytics detected: ${ctx.analyticsProviders?.join(', ')}`, { value: ctx.analyticsProviders?.join(', ') });
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
id: 'google-analytics-4',
|
|
30
|
+
name: 'Google Analytics 4',
|
|
31
|
+
category: 'technical',
|
|
32
|
+
severity: 'info',
|
|
33
|
+
description: 'Check for modern GA4 vs deprecated Universal Analytics',
|
|
34
|
+
check: (ctx) => {
|
|
35
|
+
if (!ctx.analyticsProviders || ctx.analyticsProviders.length === 0) {
|
|
36
|
+
return createResult({ id: 'google-analytics-4', name: 'Google Analytics 4', category: 'technical', severity: 'info' }, 'info', 'Not applicable (no analytics providers detected)', { recommendation: 'This rule checks for modern Google Analytics 4 implementation' });
|
|
37
|
+
}
|
|
38
|
+
const hasUA = ctx.analyticsProviders.some(p => p === 'Universal Analytics (UA)');
|
|
39
|
+
const hasGA4 = ctx.analyticsProviders.some(p => p === 'Google Analytics 4 (GA4)');
|
|
40
|
+
if (hasUA && !hasGA4) {
|
|
41
|
+
return createResult({ id: 'google-analytics-4', name: 'Google Analytics 4', category: 'technical', severity: 'info' }, 'warn', 'Using deprecated Universal Analytics (UA) without GA4', {
|
|
42
|
+
recommendation: 'Migrate to Google Analytics 4 - Universal Analytics stopped processing data July 2023.',
|
|
43
|
+
evidence: {
|
|
44
|
+
found: 'Universal Analytics (analytics.js or ga.js)',
|
|
45
|
+
expected: 'Google Analytics 4 (gtag.js with G-XXXXXX)',
|
|
46
|
+
impact: 'Universal Analytics is deprecated and no longer collects data. GA4 offers better cross-platform tracking, machine learning insights, and privacy-focused measurement.',
|
|
47
|
+
example: '<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXX"></script>',
|
|
48
|
+
learnMore: 'https://support.google.com/analytics/answer/11583528',
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
if (hasGA4) {
|
|
53
|
+
return createResult({ id: 'google-analytics-4', name: 'Google Analytics 4', category: 'technical', severity: 'info' }, 'pass', 'Using modern Google Analytics 4');
|
|
54
|
+
}
|
|
55
|
+
return createResult({ id: 'google-analytics-4', name: 'Google Analytics 4', category: 'technical', severity: 'info' }, 'pass', 'Using other analytics provider (not Google Analytics)');
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
id: 'google-tag-manager',
|
|
60
|
+
name: 'Google Tag Manager',
|
|
61
|
+
category: 'technical',
|
|
62
|
+
severity: 'info',
|
|
63
|
+
description: 'Google Tag Manager enables flexible tag management without code changes',
|
|
64
|
+
check: (ctx) => {
|
|
65
|
+
if (!ctx.analyticsProviders) {
|
|
66
|
+
return createResult({ id: 'google-tag-manager', name: 'Google Tag Manager', category: 'technical', severity: 'info' }, 'info', 'Not applicable (analytics provider data unavailable)', { recommendation: 'This rule checks for Google Tag Manager for easier tag management' });
|
|
67
|
+
}
|
|
68
|
+
const hasGTM = ctx.analyticsProviders.some(p => p === 'Google Tag Manager');
|
|
69
|
+
if (hasGTM) {
|
|
70
|
+
return createResult({ id: 'google-tag-manager', name: 'Google Tag Manager', category: 'technical', severity: 'info' }, 'pass', 'Google Tag Manager detected', {
|
|
71
|
+
evidence: {
|
|
72
|
+
found: 'GTM container script',
|
|
73
|
+
impact: 'GTM allows marketing teams to manage tags without developer intervention, improving agility.',
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
if (ctx.analyticsDetected && ctx.analyticsProviders.length > 0) {
|
|
78
|
+
return createResult({ id: 'google-tag-manager', name: 'Google Tag Manager', category: 'technical', severity: 'info' }, 'info', 'Consider using Google Tag Manager for easier tag management', {
|
|
79
|
+
recommendation: 'GTM centralizes all tracking scripts and enables non-developers to manage tags.',
|
|
80
|
+
evidence: {
|
|
81
|
+
found: `Direct analytics scripts: ${ctx.analyticsProviders.join(', ')}`,
|
|
82
|
+
expected: 'Google Tag Manager container managing all tags',
|
|
83
|
+
impact: 'GTM provides version control, preview mode, and easy updates without code changes. Best practice for sites with multiple tracking tools.',
|
|
84
|
+
learnMore: 'https://tagmanager.google.com/',
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
return createResult({ id: 'google-tag-manager', name: 'Google Tag Manager', category: 'technical', severity: 'info' }, 'pass', 'No analytics detected or GTM not needed');
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
id: 'multiple-analytics-warning',
|
|
93
|
+
name: 'Multiple Analytics Scripts',
|
|
94
|
+
category: 'performance',
|
|
95
|
+
severity: 'warning',
|
|
96
|
+
description: 'Too many analytics scripts can impact page performance',
|
|
97
|
+
check: (ctx) => {
|
|
98
|
+
if (!ctx.analyticsProviders || ctx.analyticsProviders.length < 4) {
|
|
99
|
+
return createResult({ id: 'multiple-analytics-warning', name: 'Multiple Analytics Scripts', category: 'performance', severity: 'warning' }, 'info', 'Not applicable (acceptable number of analytics scripts)', { recommendation: 'This rule warns when too many analytics scripts impact performance' });
|
|
100
|
+
}
|
|
101
|
+
return createResult({ id: 'multiple-analytics-warning', name: 'Multiple Analytics Scripts', category: 'performance', severity: 'warning' }, 'warn', `${ctx.analyticsProviders.length} analytics scripts detected`, {
|
|
102
|
+
value: ctx.analyticsProviders.length,
|
|
103
|
+
recommendation: 'Consolidate analytics tools to reduce page load impact.',
|
|
104
|
+
evidence: {
|
|
105
|
+
found: ctx.analyticsProviders.join(', '),
|
|
106
|
+
expected: '1-3 analytics tools',
|
|
107
|
+
impact: 'Each analytics script adds HTTP requests, JavaScript execution time, and can slow page rendering. Too many tools can impact Core Web Vitals and user experience.',
|
|
108
|
+
learnMore: 'https://web.dev/articles/optimize-third-party-javascript',
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
id: 'heatmap-tool',
|
|
115
|
+
name: 'Heatmap/Session Recording',
|
|
116
|
+
category: 'technical',
|
|
117
|
+
severity: 'info',
|
|
118
|
+
description: 'Heatmap tools like Hotjar help visualize user behavior',
|
|
119
|
+
check: (ctx) => {
|
|
120
|
+
if (!ctx.analyticsProviders) {
|
|
121
|
+
return createResult({ id: 'heatmap-tool', name: 'Heatmap Tool', category: 'technical', severity: 'info' }, 'info', 'Not applicable (analytics provider data unavailable)', { recommendation: 'This rule checks for heatmap and session recording tools' });
|
|
122
|
+
}
|
|
123
|
+
const heatmapTools = ['Hotjar', 'Microsoft Clarity', 'FullStory', 'Lucky Orange', 'Crazy Egg'];
|
|
124
|
+
const foundHeatmap = ctx.analyticsProviders.filter(p => heatmapTools.includes(p));
|
|
125
|
+
if (foundHeatmap.length > 0) {
|
|
126
|
+
return createResult({ id: 'heatmap-tool', name: 'Heatmap Tool', category: 'technical', severity: 'info' }, 'pass', `Heatmap/session recording detected: ${foundHeatmap.join(', ')}`, {
|
|
127
|
+
evidence: {
|
|
128
|
+
found: foundHeatmap.join(', '),
|
|
129
|
+
impact: 'Heatmaps and session recordings help identify UX issues, understand user journeys, and optimize conversion paths.',
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
return createResult({ id: 'heatmap-tool', name: 'Heatmap Tool', category: 'technical', severity: 'info' }, 'pass', 'No heatmap tool detected (optional)');
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
id: 'cta-presence',
|
|
138
|
+
name: 'Call-to-Action Elements',
|
|
139
|
+
category: 'content',
|
|
140
|
+
severity: 'info',
|
|
141
|
+
description: 'Page should have clear call-to-action elements',
|
|
142
|
+
check: (ctx) => {
|
|
143
|
+
if (ctx.ctaButtonsCount === undefined) {
|
|
144
|
+
return createResult({ id: 'cta-presence', name: 'Call-to-Action Elements', category: 'content', severity: 'info' }, 'info', 'Not applicable (CTA button data unavailable)', { recommendation: 'This rule checks for clear call-to-action elements to guide conversions' });
|
|
145
|
+
}
|
|
146
|
+
if (ctx.ctaButtonsCount === 0) {
|
|
147
|
+
return createResult({ id: 'cta-presence', name: 'Call-to-Action Elements', category: 'content', severity: 'info' }, 'info', 'No clear call-to-action buttons detected', {
|
|
148
|
+
recommendation: 'Add clear CTAs to guide users toward conversion.',
|
|
149
|
+
evidence: {
|
|
150
|
+
found: 'No buttons with action-oriented text',
|
|
151
|
+
expected: 'Buttons like "Get Started", "Sign Up", "Buy Now", "Contact Us"',
|
|
152
|
+
impact: 'Clear CTAs guide users through the conversion funnel. Pages without obvious next steps lose potential conversions.',
|
|
153
|
+
example: '<button class="cta">Get Started Free</button>\n<a href="/signup" class="btn-primary">Start Your Trial</a>',
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
return createResult({ id: 'cta-presence', name: 'Call-to-Action Elements', category: 'content', severity: 'info' }, 'pass', `${ctx.ctaButtonsCount} call-to-action element(s) found`, { value: ctx.ctaButtonsCount });
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
id: 'form-presence',
|
|
162
|
+
name: 'Form Elements',
|
|
163
|
+
category: 'content',
|
|
164
|
+
severity: 'info',
|
|
165
|
+
description: 'Detect forms for lead generation or conversion',
|
|
166
|
+
check: (ctx) => {
|
|
167
|
+
if (ctx.formCount === undefined) {
|
|
168
|
+
return createResult({ id: 'form-presence', name: 'Form Elements', category: 'content', severity: 'info' }, 'info', 'Not applicable (form count data unavailable)', { recommendation: 'This rule checks for forms for lead generation and conversions' });
|
|
169
|
+
}
|
|
170
|
+
if (ctx.formCount > 0) {
|
|
171
|
+
return createResult({ id: 'form-presence', name: 'Form Elements', category: 'content', severity: 'info' }, 'pass', `${ctx.formCount} form(s) detected for lead capture/conversion`, {
|
|
172
|
+
value: ctx.formCount,
|
|
173
|
+
evidence: {
|
|
174
|
+
found: `${ctx.formCount} form elements`,
|
|
175
|
+
impact: 'Forms are essential for lead generation, contact, and conversions.',
|
|
176
|
+
},
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
return createResult({ id: 'form-presence', name: 'Form Elements', category: 'content', severity: 'info' }, 'pass', 'No forms detected (may not be needed for this page type)');
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
id: 'contact-methods',
|
|
184
|
+
name: 'Contact Methods',
|
|
185
|
+
category: 'content',
|
|
186
|
+
severity: 'info',
|
|
187
|
+
description: 'Page should provide contact methods for user trust',
|
|
188
|
+
check: (ctx) => {
|
|
189
|
+
const methods = [];
|
|
190
|
+
if (ctx.emailsFound && ctx.emailsFound.length > 0)
|
|
191
|
+
methods.push('email');
|
|
192
|
+
if (ctx.hasPhoneOnPage)
|
|
193
|
+
methods.push('phone');
|
|
194
|
+
if (ctx.hasWhatsAppLink)
|
|
195
|
+
methods.push('WhatsApp');
|
|
196
|
+
if (methods.length === 0 && ctx.hasContactPageLink) {
|
|
197
|
+
return createResult({ id: 'contact-methods', name: 'Contact Methods', category: 'content', severity: 'info' }, 'pass', 'Contact page link found', { evidence: { found: 'Link to contact page' } });
|
|
198
|
+
}
|
|
199
|
+
if (methods.length > 0) {
|
|
200
|
+
return createResult({ id: 'contact-methods', name: 'Contact Methods', category: 'content', severity: 'info' }, 'pass', `Contact methods found: ${methods.join(', ')}`, { value: methods.join(', ') });
|
|
201
|
+
}
|
|
202
|
+
return createResult({ id: 'contact-methods', name: 'Contact Methods', category: 'content', severity: 'info' }, 'pass', 'No direct contact methods detected (may not be needed)');
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
id: 'rss-feed-present',
|
|
207
|
+
name: 'RSS Feed',
|
|
208
|
+
category: 'technical',
|
|
209
|
+
severity: 'info',
|
|
210
|
+
description: 'RSS feeds help users and search engines discover new content',
|
|
211
|
+
check: (ctx) => {
|
|
212
|
+
if (ctx.hasRssFeed === undefined) {
|
|
213
|
+
return createResult({ id: 'rss-feed-present', name: 'RSS Feed', category: 'technical', severity: 'info' }, 'info', 'Not applicable (RSS feed data unavailable)', { recommendation: 'This rule checks for RSS feeds for content distribution' });
|
|
214
|
+
}
|
|
215
|
+
if (ctx.hasRssFeed) {
|
|
216
|
+
return createResult({ id: 'rss-feed-present', name: 'RSS Feed', category: 'technical', severity: 'info' }, 'pass', 'RSS feed detected', {
|
|
217
|
+
value: ctx.rssFeedUrl,
|
|
218
|
+
evidence: {
|
|
219
|
+
found: ctx.rssFeedUrl || 'RSS link tag present',
|
|
220
|
+
impact: 'RSS feeds notify search engines and feed readers when content is updated, improving content discovery.',
|
|
221
|
+
},
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
return createResult({ id: 'rss-feed-present', name: 'RSS Feed', category: 'technical', severity: 'info' }, 'info', 'No RSS feed detected', {
|
|
225
|
+
recommendation: 'Add an RSS feed for blogs or news sites to improve content distribution.',
|
|
226
|
+
evidence: {
|
|
227
|
+
expected: '<link rel="alternate" type="application/rss+xml" href="/feed.xml">',
|
|
228
|
+
impact: 'RSS feeds help users subscribe to content and notify search engines of updates. Particularly important for blogs and news sites.',
|
|
229
|
+
learnMore: 'https://developers.google.com/search/docs/crawling-indexing/sitemaps/build-sitemap#rss-atom',
|
|
230
|
+
},
|
|
231
|
+
});
|
|
232
|
+
},
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
id: 'atom-feed-present',
|
|
236
|
+
name: 'Atom Feed',
|
|
237
|
+
category: 'technical',
|
|
238
|
+
severity: 'info',
|
|
239
|
+
description: 'Atom feeds are an alternative to RSS for content syndication',
|
|
240
|
+
check: (ctx) => {
|
|
241
|
+
if (ctx.hasAtomFeed === undefined) {
|
|
242
|
+
return createResult({ id: 'atom-feed-present', name: 'Atom Feed', category: 'technical', severity: 'info' }, 'info', 'Not applicable (Atom feed data unavailable)', { recommendation: 'This rule checks for Atom feeds as alternative to RSS' });
|
|
243
|
+
}
|
|
244
|
+
if (ctx.hasAtomFeed) {
|
|
245
|
+
return createResult({ id: 'atom-feed-present', name: 'Atom Feed', category: 'technical', severity: 'info' }, 'pass', 'Atom feed detected', {
|
|
246
|
+
value: ctx.atomFeedUrl,
|
|
247
|
+
evidence: {
|
|
248
|
+
found: ctx.atomFeedUrl || 'Atom link tag present',
|
|
249
|
+
},
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
return createResult({ id: 'atom-feed-present', name: 'Atom Feed', category: 'technical', severity: 'info' }, 'pass', 'No Atom feed detected (RSS is more common)');
|
|
253
|
+
},
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
id: 'noindex-canonical-conflict',
|
|
257
|
+
name: 'Noindex + Canonical Conflict',
|
|
258
|
+
category: 'technical',
|
|
259
|
+
severity: 'error',
|
|
260
|
+
description: 'Having both noindex and canonical sends conflicting signals',
|
|
261
|
+
check: (ctx) => {
|
|
262
|
+
if (!ctx.metaRobots || !ctx.hasCanonical) {
|
|
263
|
+
return createResult({ id: 'noindex-canonical-conflict', name: 'Noindex + Canonical Conflict', category: 'technical', severity: 'error' }, 'info', 'Not applicable (robots meta tag or canonical data unavailable)', { recommendation: 'This rule detects conflicting noindex and canonical directives' });
|
|
264
|
+
}
|
|
265
|
+
const hasNoindex = ctx.metaRobots.includes('noindex');
|
|
266
|
+
if (hasNoindex && ctx.hasCanonical) {
|
|
267
|
+
return createResult({ id: 'noindex-canonical-conflict', name: 'Noindex + Canonical Conflict', category: 'technical', severity: 'error' }, 'fail', 'Page has both noindex and canonical - conflicting signals', {
|
|
268
|
+
recommendation: 'Remove either noindex or canonical. They serve opposite purposes.',
|
|
269
|
+
evidence: {
|
|
270
|
+
found: `noindex meta tag + canonical: ${ctx.canonicalUrl}`,
|
|
271
|
+
expected: 'Either noindex (to block indexing) OR canonical (to consolidate indexing)',
|
|
272
|
+
impact: 'noindex tells search engines not to index this page, while canonical tells them to consolidate signals to a URL. Google may ignore the canonical if noindex is present, wasting crawl budget.',
|
|
273
|
+
issue: 'Conflicting indexing directives',
|
|
274
|
+
learnMore: 'https://developers.google.com/search/docs/crawling-indexing/consolidate-duplicate-urls',
|
|
275
|
+
},
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
return createResult({ id: 'noindex-canonical-conflict', name: 'Noindex + Canonical Conflict', category: 'technical', severity: 'error' }, 'pass', 'No conflicting noindex and canonical directives');
|
|
279
|
+
},
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
id: 'noindex-sitemap-hint',
|
|
283
|
+
name: 'Noindex Pages in Sitemap',
|
|
284
|
+
category: 'crawlability',
|
|
285
|
+
severity: 'warning',
|
|
286
|
+
description: 'Pages with noindex should not be in sitemap',
|
|
287
|
+
check: (ctx) => {
|
|
288
|
+
if (!ctx.metaRobots) {
|
|
289
|
+
return createResult({ id: 'noindex-sitemap-hint', name: 'Noindex in Sitemap', category: 'crawlability', severity: 'warning' }, 'info', 'Not applicable (robots meta tag data unavailable)', { recommendation: 'This rule checks if noindex pages are incorrectly included in sitemap' });
|
|
290
|
+
}
|
|
291
|
+
const hasNoindex = ctx.metaRobots.includes('noindex');
|
|
292
|
+
if (hasNoindex && ctx.pageInSitemap) {
|
|
293
|
+
return createResult({ id: 'noindex-sitemap-hint', name: 'Noindex in Sitemap', category: 'crawlability', severity: 'warning' }, 'warn', 'Page has noindex but appears to be in sitemap', {
|
|
294
|
+
recommendation: 'Remove noindex pages from sitemap to avoid wasting crawl budget.',
|
|
295
|
+
evidence: {
|
|
296
|
+
found: 'noindex directive on page that may be in sitemap',
|
|
297
|
+
expected: 'Only indexable pages in sitemap',
|
|
298
|
+
impact: 'Including noindex pages in sitemap wastes crawl budget and sends conflicting signals to search engines.',
|
|
299
|
+
learnMore: 'https://developers.google.com/search/docs/crawling-indexing/sitemaps/build-sitemap#best-practices',
|
|
300
|
+
},
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
return createResult({ id: 'noindex-sitemap-hint', name: 'Noindex in Sitemap', category: 'crawlability', severity: 'warning' }, 'pass', 'Page is indexable or not in sitemap');
|
|
304
|
+
},
|
|
305
|
+
},
|
|
306
|
+
];
|
|
@@ -7,8 +7,9 @@ export const bestPracticesRules = [
|
|
|
7
7
|
severity: 'error',
|
|
8
8
|
description: 'Page must have the HTML doctype',
|
|
9
9
|
check: (ctx) => {
|
|
10
|
-
if (ctx.hasDoctype === undefined)
|
|
11
|
-
return
|
|
10
|
+
if (ctx.hasDoctype === undefined) {
|
|
11
|
+
return createResult({ id: 'bp-doctype', name: 'HTML Doctype', category: 'technical', severity: 'error' }, 'info', 'Unable to check doctype (data unavailable)', { recommendation: 'Ensure HTML content is properly fetched' });
|
|
12
|
+
}
|
|
12
13
|
if (!ctx.hasDoctype) {
|
|
13
14
|
return createResult({ id: 'bp-doctype', name: 'HTML Doctype', category: 'technical', severity: 'error' }, 'fail', 'Page is missing HTML doctype', {
|
|
14
15
|
recommendation: 'Add <!DOCTYPE html> at the start of the document',
|
|
@@ -29,8 +30,9 @@ export const bestPracticesRules = [
|
|
|
29
30
|
severity: 'error',
|
|
30
31
|
description: 'Page should properly define charset',
|
|
31
32
|
check: (ctx) => {
|
|
32
|
-
if (ctx.hasCharset === undefined)
|
|
33
|
-
return
|
|
33
|
+
if (ctx.hasCharset === undefined) {
|
|
34
|
+
return createResult({ id: 'bp-charset', name: 'Character Encoding', category: 'technical', severity: 'error' }, 'info', 'Unable to check charset (data unavailable)', { recommendation: 'Ensure HTML content is properly fetched' });
|
|
35
|
+
}
|
|
34
36
|
if (!ctx.hasCharset) {
|
|
35
37
|
return createResult({ id: 'bp-charset', name: 'Character Encoding', category: 'technical', severity: 'error' }, 'fail', 'Page is missing character encoding declaration', {
|
|
36
38
|
recommendation: 'Add <meta charset="UTF-8"> in the <head>',
|
|
@@ -60,8 +62,9 @@ export const bestPracticesRules = [
|
|
|
60
62
|
severity: 'error',
|
|
61
63
|
description: 'Page should have successful HTTP status code',
|
|
62
64
|
check: (ctx) => {
|
|
63
|
-
if (ctx.httpStatusCode === undefined)
|
|
64
|
-
return
|
|
65
|
+
if (ctx.httpStatusCode === undefined) {
|
|
66
|
+
return createResult({ id: 'bp-http-status', name: 'HTTP Status Code', category: 'technical', severity: 'error' }, 'info', 'Unable to check HTTP status (data unavailable)', { recommendation: 'Ensure the page request completed successfully' });
|
|
67
|
+
}
|
|
65
68
|
const status = ctx.httpStatusCode;
|
|
66
69
|
if (status >= 400) {
|
|
67
70
|
return createResult({ id: 'bp-http-status', name: 'HTTP Status Code', category: 'technical', severity: 'error' }, 'fail', `HTTP ${status} error response`, {
|
|
@@ -90,8 +93,9 @@ export const bestPracticesRules = [
|
|
|
90
93
|
severity: 'error',
|
|
91
94
|
description: 'Page should not be blocked from indexing',
|
|
92
95
|
check: (ctx) => {
|
|
93
|
-
if (ctx.metaRobots === undefined)
|
|
94
|
-
return
|
|
96
|
+
if (ctx.metaRobots === undefined) {
|
|
97
|
+
return createResult({ id: 'bp-indexable', name: 'Page Indexable', category: 'technical', severity: 'error' }, 'info', 'Unable to check indexability (no robots meta)', { recommendation: 'Check if the page has robots meta tags' });
|
|
98
|
+
}
|
|
95
99
|
const robots = Array.isArray(ctx.metaRobots) ? ctx.metaRobots : [ctx.metaRobots];
|
|
96
100
|
const blocked = robots.some(r => r.toLowerCase().includes('noindex') || r.toLowerCase().includes('none'));
|
|
97
101
|
if (blocked) {
|
|
@@ -114,8 +118,9 @@ export const bestPracticesRules = [
|
|
|
114
118
|
severity: 'warning',
|
|
115
119
|
description: 'Links should be crawlable by search engines',
|
|
116
120
|
check: (ctx) => {
|
|
117
|
-
if (ctx.uncrawlableLinksCount === undefined)
|
|
118
|
-
return
|
|
121
|
+
if (ctx.uncrawlableLinksCount === undefined) {
|
|
122
|
+
return createResult({ id: 'bp-links-crawlable', name: 'Crawlable Links', category: 'links', severity: 'warning' }, 'info', 'Unable to check link crawlability (data unavailable)', { recommendation: 'Ensure link analysis completed' });
|
|
123
|
+
}
|
|
119
124
|
const count = ctx.uncrawlableLinksCount;
|
|
120
125
|
if (count > 0) {
|
|
121
126
|
return createResult({ id: 'bp-links-crawlable', name: 'Crawlable Links', category: 'links', severity: 'warning' }, 'warn', `${count} link(s) are not crawlable`, {
|
|
@@ -139,8 +144,9 @@ export const bestPracticesRules = [
|
|
|
139
144
|
severity: 'info',
|
|
140
145
|
description: 'robots.txt should be valid and accessible',
|
|
141
146
|
check: (ctx) => {
|
|
142
|
-
if (ctx.robotsTxtValid === undefined)
|
|
143
|
-
return
|
|
147
|
+
if (ctx.robotsTxtValid === undefined) {
|
|
148
|
+
return createResult({ id: 'bp-robots-txt', name: 'Valid robots.txt', category: 'technical', severity: 'info' }, 'info', 'Unable to check robots.txt (data unavailable)', { recommendation: 'Check if robots.txt is accessible at /robots.txt' });
|
|
149
|
+
}
|
|
144
150
|
if (!ctx.robotsTxtValid) {
|
|
145
151
|
return createResult({ id: 'bp-robots-txt', name: 'Valid robots.txt', category: 'technical', severity: 'info' }, 'warn', 'robots.txt is invalid or inaccessible', {
|
|
146
152
|
recommendation: 'Ensure robots.txt is valid and returns 200 status',
|
|
@@ -162,8 +168,9 @@ export const bestPracticesRules = [
|
|
|
162
168
|
severity: 'info',
|
|
163
169
|
description: 'Page should have valid structured data',
|
|
164
170
|
check: (ctx) => {
|
|
165
|
-
if (ctx.structuredDataErrors === undefined)
|
|
166
|
-
return
|
|
171
|
+
if (ctx.structuredDataErrors === undefined) {
|
|
172
|
+
return createResult({ id: 'bp-structured-data', name: 'Structured Data', category: 'structured-data', severity: 'info' }, 'info', 'Unable to check structured data (data unavailable)', { recommendation: 'Ensure JSON-LD structured data analysis completed' });
|
|
173
|
+
}
|
|
167
174
|
const errors = ctx.structuredDataErrors;
|
|
168
175
|
if (errors > 0) {
|
|
169
176
|
return createResult({ id: 'bp-structured-data', name: 'Structured Data', category: 'structured-data', severity: 'info' }, 'warn', `${errors} structured data error(s) found`, {
|
|
@@ -7,14 +7,17 @@ export const canonicalRules = [
|
|
|
7
7
|
severity: 'warning',
|
|
8
8
|
description: 'Pages should have a canonical URL defined',
|
|
9
9
|
check: (ctx) => {
|
|
10
|
-
if (ctx.hasCanonical === undefined)
|
|
11
|
-
return
|
|
10
|
+
if (ctx.hasCanonical === undefined) {
|
|
11
|
+
return createResult({ id: 'canonical-present', name: 'Canonical Tag Present', category: 'canonicalization', severity: 'warning' }, 'info', 'Not applicable (canonical data unavailable)', { recommendation: 'This rule checks if a canonical URL is defined to avoid duplicate content issues' });
|
|
12
|
+
}
|
|
12
13
|
if (!ctx.hasCanonical) {
|
|
13
14
|
return createResult({ id: 'canonical-present', name: 'Canonical Tag Present', category: 'canonicalization', severity: 'warning' }, 'warn', 'Page is missing canonical tag', {
|
|
14
15
|
recommendation: 'Add <link rel="canonical" href="..."> to specify the preferred URL',
|
|
15
16
|
evidence: {
|
|
17
|
+
found: 'No canonical tag',
|
|
16
18
|
expected: '<link rel="canonical" href="https://example.com/page">',
|
|
17
|
-
impact: 'Without canonical, search engines may index duplicate versions'
|
|
19
|
+
impact: 'Without canonical, search engines may index duplicate versions',
|
|
20
|
+
example: '<head>\n <link rel="canonical" href="https://example.com/page" />\n</head>',
|
|
18
21
|
}
|
|
19
22
|
});
|
|
20
23
|
}
|
|
@@ -28,8 +31,9 @@ export const canonicalRules = [
|
|
|
28
31
|
severity: 'error',
|
|
29
32
|
description: 'Page should have only one canonical tag',
|
|
30
33
|
check: (ctx) => {
|
|
31
|
-
if (ctx.canonicalCount === undefined)
|
|
32
|
-
return
|
|
34
|
+
if (ctx.canonicalCount === undefined) {
|
|
35
|
+
return createResult({ id: 'canonical-multiple', name: 'Multiple Canonical Tags', category: 'canonicalization', severity: 'error' }, 'info', 'Not applicable (canonical count data unavailable)', { recommendation: 'This rule ensures only one canonical tag exists per page' });
|
|
36
|
+
}
|
|
33
37
|
if (ctx.canonicalCount > 1) {
|
|
34
38
|
return createResult({ id: 'canonical-multiple', name: 'Multiple Canonical Tags', category: 'canonicalization', severity: 'error' }, 'fail', `Page has ${ctx.canonicalCount} canonical tags`, {
|
|
35
39
|
value: ctx.canonicalCount,
|
|
@@ -37,11 +41,12 @@ export const canonicalRules = [
|
|
|
37
41
|
evidence: {
|
|
38
42
|
found: ctx.canonicalUrls || [],
|
|
39
43
|
expected: 'Single canonical tag',
|
|
40
|
-
impact: 'Multiple canonicals confuse search engines about preferred URL'
|
|
44
|
+
impact: 'Multiple canonicals confuse search engines about preferred URL',
|
|
45
|
+
example: '<link rel="canonical" href="https://example.com/page" /> <!-- Keep only one -->',
|
|
41
46
|
}
|
|
42
47
|
});
|
|
43
48
|
}
|
|
44
|
-
return
|
|
49
|
+
return createResult({ id: 'canonical-multiple', name: 'Multiple Canonical Tags', category: 'canonicalization', severity: 'error' }, 'pass', 'Single canonical tag found');
|
|
45
50
|
},
|
|
46
51
|
},
|
|
47
52
|
{
|
|
@@ -51,8 +56,9 @@ export const canonicalRules = [
|
|
|
51
56
|
severity: 'info',
|
|
52
57
|
description: 'Canonical should typically point to the current page URL',
|
|
53
58
|
check: (ctx) => {
|
|
54
|
-
if (!ctx.canonicalUrl || !ctx.url)
|
|
55
|
-
return
|
|
59
|
+
if (!ctx.canonicalUrl || !ctx.url) {
|
|
60
|
+
return createResult({ id: 'canonical-self-referencing', name: 'Canonical Self-Reference', category: 'canonicalization', severity: 'info' }, 'info', 'Not applicable (canonical or URL data unavailable)', { recommendation: 'This rule checks if canonical URL points to the current page' });
|
|
61
|
+
}
|
|
56
62
|
const isSelfReferencing = normalizeUrl(ctx.canonicalUrl) === normalizeUrl(ctx.url);
|
|
57
63
|
if (!isSelfReferencing) {
|
|
58
64
|
return createResult({ id: 'canonical-self-referencing', name: 'Canonical Self-Reference', category: 'canonicalization', severity: 'info' }, 'info', 'Canonical points to different URL', {
|
|
@@ -60,7 +66,8 @@ export const canonicalRules = [
|
|
|
60
66
|
evidence: {
|
|
61
67
|
found: ctx.canonicalUrl,
|
|
62
68
|
expected: ctx.url,
|
|
63
|
-
impact: 'This page signals to search engines that another URL is preferred'
|
|
69
|
+
impact: 'This page signals to search engines that another URL is preferred',
|
|
70
|
+
example: `Current URL: ${ctx.url}\nCanonical: ${ctx.canonicalUrl}`,
|
|
64
71
|
}
|
|
65
72
|
});
|
|
66
73
|
}
|
|
@@ -74,8 +81,9 @@ export const canonicalRules = [
|
|
|
74
81
|
severity: 'error',
|
|
75
82
|
description: 'Canonical URL should be accessible (not 404)',
|
|
76
83
|
check: (ctx) => {
|
|
77
|
-
if (ctx.canonicalStatus === undefined)
|
|
78
|
-
return
|
|
84
|
+
if (ctx.canonicalStatus === undefined) {
|
|
85
|
+
return createResult({ id: 'canonical-broken', name: 'Broken Canonical URL', category: 'canonicalization', severity: 'error' }, 'info', 'Not applicable (canonical status data unavailable)', { recommendation: 'This rule validates that canonical URL is accessible and returns 200 OK' });
|
|
86
|
+
}
|
|
79
87
|
if (ctx.canonicalStatus === 404) {
|
|
80
88
|
return createResult({ id: 'canonical-broken', name: 'Broken Canonical URL', category: 'canonicalization', severity: 'error' }, 'fail', 'Canonical URL returns 404', {
|
|
81
89
|
value: ctx.canonicalUrl,
|
|
@@ -83,7 +91,8 @@ export const canonicalRules = [
|
|
|
83
91
|
evidence: {
|
|
84
92
|
found: `${ctx.canonicalUrl} → 404`,
|
|
85
93
|
expected: '200 OK',
|
|
86
|
-
impact: 'Broken canonical prevents proper indexing'
|
|
94
|
+
impact: 'Broken canonical prevents proper indexing',
|
|
95
|
+
example: '<link rel="canonical" href="https://example.com/existing-page" />',
|
|
87
96
|
}
|
|
88
97
|
});
|
|
89
98
|
}
|
|
@@ -94,7 +103,8 @@ export const canonicalRules = [
|
|
|
94
103
|
evidence: {
|
|
95
104
|
found: `${ctx.canonicalUrl} → ${ctx.canonicalStatus}`,
|
|
96
105
|
expected: '200 OK',
|
|
97
|
-
impact: 'Canonical errors prevent proper indexing'
|
|
106
|
+
impact: 'Canonical errors prevent proper indexing',
|
|
107
|
+
example: '<link rel="canonical" href="https://example.com/accessible-page" />',
|
|
98
108
|
}
|
|
99
109
|
});
|
|
100
110
|
}
|
|
@@ -105,7 +115,8 @@ export const canonicalRules = [
|
|
|
105
115
|
evidence: {
|
|
106
116
|
found: `${ctx.canonicalUrl} → ${ctx.canonicalFinalUrl}`,
|
|
107
117
|
expected: 'Direct URL without redirect',
|
|
108
|
-
impact: 'Canonical redirects waste crawl budget'
|
|
118
|
+
impact: 'Canonical redirects waste crawl budget',
|
|
119
|
+
example: `<link rel="canonical" href="${ctx.canonicalFinalUrl}" />`,
|
|
109
120
|
}
|
|
110
121
|
});
|
|
111
122
|
}
|
|
@@ -119,8 +130,9 @@ export const canonicalRules = [
|
|
|
119
130
|
severity: 'warning',
|
|
120
131
|
description: 'Canonical should use HTTPS protocol',
|
|
121
132
|
check: (ctx) => {
|
|
122
|
-
if (!ctx.canonicalUrl)
|
|
123
|
-
return
|
|
133
|
+
if (!ctx.canonicalUrl) {
|
|
134
|
+
return createResult({ id: 'canonical-protocol', name: 'Canonical Protocol', category: 'canonicalization', severity: 'warning' }, 'info', 'Not applicable (no canonical URL detected)', { recommendation: 'This rule ensures canonical URLs use HTTPS protocol for security' });
|
|
135
|
+
}
|
|
124
136
|
if (ctx.canonicalUrl.startsWith('http://')) {
|
|
125
137
|
return createResult({ id: 'canonical-protocol', name: 'Canonical Protocol', category: 'canonicalization', severity: 'warning' }, 'warn', 'Canonical uses HTTP instead of HTTPS', {
|
|
126
138
|
value: ctx.canonicalUrl,
|
|
@@ -128,7 +140,8 @@ export const canonicalRules = [
|
|
|
128
140
|
evidence: {
|
|
129
141
|
found: ctx.canonicalUrl,
|
|
130
142
|
expected: ctx.canonicalUrl.replace('http://', 'https://'),
|
|
131
|
-
impact: 'HTTP canonicals may cause indexing preference issues'
|
|
143
|
+
impact: 'HTTP canonicals may cause indexing preference issues',
|
|
144
|
+
example: `<link rel="canonical" href="${ctx.canonicalUrl.replace('http://', 'https://')}" />`,
|
|
132
145
|
}
|
|
133
146
|
});
|
|
134
147
|
}
|
|
@@ -142,8 +155,9 @@ export const canonicalRules = [
|
|
|
142
155
|
severity: 'warning',
|
|
143
156
|
description: 'Canonical URL should be absolute, not relative',
|
|
144
157
|
check: (ctx) => {
|
|
145
|
-
if (!ctx.canonicalUrl)
|
|
146
|
-
return
|
|
158
|
+
if (!ctx.canonicalUrl) {
|
|
159
|
+
return createResult({ id: 'canonical-absolute', name: 'Canonical Absolute URL', category: 'canonicalization', severity: 'warning' }, 'info', 'Not applicable (no canonical URL detected)', { recommendation: 'This rule checks that canonical URLs are absolute, not relative' });
|
|
160
|
+
}
|
|
147
161
|
const isRelative = !ctx.canonicalUrl.startsWith('http://') &&
|
|
148
162
|
!ctx.canonicalUrl.startsWith('https://') &&
|
|
149
163
|
!ctx.canonicalUrl.startsWith('//');
|
|
@@ -154,7 +168,8 @@ export const canonicalRules = [
|
|
|
154
168
|
evidence: {
|
|
155
169
|
found: ctx.canonicalUrl,
|
|
156
170
|
expected: 'https://example.com/page',
|
|
157
|
-
impact: 'Relative canonicals may be misinterpreted by crawlers'
|
|
171
|
+
impact: 'Relative canonicals may be misinterpreted by crawlers',
|
|
172
|
+
example: '<link rel="canonical" href="https://example.com/page" />',
|
|
158
173
|
}
|
|
159
174
|
});
|
|
160
175
|
}
|
|
@@ -168,8 +183,9 @@ export const canonicalRules = [
|
|
|
168
183
|
severity: 'warning',
|
|
169
184
|
description: 'Canonical should not create chains',
|
|
170
185
|
check: (ctx) => {
|
|
171
|
-
if (ctx.canonicalChainLength === undefined)
|
|
172
|
-
return
|
|
186
|
+
if (ctx.canonicalChainLength === undefined) {
|
|
187
|
+
return createResult({ id: 'canonical-chain', name: 'Canonical Chain', category: 'canonicalization', severity: 'warning' }, 'info', 'Not applicable (canonical chain data unavailable)', { recommendation: 'This rule detects canonical chains where one canonical points to another' });
|
|
188
|
+
}
|
|
173
189
|
if (ctx.canonicalChainLength > 1) {
|
|
174
190
|
return createResult({ id: 'canonical-chain', name: 'Canonical Chain', category: 'canonicalization', severity: 'warning' }, 'warn', `Canonical chain detected (${ctx.canonicalChainLength} hops)`, {
|
|
175
191
|
value: ctx.canonicalChainLength,
|
|
@@ -177,11 +193,12 @@ export const canonicalRules = [
|
|
|
177
193
|
evidence: {
|
|
178
194
|
found: ctx.canonicalChain || [],
|
|
179
195
|
expected: 'Direct canonical to final URL',
|
|
180
|
-
impact: 'Canonical chains may cause consolidation issues'
|
|
196
|
+
impact: 'Canonical chains may cause consolidation issues',
|
|
197
|
+
example: `<link rel="canonical" href="${ctx.canonicalChain?.[ctx.canonicalChain.length - 1] || 'https://example.com/final-page'}" />`,
|
|
181
198
|
}
|
|
182
199
|
});
|
|
183
200
|
}
|
|
184
|
-
return
|
|
201
|
+
return createResult({ id: 'canonical-chain', name: 'Canonical Chain', category: 'canonicalization', severity: 'warning' }, 'pass', 'No canonical chain detected');
|
|
185
202
|
},
|
|
186
203
|
},
|
|
187
204
|
{
|
|
@@ -191,8 +208,9 @@ export const canonicalRules = [
|
|
|
191
208
|
severity: 'info',
|
|
192
209
|
description: 'Canonical URLs with query parameters should be intentional',
|
|
193
210
|
check: (ctx) => {
|
|
194
|
-
if (!ctx.canonicalUrl)
|
|
195
|
-
return
|
|
211
|
+
if (!ctx.canonicalUrl) {
|
|
212
|
+
return createResult({ id: 'canonical-parameters', name: 'Canonical Query Parameters', category: 'canonicalization', severity: 'info' }, 'info', 'Not applicable (no canonical URL detected)', { recommendation: 'This rule checks for query parameters in canonical URLs' });
|
|
213
|
+
}
|
|
196
214
|
try {
|
|
197
215
|
const url = new URL(ctx.canonicalUrl);
|
|
198
216
|
if (url.search && url.search.length > 1) {
|
|
@@ -208,7 +226,7 @@ export const canonicalRules = [
|
|
|
208
226
|
}
|
|
209
227
|
catch {
|
|
210
228
|
}
|
|
211
|
-
return
|
|
229
|
+
return createResult({ id: 'canonical-parameters', name: 'Canonical Query Parameters', category: 'canonicalization', severity: 'info' }, 'pass', 'Canonical URL has no query parameters');
|
|
212
230
|
},
|
|
213
231
|
},
|
|
214
232
|
{
|
|
@@ -218,8 +236,9 @@ export const canonicalRules = [
|
|
|
218
236
|
severity: 'warning',
|
|
219
237
|
description: 'Pages with noindex should not have canonical to indexed page',
|
|
220
238
|
check: (ctx) => {
|
|
221
|
-
if (!ctx.hasCanonical || ctx.metaRobots === undefined)
|
|
222
|
-
return
|
|
239
|
+
if (!ctx.hasCanonical || ctx.metaRobots === undefined) {
|
|
240
|
+
return createResult({ id: 'canonical-noindex-conflict', name: 'Canonical + Noindex Conflict', category: 'canonicalization', severity: 'warning' }, 'info', 'Not applicable (canonical or robots meta tag data unavailable)', { recommendation: 'This rule detects conflicting noindex and canonical directives' });
|
|
241
|
+
}
|
|
223
242
|
const robots = Array.isArray(ctx.metaRobots) ? ctx.metaRobots : [ctx.metaRobots];
|
|
224
243
|
const hasNoindex = robots.some(r => r.toLowerCase().includes('noindex'));
|
|
225
244
|
if (hasNoindex && ctx.canonicalUrl) {
|
|
@@ -229,12 +248,14 @@ export const canonicalRules = [
|
|
|
229
248
|
recommendation: 'Remove canonical or noindex - conflicting signals',
|
|
230
249
|
evidence: {
|
|
231
250
|
found: `noindex + canonical to self (${ctx.canonicalUrl})`,
|
|
232
|
-
|
|
251
|
+
expected: 'Either noindex OR canonical, not both',
|
|
252
|
+
impact: 'Google ignores noindex if page has canonical to itself',
|
|
253
|
+
example: '<meta name="robots" content="noindex" /> <!-- Remove this -->\n<!-- OR -->\n<link rel="canonical" href="..." /> <!-- Remove this -->',
|
|
233
254
|
}
|
|
234
255
|
});
|
|
235
256
|
}
|
|
236
257
|
}
|
|
237
|
-
return
|
|
258
|
+
return createResult({ id: 'canonical-noindex-conflict', name: 'Canonical + Noindex Conflict', category: 'canonicalization', severity: 'warning' }, 'pass', 'No conflicting noindex and canonical directives');
|
|
238
259
|
},
|
|
239
260
|
},
|
|
240
261
|
];
|