Skip to content

Commit 8207e8a

Browse files
authored
fix: DR-6518 UTM persistence (#7384)
* Remove leftover code tentative fix on docs utm persistence * Update utm params internally for docs * remove console logs * Another try * update for removal + simplification
1 parent e7339bb commit 8207e8a

File tree

9 files changed

+185
-61
lines changed

9 files changed

+185
-61
lines changed

content/300-accelerate/index.mdx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ pagination_next: 'accelerate/getting-started'
1010

1111
import {
1212
Bolt,
13-
BorderBox,
1413
BoxTitle,
1514
Database,
1615
Grid,

content/700-optimize/index.mdx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ pagination_next: 'optimize/getting-started'
1010

1111
import {
1212
Bolt,
13-
BorderBox,
1413
BoxTitle,
1514
Database,
1615
Grid,

functions/_middleware.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,6 @@ export const onRequest: PagesFunction<Env> = async (context) => {
9595
if (response.ok) {
9696
// Check what content type we actually got
9797
const actualContentType = response.headers.get('content-type');
98-
console.log(`Fetched ${markdownPath}, got content-type: ${actualContentType}`);
9998

10099
return new Response(response.body, {
101100
status: 200,

src/hooks/useUTMParams.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { useEffect, useState } from 'react';
2+
3+
export const useUTMParams = (): string => {
4+
const [utmParams, setUTMParams] = useState('');
5+
6+
useEffect(() => {
7+
if (typeof window !== 'undefined') {
8+
const updateUTMParams = () => {
9+
const storedParams = sessionStorage.getItem('utm_params');
10+
setUTMParams(storedParams || '');
11+
};
12+
13+
// Initial load
14+
updateUTMParams();
15+
16+
const handleStorageChange = (e: StorageEvent) => {
17+
if (e.key === 'utm_params') {
18+
updateUTMParams();
19+
}
20+
};
21+
22+
window.addEventListener('storage', handleStorageChange);
23+
24+
const interval = setInterval(updateUTMParams, 500);
25+
26+
return () => {
27+
window.removeEventListener('storage', handleStorageChange);
28+
clearInterval(interval);
29+
};
30+
}
31+
}, []);
32+
33+
return utmParams;
34+
};

src/theme/Layout/index.tsx

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ export default function Layout(props: Props): ReactNode {
2222
children,
2323
noFooter,
2424
wrapperClassName,
25-
// Not really layout-related, but kept for convenience/retro-compatibility
2625
title,
2726
description,
2827
} = props;
@@ -43,15 +42,12 @@ export default function Layout(props: Props): ReactNode {
4342
new URLSearchParams(utms).forEach((v, k) => url.searchParams.set(k, v));
4443
a.setAttribute('href', url.toString());
4544
} catch (e) {
46-
// Handle invalid URLs
4745
}
4846
});
4947
};
5048

51-
// Initial update
5249
updateLinks(document.querySelectorAll('a[href*="console.prisma.io"]'));
5350

54-
// Watch for new links
5551
const observer = new MutationObserver((mutations) => {
5652
mutations.forEach((mutation) => {
5753
mutation.addedNodes.forEach((node) => {

src/theme/Logo/index.tsx

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import React, {type ReactNode} from 'react';
2+
import Link from '@docusaurus/Link';
3+
import useBaseUrl from '@docusaurus/useBaseUrl';
4+
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
5+
import {useThemeConfig, type NavbarLogo} from '@docusaurus/theme-common';
6+
import ThemedImage from '@theme/ThemedImage';
7+
import type {Props} from '@theme/Logo';
8+
import { useUTMParams } from '@site/src/hooks/useUTMParams';
9+
10+
function LogoThemedImage({
11+
logo,
12+
alt,
13+
imageClassName,
14+
}: {
15+
logo: NavbarLogo;
16+
alt: string;
17+
imageClassName?: string;
18+
}) {
19+
const sources = {
20+
light: useBaseUrl(logo.src),
21+
dark: useBaseUrl(logo.srcDark || logo.src),
22+
};
23+
const themedImage = (
24+
<ThemedImage
25+
className={logo.className}
26+
sources={sources}
27+
height={logo.height}
28+
width={logo.width}
29+
alt={alt}
30+
style={logo.style}
31+
/>
32+
);
33+
34+
return imageClassName ? (
35+
<div className={imageClassName}>{themedImage}</div>
36+
) : (
37+
themedImage
38+
);
39+
}
40+
41+
export default function Logo(props: Props): ReactNode {
42+
const {
43+
siteConfig: {title},
44+
} = useDocusaurusContext();
45+
const {
46+
navbar: {title: navbarTitle, logo},
47+
} = useThemeConfig();
48+
49+
const {imageClassName, titleClassName, ...propsRest} = props;
50+
const logoLink = useBaseUrl(logo?.href || '/');
51+
const utmParams = useUTMParams();
52+
53+
const appendUtmParams = (url: string): string => {
54+
if (!utmParams) {
55+
return url;
56+
}
57+
const separator = url.includes('?') ? '&' : '?';
58+
const result = `${url}${separator}${utmParams}`;
59+
return result;
60+
};
61+
const fallbackAlt = navbarTitle ? '' : title;
62+
63+
const alt = logo?.alt ?? fallbackAlt;
64+
65+
return (
66+
<Link
67+
to={appendUtmParams(logoLink)}
68+
{...propsRest}
69+
{...(logo?.target && {target: logo.target})}>
70+
{logo && (
71+
<LogoThemedImage
72+
logo={logo}
73+
alt={alt}
74+
imageClassName={imageClassName}
75+
/>
76+
)}
77+
{navbarTitle != null && <b className={titleClassName}>{navbarTitle}</b>}
78+
</Link>
79+
);
80+
}

src/theme/Navbar/Content/index.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import React, { type ReactNode } from 'react';
1111
import styles from './styles.module.css';
1212
import Link from '@docusaurus/Link';
1313
import useBaseUrl from '@docusaurus/useBaseUrl';
14+
import { useUTMParams } from '@site/src/hooks/useUTMParams';
1415

1516
function useNavbarItems() {
1617
// TODO temporary casting until ThemeConfig type is improved
@@ -58,12 +59,20 @@ function NavbarContentLayout({
5859

5960
export default function NavbarContent(): ReactNode {
6061
const mobileSidebar = useNavbarMobileSidebar();
62+
const utmParams = useUTMParams();
6163

6264
const items = useNavbarItems();
6365
const [leftItems, rightItems] = splitNavbarItems(items);
6466

6567
const searchBarItem = items.find((item) => item.type === 'search');
6668
const baseUrl = useBaseUrl("/");
69+
70+
// Helper function to append UTM params to URL
71+
const appendUtmParams = (url: string): string => {
72+
if (!utmParams) return url;
73+
const separator = url.includes('?') ? '&' : '?';
74+
return `${url}${separator}${utmParams}`;
75+
};
6776

6877
return (
6978
<NavbarContentLayout
@@ -73,7 +82,7 @@ export default function NavbarContent(): ReactNode {
7382
{!mobileSidebar.disabled && <NavbarMobileSidebarToggle />}
7483
<NavbarLogo />
7584
<span className={styles.separator}>/</span>
76-
<Link to={baseUrl} className="logo-link">docs</Link>
85+
<Link to={appendUtmParams(baseUrl)} className="logo-link">docs</Link>
7786
</>
7887
}
7988
middle={

src/theme/NavbarItem/NavbarNavLink.tsx

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {isRegexpStringMatch} from '@docusaurus/theme-common';
66
import IconExternalLink from '@theme/Icon/ExternalLink';
77
import type {Props} from '@theme/NavbarItem/NavbarNavLink';
88
import { Icon } from '@site/src/components/Icon';
9+
import { useUTMParams } from '@site/src/hooks/useUTMParams';
910

1011

1112
type CustomProps = Props & {
@@ -31,7 +32,23 @@ export default function NavbarNavLink({
3132
const normalizedHref = useBaseUrl(href, {forcePrependBaseUrl: true});
3233
const isExternalLink = label && href && !isInternalUrl(href);
3334

34-
// Link content is set through html XOR label
35+
const utmParams = useUTMParams();
36+
37+
const appendUtmParams = (url: string): string => {
38+
if (!utmParams) {
39+
return url;
40+
}
41+
42+
const [baseUrl, existingQuery] = url.split('?');
43+
if (existingQuery) {
44+
const result = `${baseUrl}?${existingQuery}&${utmParams}`;
45+
return result;
46+
} else {
47+
const result = `${baseUrl}?${utmParams}`;
48+
return result;
49+
}
50+
};
51+
3552
const linkContentProps = html
3653
? {dangerouslySetInnerHTML: {__html: html}}
3754
: {
@@ -54,18 +71,33 @@ export default function NavbarNavLink({
5471
};
5572

5673
if (href) {
74+
if (isExternalLink) {
75+
return (
76+
<Link
77+
href={prependBaseUrlToHref ? normalizedHref : href}
78+
{...props}
79+
{...linkContentProps}
80+
/>
81+
);
82+
}
83+
84+
const finalHref = prependBaseUrlToHref ? normalizedHref : href;
85+
const urlWithUtms = appendUtmParams(finalHref);
86+
5787
return (
5888
<Link
59-
href={prependBaseUrlToHref ? normalizedHref : href}
89+
href={urlWithUtms}
6090
{...props}
6191
{...linkContentProps}
6292
/>
6393
);
6494
}
6595

96+
const urlWithUtms = appendUtmParams(toUrl);
97+
6698
return (
6799
<Link
68-
to={toUrl}
100+
to={urlWithUtms}
69101
isNavLink
70102
{...((activeBasePath || activeBaseRegex) && {
71103
isActive: (_match, location) =>

src/utils/useUTMPersistenceDocs.ts

Lines changed: 26 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,41 @@
11
import { useHistory, useLocation } from '@docusaurus/router';
22
import { useEffect, useRef } from 'react';
33

4-
const hasUTMParams = (search: string) => {
4+
const UTM_KEYS = ['utm_source', 'utm_medium', 'utm_campaign'] as const;
5+
const STORAGE_KEY = 'utm_params';
6+
7+
const extractUTMParams = (search: string): string => {
58
const params = new URLSearchParams(search);
6-
return ['utm_source', 'utm_medium', 'utm_campaign'].some(p => params.has(p));
9+
const utmParams = new URLSearchParams();
10+
UTM_KEYS.forEach(key => {
11+
const value = params.get(key);
12+
if (value) utmParams.set(key, value);
13+
});
14+
return utmParams.toString();
715
};
816

917
export const useUTMPersistenceDocs = () => {
1018
const location = useLocation();
1119
const history = useHistory();
12-
const isManualRemoval = useRef(false);
13-
const previousSearch = useRef('');
14-
15-
const getUTMParams = (search: string) => {
16-
const params = new URLSearchParams(search);
17-
const utmParams = new URLSearchParams();
18-
['utm_source', 'utm_medium', 'utm_campaign'].forEach(param => {
19-
const value = params.get(param);
20-
if (value) utmParams.set(param, value);
21-
});
22-
return utmParams.toString();
23-
};
20+
const prevPathname = useRef(location.pathname);
2421

2522
useEffect(() => {
26-
// Skip initial render
27-
if (previousSearch.current === '') {
28-
previousSearch.current = location.search;
29-
if (hasUTMParams(location.search)) {
30-
sessionStorage.setItem('utm_params', getUTMParams(location.search));
31-
}
32-
return;
33-
}
23+
const currentUTMs = extractUTMParams(location.search);
24+
const storedUTMs = sessionStorage.getItem(STORAGE_KEY);
25+
const isNavigation = prevPathname.current !== location.pathname;
3426

35-
const hadUTMs = hasUTMParams(previousSearch.current);
36-
const hasUTMs = hasUTMParams(location.search);
37-
38-
// Detect manual removal
39-
if (hadUTMs && !hasUTMs && location.pathname === previousSearch.current.split('?')[0]) {
40-
isManualRemoval.current = true;
41-
sessionStorage.removeItem('utm_params');
42-
console.log('Manual removal detected - UTMs cleared');
43-
}
44-
// Save new UTMs if they exist
45-
else if (hasUTMs) {
46-
isManualRemoval.current = false;
47-
sessionStorage.setItem('utm_params', getUTMParams(location.search));
48-
}
49-
// Restore UTMs if they're missing and weren't manually removed
50-
else if (!isManualRemoval.current) {
51-
const storedParams = sessionStorage.getItem('utm_params');
52-
if (storedParams) {
53-
const newSearch = storedParams ? `?${storedParams}` : '';
54-
if (location.search !== newSearch) {
55-
history.replace({
56-
pathname: location.pathname,
57-
search: newSearch,
58-
});
59-
}
60-
}
27+
if (currentUTMs) {
28+
sessionStorage.setItem(STORAGE_KEY, currentUTMs);
29+
} else if (storedUTMs && isNavigation) {
30+
history.replace({
31+
pathname: location.pathname,
32+
search: `?${storedUTMs}`,
33+
hash: location.hash,
34+
});
35+
} else if (!currentUTMs && storedUTMs && !isNavigation) {
36+
sessionStorage.removeItem(STORAGE_KEY);
6137
}
6238

63-
previousSearch.current = location.search;
64-
}, [location, history]);
39+
prevPathname.current = location.pathname;
40+
}, [location.pathname, location.search, location.hash]);
6541
};

0 commit comments

Comments
 (0)