Skip to content

Commit e4cf0d2

Browse files
authored
Expand UtmPreserver to all external github.com links on article and landing pages (#57920)
1 parent ed5b9ca commit e4cf0d2

File tree

8 files changed

+36
-30
lines changed

8 files changed

+36
-30
lines changed

src/frame/components/UtmPreserver.tsx

Lines changed: 22 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,10 @@
11
import { useEffect } from 'react'
22
import { useRouter } from 'next/router'
33

4-
type UtmPreserverProps = {
5-
// CSS selector for links that should preserve UTM parameters
6-
linkSelector?: string
7-
// Specific page paths where this component should be active
8-
activePaths?: string[]
9-
}
10-
11-
export const UtmPreserver = ({
12-
linkSelector = 'a[href*="github.com/copilot"], a[href*="github.com/github-copilot"]',
13-
activePaths = ['/copilot/get-started/plans'],
14-
}: UtmPreserverProps) => {
4+
export const UtmPreserver = () => {
155
const router = useRouter()
166

177
useEffect(() => {
18-
// Check if current page should have UTM preservation
19-
const shouldPreserveUtm = activePaths.some((path) => router.asPath.includes(path))
20-
if (!shouldPreserveUtm) return
21-
228
// Extract UTM parameters from current URL
239
const getUtmParams = (): URLSearchParams => {
2410
const urlParams = new URLSearchParams(window.location.search)
@@ -33,6 +19,22 @@ export const UtmPreserver = ({
3319
return utmParams
3420
}
3521

22+
const utmParams = getUtmParams()
23+
if (utmParams.toString() === '') return
24+
25+
// Check if a link should have UTM parameters preserved
26+
const shouldPreserveUtm = (url: string): boolean => {
27+
const lowercaseUrl = url.toLowerCase()
28+
29+
// Preserve UTM for any external github.com links (including subdomains like blog.github.com)
30+
// but NOT for docs.github.com (which are internal links anyway)
31+
const hasProtocol = lowercaseUrl.startsWith('https://') || lowercaseUrl.startsWith('http://')
32+
const isGithubCom = lowercaseUrl.includes('github.com')
33+
const isDocsGithubCom = lowercaseUrl.includes('docs.github.com')
34+
35+
return hasProtocol && isGithubCom && !isDocsGithubCom
36+
}
37+
3638
// Add UTM parameters to a URL
3739
const addUtmParamsToUrl = (url: string, utmParams: URLSearchParams): string => {
3840
try {
@@ -51,14 +53,10 @@ export const UtmPreserver = ({
5153

5254
// Apply UTM parameters to relevant links
5355
const applyUtmToLinks = (): void => {
54-
const utmParams = getUtmParams()
55-
56-
if (utmParams.toString() === '') return
57-
58-
const links = document.querySelectorAll<HTMLAnchorElement>(linkSelector)
56+
const links = document.querySelectorAll<HTMLAnchorElement>('a[href]')
5957

6058
links.forEach((link) => {
61-
if (link.href && (link.href.startsWith('http://') || link.href.startsWith('https://'))) {
59+
if (link.href && shouldPreserveUtm(link.href)) {
6260
link.href = addUtmParamsToUrl(link.href, utmParams)
6361
}
6462
})
@@ -67,15 +65,9 @@ export const UtmPreserver = ({
6765
// Handle click events for dynamic link modification
6866
const handleLinkClick = (event: Event): void => {
6967
const link = (event.target as Element)?.closest('a') as HTMLAnchorElement
70-
if (!link) return
71-
72-
// Check if this link matches our selector
73-
if (!link.matches(linkSelector)) return
74-
75-
const utmParams = getUtmParams()
76-
if (utmParams.toString() === '') return
68+
if (!link || !link.href) return
7769

78-
if (link.href && (link.href.startsWith('http://') || link.href.startsWith('https://'))) {
70+
if (shouldPreserveUtm(link.href)) {
7971
link.href = addUtmParamsToUrl(link.href, utmParams)
8072
}
8173
}
@@ -99,7 +91,7 @@ export const UtmPreserver = ({
9991
document.removeEventListener('click', handleLinkClick, true)
10092
router.events.off('routeChangeComplete', handleRouteChange)
10193
}
102-
}, [router.asPath, router.events, linkSelector, activePaths])
94+
}, [router.asPath, router.events])
10395

10496
// This component doesn't render anything
10597
return null

src/landings/components/CategoryLanding.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { ClientSideRedirects } from '@/rest/components/ClientSideRedirects'
1212
import { RestRedirect } from '@/rest/components/RestRedirect'
1313
import { Breadcrumbs } from '@/frame/components/page-header/Breadcrumbs'
1414
import { ArticleCardItems } from '@/landings/types'
15+
import { UtmPreserver } from '@/frame/components/UtmPreserver'
1516

1617
export const CategoryLanding = () => {
1718
const { t } = useTranslation('cookbook_landing')
@@ -97,6 +98,7 @@ export const CategoryLanding = () => {
9798

9899
return (
99100
<DefaultLayout>
101+
<UtmPreserver />
100102
{router.route === '/[versionId]/rest/[category]' && <RestRedirect />}
101103
{/* Doesn't matter *where* this is included because it will
102104
never render anything. It always just return null. */}

src/landings/components/ProductGuides.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { LearningTracks } from '@/learning-track/components/guides/LearningTrack
66
import { ArticleCards } from '@/landings/components/ArticleCards'
77
import { useTranslation } from '@/languages/components/useTranslation'
88
import { useMainContext } from '@/frame/components/context/MainContext'
9+
import { UtmPreserver } from '@/frame/components/UtmPreserver'
910

1011
export const ProductGuides = () => {
1112
const { title, learningTracks, includeGuides } = useProductGuidesContext()
@@ -18,6 +19,7 @@ export const ProductGuides = () => {
1819

1920
return (
2021
<DefaultLayout>
22+
<UtmPreserver />
2123
<LandingSection className="pt-3">
2224
<GuidesHero />
2325
</LandingSection>

src/landings/components/ProductLanding.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { ProductArticlesList } from '@/landings/components/ProductArticlesList'
1313
import { ProductReleases } from '@/landings/components/ProductReleases'
1414
import { useVersion } from '@/versions/components/useVersion'
1515
import { RestRedirect } from '@/rest/components/RestRedirect'
16+
import { UtmPreserver } from '@/frame/components/UtmPreserver'
1617

1718
export const ProductLanding = () => {
1819
const router = useRouter()
@@ -23,6 +24,7 @@ export const ProductLanding = () => {
2324

2425
return (
2526
<DefaultLayout>
27+
<UtmPreserver />
2628
<div data-search="article-body">
2729
{router.query.productId === 'rest' && <RestRedirect />}
2830
<LandingSection className="pt-3">

src/landings/components/TocLanding.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { LearningTrackNav } from '@/learning-track/components/article/LearningTr
1515
import { ClientSideRedirects } from '@/rest/components/ClientSideRedirects'
1616
import { RestRedirect } from '@/rest/components/RestRedirect'
1717
import { Breadcrumbs } from '@/frame/components/page-header/Breadcrumbs'
18+
import { UtmPreserver } from '@/frame/components/UtmPreserver'
1819

1920
export const TocLanding = () => {
2021
const router = useRouter()
@@ -33,6 +34,7 @@ export const TocLanding = () => {
3334

3435
return (
3536
<DefaultLayout>
37+
<UtmPreserver />
3638
{router.route === '/[versionId]/rest/[category]' && <RestRedirect />}
3739
{/* Doesn't matter *where* this is included because it will
3840
never render anything. It always just return null. */}

src/landings/components/bespoke/BespokeLanding.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { DefaultLayout } from '@/frame/components/DefaultLayout'
44
import { useLandingContext } from '@/landings/context/LandingContext'
55
import { LandingHero } from '@/landings/components/shared/LandingHero'
66
import { ArticleGrid } from '@/landings/components/shared/LandingArticleGridWithFilter'
7+
import { UtmPreserver } from '@/frame/components/UtmPreserver'
78
import { LandingCarousel } from '@/landings/components/shared/LandingCarousel'
89

910
import type { ArticleCardItems } from '@/landings/types'
@@ -18,6 +19,7 @@ export const BespokeLanding = () => {
1819

1920
return (
2021
<DefaultLayout>
22+
<UtmPreserver />
2123
<div data-search="article-body">
2224
<LandingHero title={title} intro={intro} heroImage={heroImage} introLinks={introLinks} />
2325

src/landings/components/discovery/DiscoveryLanding.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { useLandingContext } from '@/landings/context/LandingContext'
55
import { LandingHero } from '@/landings/components/shared/LandingHero'
66
import { ArticleGrid } from '@/landings/components/shared/LandingArticleGridWithFilter'
77
import { LandingCarousel } from '@/landings/components/shared/LandingCarousel'
8+
import { UtmPreserver } from '@/frame/components/UtmPreserver'
89

910
import type { ArticleCardItems } from '@/landings/types'
1011

@@ -18,6 +19,7 @@ export const DiscoveryLanding = () => {
1819

1920
return (
2021
<DefaultLayout>
22+
<UtmPreserver />
2123
<div>
2224
<LandingHero title={title} intro={intro} heroImage={heroImage} introLinks={introLinks} />
2325
<div className="container-xl px-3 px-md-6 mt-6 mb-4">

src/landings/components/journey/JourneyLanding.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@ import { DefaultLayout } from '@/frame/components/DefaultLayout'
22
import { useLandingContext } from '@/landings/context/LandingContext'
33
import { LandingHero } from '@/landings/components/shared/LandingHero'
44
import { JourneyLearningTracks } from './JourneyLearningTracks'
5+
import { UtmPreserver } from '@/frame/components/UtmPreserver'
56

67
export const JourneyLanding = () => {
78
const { title, intro, heroImage, introLinks, journeyTracks } = useLandingContext()
89

910
return (
1011
<DefaultLayout>
12+
<UtmPreserver />
1113
<div>
1214
<LandingHero title={title} intro={intro} heroImage={heroImage} introLinks={introLinks} />
1315

0 commit comments

Comments
 (0)