Conversation
Adds the `useScriptAhrefsAnalytics` composable for Ahrefs's privacy-first, cookie-less web analytics. Bundles `analytics.js` and proxies the beacon endpoint through `/_scripts/p/` (`PRIVACY_IP_ONLY`). SPA navigation is auto-tracked by the script's native `history.pushState` patch, so no `enableAutoSpaTracking` flag is required.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
commit: |
|
Caution Review failedPull request was closed or merged during review 📝 WalkthroughWalkthroughThis pull request integrates Ahrefs Web Analytics support into a Nuxt Scripts framework. The changes include a new Estimated code review effort🎯 3 (Moderate) | ⏱️ ~22 minutes 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/content/docs/1.guides/2.first-party.md`:
- Line 66: The "IP only" table row incorrectly lists Google AdSense; per
FIRST_PARTY.md AdSense uses the same proxy config as Google Analytics and
belongs to PRIVACY_HEATMAP. Remove Google AdSense from the "IP only" row and add
it to the "Heatmap-safe" row alongside Google Analytics (ensure the term "Google
AdSense" matches existing casing and that you reference
PRIVACY_HEATMAP/Heatmap-safe in the docs to keep consistency with
FIRST_PARTY.md).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 721d4162-fd89-4b88-8439-3542c861e9fe
📒 Files selected for processing (29)
FIRST_PARTY.mddocs/content/docs/1.guides/2.first-party.mddocs/content/scripts/ahrefs-analytics.mdpackage.jsonpackages/script/src/registry-logos.tspackages/script/src/registry-types.jsonpackages/script/src/registry.tspackages/script/src/runtime/registry/ahrefs-analytics.tspackages/script/src/runtime/registry/schemas.tspackages/script/src/runtime/types.tspackages/script/src/script-meta.tsplayground/pages/index.vueplayground/pages/third-parties/ahrefs-analytics/default.vueplayground/pages/third-parties/ahrefs-analytics/nuxt-scripts.vuetest/e2e/_ahrefs-analytics-suite.tstest/e2e/ahrefs-analytics-cdn.test.tstest/e2e/ahrefs-analytics.test.tstest/fixtures/ahrefs-analytics-cdn/nuxt.config.tstest/fixtures/ahrefs-analytics-cdn/pages/ahrefs.vuetest/fixtures/ahrefs-analytics-cdn/tsconfig.jsontest/fixtures/ahrefs-analytics/app.vuetest/fixtures/ahrefs-analytics/nuxt.config.tstest/fixtures/ahrefs-analytics/package.jsontest/fixtures/ahrefs-analytics/pages/ahrefs.vuetest/fixtures/ahrefs-analytics/pages/index.vuetest/fixtures/ahrefs-analytics/tsconfig.jsontest/types/types.test-d.tstest/unit/first-party.test.tstest/unit/proxy-configs.test.ts
| | **Full** | IP, user agent, language, screen, timezone, hardware fingerprints | Meta Pixel, TikTok Pixel, X Pixel, Snapchat Pixel, Reddit Pixel, LinkedIn Insight Tag | | ||
| | **Heatmap-safe** | IP, language, hardware fingerprints (preserves screen and user agent for session replay) | Google Analytics, Microsoft Clarity, Hotjar | | ||
| | **IP only** | IP addresses anonymised to subnet level | Plausible, PostHog, Umami, Fathom, CF Web Analytics, Vercel Analytics, Rybbit, Databuddy, Matomo, Intercom, YouTube, Vimeo, Gravatar, Carbon Ads, Lemon Squeezy, Google AdSense | | ||
| | **IP only** | IP addresses anonymised to subnet level | Plausible, PostHog, Umami, Fathom, CF Web Analytics, Vercel Analytics, Rybbit, Databuddy, Matomo, Ahrefs Web Analytics, Intercom, YouTube, Vimeo, Gravatar, Carbon Ads, Lemon Squeezy, Google AdSense | |
There was a problem hiding this comment.
Google AdSense is listed under the wrong privacy tier.
The "IP only" row here includes Google AdSense, but according to FIRST_PARTY.md, AdSense shares Google Analytics's proxy config and uses PRIVACY_HEATMAP (IP + language + hardware). It should either be removed from this row or moved to Heatmap-safe alongside Google Analytics.
📝 Suggested fix
-| **IP only** | IP addresses anonymised to subnet level | Plausible, PostHog, Umami, Fathom, CF Web Analytics, Vercel Analytics, Rybbit, Databuddy, Matomo, Ahrefs Web Analytics, Intercom, YouTube, Vimeo, Gravatar, Carbon Ads, Lemon Squeezy, Google AdSense |
+| **IP only** | IP addresses anonymised to subnet level | Plausible, PostHog, Umami, Fathom, CF Web Analytics, Vercel Analytics, Rybbit, Databuddy, Matomo, Ahrefs Web Analytics, Intercom, YouTube, Vimeo, Gravatar, Carbon Ads, Lemon Squeezy |And update the Heatmap-safe row:
-| **Heatmap-safe** | IP, language, hardware fingerprints (preserves screen and user agent for session replay) | Google Analytics, Microsoft Clarity, Hotjar |
+| **Heatmap-safe** | IP, language, hardware fingerprints (preserves screen and user agent for session replay) | Google Analytics, Google AdSense, Microsoft Clarity, Hotjar |🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/content/docs/1.guides/2.first-party.md` at line 66, The "IP only" table
row incorrectly lists Google AdSense; per FIRST_PARTY.md AdSense uses the same
proxy config as Google Analytics and belongs to PRIVACY_HEATMAP. Remove Google
AdSense from the "IP only" row and add it to the "Heatmap-safe" row alongside
Google Analytics (ensure the term "Google AdSense" matches existing casing and
that you reference PRIVACY_HEATMAP/Heatmap-safe in the docs to keep consistency
with FIRST_PARTY.md).
Probing analytics.ahrefs.com proved the script is reachable on CI but beacons silently drop because test-ahrefs-key isn't registered to the test origin. Match the Usercentrics pattern: only run behavioural assertions when AHREFS_TEST_KEY is set; wiring assertions still run unconditionally.
There was a problem hiding this comment.
🧹 Nitpick comments (2)
test/e2e/_ahrefs-analytics-suite.ts (2)
109-109: 💤 Low value
waitForURLtimeout of 5 000 ms is significantly shorter than the surrounding assertionsEvery other
waitFor/waitForSelectorin this suite uses 10–15 s timeouts, butpage.waitForURLhere uses 5 000 ms. A slower CI runner or a brief network hiccup during SPA navigation could hit this limit while the overall 60 s test budget still has plenty of time left.♻️ Proposed change
- await page.waitForURL('**/', { timeout: 5000 }) + await page.waitForURL('**/', { timeout: 15000 })🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@test/e2e/_ahrefs-analytics-suite.ts` at line 109, The page.waitForURL call uses a 5000ms timeout which is much shorter than surrounding waits and can flake on slow CI; update the call to use the same longer timeout as nearby waits (e.g., 10000–15000 ms) so page.waitForURL('**/', { timeout: 10000 }) (or 15000) matches the suite's timing expectations and reduces intermittent failures.
26-37: ⚡ Quick winConsider
page.waitForResponse/ Playwright's built-in request interception instead of the custom busy-poll helper
waitForbusy-polls at 50 ms intervals, burning CPU for up to 10 seconds on every beacon assertion. Playwright already exposespage.waitForResponse(predicate)which is event-driven and integrates cleanly with the existing request list. This also removes the hand-rolled timeout logic.♻️ Example using `page.waitForResponse`
-async function waitFor( - predicate: () => boolean, - { timeoutMs = 10000, intervalMs = 50, message = 'condition' }: { timeoutMs?: number, intervalMs?: number, message?: string } = {}, -) { - const deadline = Date.now() + timeoutMs - while (Date.now() < deadline) { - if (predicate()) - return - await new Promise(r => setTimeout(r, intervalMs)) - } - throw new Error(`Timed out after ${timeoutMs}ms waiting for ${message}`) -}Then replace e.g.:
// Before await waitFor(() => matches().length > 0, { message: '/api/event beacon' }) expect(matches().length).toBeGreaterThan(0) // After await page.waitForResponse( res => res.request().method() === 'POST' && (res.url().includes('analytics.ahrefs.com/api/event') || res.url().includes('/analytics.ahrefs.com/api/event')), { timeout: 10000 }, )🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@test/e2e/_ahrefs-analytics-suite.ts` around lines 26 - 37, The custom busy-poll helper waitFor (async function waitFor(...)) should be replaced by Playwright's event-driven page.waitForResponse to avoid CPU spinning and duplicate timeout logic; locate usages of waitFor and replace them with page.waitForResponse calls that match the beacon POST requests (check res.request().method() and res.url().includes(...) predicates) and pass an appropriate timeout option (e.g., 10000ms), then remove the waitFor helper function if no other references remain.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@test/e2e/_ahrefs-analytics-suite.ts`:
- Line 109: The page.waitForURL call uses a 5000ms timeout which is much shorter
than surrounding waits and can flake on slow CI; update the call to use the same
longer timeout as nearby waits (e.g., 10000–15000 ms) so page.waitForURL('**/',
{ timeout: 10000 }) (or 15000) matches the suite's timing expectations and
reduces intermittent failures.
- Around line 26-37: The custom busy-poll helper waitFor (async function
waitFor(...)) should be replaced by Playwright's event-driven
page.waitForResponse to avoid CPU spinning and duplicate timeout logic; locate
usages of waitFor and replace them with page.waitForResponse calls that match
the beacon POST requests (check res.request().method() and
res.url().includes(...) predicates) and pass an appropriate timeout option
(e.g., 10000ms), then remove the waitFor helper function if no other references
remain.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 52fd84aa-899d-4d99-840f-ca69dffc84de
📒 Files selected for processing (1)
test/e2e/_ahrefs-analytics-suite.ts
Replaces the AHREFS_TEST_KEY env-var skip with a Playwright network stub. The real analytics.js bails out on localhost before posting /api/event, so we also stub the script itself with a minimal pageview-firing replacement that mirrors the integration shape (initial POST + history.pushState patch). Both behavioural tests now run unconditionally on CI; assertions verify method, origin, path, and non-empty payload for every captured beacon.
Bundled mode silently dropped every Ahrefs beacon: analytics.js computes its endpoint as `new URL(currentScript.src).origin + "/api/event"`, and when bundled the script src origin is the local Nuxt origin, so beacons landed on a 404. Add a `replace-new-url-origin` sdkPatch (same family as Fathom's neutralize-domain-check and Rybbit's replace-src-split) that rewrites the derivation to `(self.location.origin + "<proxyPath>")`, wire it into the ahrefsAnalytics proxy def, cover the AST visitor in the bundle-sdk-patches integration suite, and harden the e2e suite to fetch the unstubbed bundle and assert the rewritten endpoints — the prior stub encoded the bug as expected behaviour. Verified live against analytics.ahrefs.com: visitors are counted.
Match the structure used by fathom-analytics / cloudflare-web-analytics / plausible-analytics: 2-space `links:` indentation, defaults note, proxy/onLoaded code-group for the basic API, ::script-types:: at the end, and a "Loading in app.vue" example matching the rest of the analytics docs. Keep the SPA-tracking note since native pushState patching is a real differentiator worth surfacing.
The composable injected `<link rel=stylesheet href=https://assets.calendly.com/assets/external/widget.css>` which leaked the visitor IP to the vendor on every page render — and the `url(/assets/external/close-icon.svg)` reference inside that stylesheet leaked again on every popup-close. This bypassed the bundle/proxy posture the registry advertises (`proxy.privacy: PRIVACY_IP_ONLY`). Inlines the 2.4 KB stylesheet via `useHead({ style })`, with the close-icon SVG embedded as a data URI. No more requests to `assets.calendly.com` at any point in the widget lifecycle. Adds `<ScriptCalendlyInlineWidget>` for the inline embed shape (popup and badge stay composable-only, since they have no host element). Mirrors `<ScriptYouTubePlayer>` — visibility trigger by default, `above-the-fold` preconnect, slots for loading/awaiting/error. Hardens the e2e suite to match the Ahrefs bar: asserts no `assets.calendly.com` stylesheet link is present, that the inline `<style>` carries the data-URI close icon, and that `initInlineWidget` mounts a real iframe in the requested parentElement. Adds a Node-fetch contract test that asserts the bundled artefact still exports the widget API and contains no `assets.calendly.com` references. Docs aligned with the post-#751 layout (frontmatter component link, `script-types` at end, code-group for proxy/onLoaded, dedicated `<ScriptCalendlyInlineWidget>` section).
🔗 Linked issue
Related to #177
❓ Type of change
📚 Description
Adds Ahrefs Web Analytics to the registry as
useScriptAhrefsAnalytics. Bundled and proxied viaanalytics.ahrefs.comwithPRIVACY_IP_ONLY; the single domain covers both/analytics.jsand the/api/eventbeacon. SPA tracking works out of the box becauseanalytics.jsnatively patcheshistory.pushStateand listens forpopstate(the originally-plannedenableAutoSpaTrackingflag was dropped after confirming this). Schema rejects emptykeyso misconfig fails dev validation.🧪 Usage
Add
ahrefsAnalytics: { key: 'your-project-key' }underscripts.registryinnuxt.config.tsto register the script for first-party proxy + bundling.