From 6cd053fae62e5a9ab8c1af1a20d11427b98204b2 Mon Sep 17 00:00:00 2001 From: dslovinsky Date: Mon, 28 Jul 2025 18:01:06 -0400 Subject: [PATCH 1/3] feat: move all custom JS to afterInteractive --- fern/docs.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fern/docs.yml b/fern/docs.yml index 950ccbfd..9b972118 100644 --- a/fern/docs.yml +++ b/fern/docs.yml @@ -74,11 +74,11 @@ css: js: - path: ./dist/output.js - strategy: beforeInteractive + strategy: afterInteractive - path: assets/heap.js - strategy: beforeInteractive + strategy: afterInteractive - path: assets/dashboard-referral.js - strategy: beforeInteractive + strategy: afterInteractive experimental: openapi-parser-v3: true From cd39f5a8cb834a4c1678b4c3380352db86ed1216 Mon Sep 17 00:00:00 2001 From: dslovinsky Date: Mon, 28 Jul 2025 18:22:23 -0400 Subject: [PATCH 2/3] revert: make custom app render-blocking again --- fern/docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fern/docs.yml b/fern/docs.yml index 9b972118..3cad7407 100644 --- a/fern/docs.yml +++ b/fern/docs.yml @@ -74,7 +74,7 @@ css: js: - path: ./dist/output.js - strategy: afterInteractive + strategy: beforeInteractive - path: assets/heap.js strategy: afterInteractive - path: assets/dashboard-referral.js From 9dd69e25056bc21168ae58665e626b2c6d1e785f Mon Sep 17 00:00:00 2001 From: dslovinsky Date: Mon, 28 Jul 2025 18:33:32 -0400 Subject: [PATCH 3/3] chore: remove temporary script source code --- package.json | 1 - scripts/dashboard-referral-tracking.ts | 233 ------------------------- 2 files changed, 234 deletions(-) delete mode 100644 scripts/dashboard-referral-tracking.ts diff --git a/package.json b/package.json index 832c948c..ea9e9b08 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,6 @@ "lint:prettier": "prettier --cache --check \"{src,scripts}/**/*.{js,jsx,ts,tsx,md,mjs,mts,json,yml,yaml}\"", "lint:prettier:fix": "pnpm run lint:prettier --write", "add-evm-chain": "ts-node ./scripts/add-evm-chain.ts", - "build:dashboard-tracking": "tsc scripts/dashboard-referral-tracking.ts --outDir scripts --lib DOM,ES2017 && npx terser scripts/dashboard-referral-tracking.js --compress --mangle --output fern/assets/dashboard-referral.js && echo '/* eslint-disable no-shadow */' | cat - fern/assets/dashboard-referral.js > temp && mv temp fern/assets/dashboard-referral.js && rm -f scripts/dashboard-referral-tracking.js", "prepare": "husky" }, "dependencies": { diff --git a/scripts/dashboard-referral-tracking.ts b/scripts/dashboard-referral-tracking.ts deleted file mode 100644 index bd510268..00000000 --- a/scripts/dashboard-referral-tracking.ts +++ /dev/null @@ -1,233 +0,0 @@ -/** - * Dashboard Referral Tracking Script - * - * Automatically adds UTM tracking parameters to all dashboard.alchemy.com links - * based on the current page's slug for Google Analytics tracking. - * - * UTM Parameters: - * - utm_source: docs - * - utm_medium: referral - * - utm_campaign: docs_to_dashboard - * - utm_content: [current page slug with underscores] - */ - -// ============================================================================= -// Types and Interfaces -// ============================================================================= - -interface UTMParameters { - utm_source: string; - utm_medium: string; -} - -interface TrackingAPI { - update: () => void; - getCurrentSlug: () => string; -} - -// ============================================================================= -// Constants -// ============================================================================= - -const UTM_PARAMETERS: UTMParameters = { - utm_source: "docs", - utm_medium: "referral", -}; - -const DASHBOARD_DOMAIN = "dashboard.alchemy.com"; -const DOCS_PATH_REGEX = /\/docs\/(.+)$/; -const DASHBOARD_URL_REGEX = /^https?:\/\/dashboard\.alchemy\.com/; - -// ============================================================================= -// Main Implementation -// ============================================================================= - -(function initializeDashboardTracking() { - "use strict"; - - /** - * Extract the page slug from the current URL for utm_content parameter. - * Converts forward slashes to underscores for better Google Analytics filtering. - * - * @returns The current page slug or 'homepage' as fallback - */ - function getCurrentPageSlug(): string { - const currentUrl = window.location.href; - const currentPath = window.location.pathname; - - // Try to extract slug from URL - everything after /docs/ - const docsMatch = currentUrl.match(DOCS_PATH_REGEX); - if (docsMatch && docsMatch[1]) { - return docsMatch[1].replace(/\//g, "_"); - } - - // Fallback: use pathname without leading slash and replace slashes with underscores - const pathSlug = currentPath.replace(/^\//, "").replace(/\//g, "_"); - return pathSlug || "homepage"; - } - - /** - * Add UTM parameters to a dashboard URL. - * - * @param originalUrl - The original dashboard URL - * @param utmContent - The utm_content parameter value - * @returns The URL with UTM parameters added - */ - function addUTMParameters(originalUrl: string, utmContent: string): string { - try { - const urlObject = new URL(originalUrl); - - // Add all UTM parameters - urlObject.searchParams.set("utm_source", UTM_PARAMETERS.utm_source); - urlObject.searchParams.set("utm_medium", UTM_PARAMETERS.utm_medium); - urlObject.searchParams.set("utm_content", utmContent); - - return urlObject.toString(); - } catch (error) { - // Log error but return original URL to avoid breaking links - console.warn("Error adding UTM parameters to URL:", originalUrl, error); - return originalUrl; - } - } - - /** - * Find and update all dashboard.alchemy.com links with UTM parameters. - * Only processes links that haven't been marked as already tracked. - */ - function updateDashboardLinks(): void { - const currentSlug = getCurrentPageSlug(); - - // Find all links that point to dashboard.alchemy.com and haven't been processed - const dashboardLinks = document.querySelectorAll( - `a[href*="${DASHBOARD_DOMAIN}"]:not([data-utm-tracked="true"])`, - ); - - dashboardLinks.forEach((linkElement) => { - const anchor = linkElement as HTMLAnchorElement; - const originalHref = anchor.getAttribute("href"); - - // Only process valid dashboard.alchemy.com URLs - if (originalHref && DASHBOARD_URL_REGEX.test(originalHref)) { - const updatedHref = addUTMParameters(originalHref, currentSlug); - anchor.setAttribute("href", updatedHref); - - // Mark as processed to avoid duplicate processing - anchor.setAttribute("data-utm-tracked", "true"); - } - }); - } - - /** - * Check if a DOM node or its children contain dashboard links. - * - * @param node - The DOM node to check - * @returns True if the node contains dashboard links - */ - function containsDashboardLinks(node: Node): boolean { - if (node.nodeType !== Node.ELEMENT_NODE) { - return false; - } - - const element = node as Element; - - // Check if the element itself is a dashboard link - if (element.tagName === "A") { - const anchor = element as HTMLAnchorElement; - return Boolean(anchor.href) && anchor.href.includes(DASHBOARD_DOMAIN); - } - - // Check if the element contains dashboard links - if (element.querySelectorAll) { - return ( - element.querySelectorAll(`a[href*="${DASHBOARD_DOMAIN}"]`).length > 0 - ); - } - - return false; - } - - /** - * Set up DOM mutation observer to handle dynamically added links. - * This ensures the script works with Single Page Applications (SPAs). - */ - function setupMutationObserver(): void { - const observer = new MutationObserver((mutations) => { - let shouldUpdate = false; - - // Check all mutations for new dashboard links - mutations.forEach((mutation) => { - if (mutation.type === "childList" && mutation.addedNodes.length > 0) { - mutation.addedNodes.forEach((node) => { - if (containsDashboardLinks(node)) { - shouldUpdate = true; - } - }); - } - }); - - // Update links if new dashboard links were found - if (shouldUpdate) { - updateDashboardLinks(); - } - }); - - // Start observing the document body for changes - observer.observe(document.body, { - childList: true, - subtree: true, - }); - } - - /** - * Initialize the complete tracking system. - * Sets up initial link processing and ongoing mutation observation. - */ - function initializeTrackingSystem(): void { - // Process existing links on the page - updateDashboardLinks(); - - // Set up observer for dynamically added content - setupMutationObserver(); - } - - /** - * Wait for DOM to be ready before initializing tracking. - * - * @param callback - Function to call when DOM is ready - */ - function whenDOMReady(callback: () => void): void { - if (document.readyState === "loading") { - document.addEventListener("DOMContentLoaded", callback); - } else { - // DOM is already ready - callback(); - } - } - - // ============================================================================= - // Initialization - // ============================================================================= - - // Initialize tracking when DOM is ready - whenDOMReady(() => { - // Small delay to ensure all initial content is loaded - setTimeout(initializeTrackingSystem, 100); - }); - - // Handle Single Page Application navigation - window.addEventListener("popstate", () => { - // Re-process links after navigation with a small delay - setTimeout(updateDashboardLinks, 100); - }); - - // ============================================================================= - // Public API - // ============================================================================= - - // Export public API for manual control if needed - // Using type assertion to avoid TypeScript global declaration issues - (window as unknown as Record).alchemyUTMTracking = { - update: updateDashboardLinks, - getCurrentSlug: getCurrentPageSlug, - } as TrackingAPI; -})();