Skip to content

Commit c66d5f6

Browse files
committed
update
1 parent 7793129 commit c66d5f6

File tree

8 files changed

+130
-57
lines changed

8 files changed

+130
-57
lines changed

src/components/CCIP/Chain/Chain.astro

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ const chainStructuredData = generateChainStructuredData(
9898
image: chainMetadata.image,
9999
excerpt: chainMetadata.excerpt,
100100
}}
101+
suppressDefaultStructuredData={true}
101102
>
102103
<ChainHero
103104
chains={networks}

src/components/CCIP/Landing/ccip-landing.astro

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ const directoryStructuredData = generateDirectoryStructuredData(environment, net
6363
description: `Explore CCIP Directory for ${environment === Environment.Mainnet ? "Mainnet" : "Testnet"}: configuration data for supported blockchains and tokens, cross-chain lanes, contract addresses, rate limits, and operational status on ${environment === Environment.Mainnet ? "Mainnet" : "Testnet"}.`,
6464
image: "/files/ccip-directory.jpg",
6565
}}
66+
suppressDefaultStructuredData={true}
6667
>
6768
<Hero chains={networks} tokens={allTokens} environment={environment} client:load lanes={searchLanes} />
6869
<section class="layout">

src/components/CCIP/Token/Token.astro

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ const tokenStructuredData = generateTokenStructuredData(token, environment, chai
110110
image: tokenMetadata.image,
111111
excerpt: tokenMetadata.excerpt,
112112
}}
113+
suppressDefaultStructuredData={true}
113114
>
114115
<ChainHero
115116
chains={networks}

src/components/HeadSEO.astro

Lines changed: 33 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
---
22
import { Metadata, QuickstartsFrontmatter } from "src/content.config.ts"
33
import { SITE, OPEN_GRAPH, PAGE } from "../config"
4+
import { getOgType } from "~/utils/seo/og"
5+
import { resolveCanonical, getMimeFromUrlPath, toAbsoluteUrl } from "~/utils/seo/url"
46
import { detectApiReference } from "@components/VersionSelector/utils/versions"
57
import { extractVersionInfo } from "@components/VersionSelector/utils/extractVersionInfo"
68
import VersionSelectorHead from "@components/VersionSelector/base/VersionSelectorHead.astro"
@@ -13,9 +15,11 @@ export interface Props {
1315
quickstartFrontmatter?: QuickstartsFrontmatter
1416
pageTitle?: string // Add page title from frontmatter
1517
howToSteps?: { name: string; slug: string }[]
18+
suppressDefaultStructuredData?: boolean
1619
}
1720
18-
const { metadata, canonicalURL, quickstartFrontmatter, pageTitle, howToSteps } = Astro.props
21+
const { metadata, canonicalURL, quickstartFrontmatter, pageTitle, howToSteps, suppressDefaultStructuredData } =
22+
Astro.props
1923
const contentTitle = pageTitle ?? metadata?.title ?? PAGE.titleFallback
2024
const formattedContentTitle = `${contentTitle} | ${SITE.title}`
2125
const description = metadata?.description ?? SITE.description
@@ -26,14 +30,10 @@ var canonicalImageSrc = OPEN_GRAPH.image.src
2630
let ogImageType = "image/png"
2731
let ogImageAlt = OPEN_GRAPH.image.alt
2832
if (metadata?.image) {
29-
const imageUrl = new URL(metadata.image, Astro.site).toString()
33+
const imageUrl = toAbsoluteUrl(metadata.image, Astro.site)
3034
canonicalImageSrc = imageUrl
31-
// Infer from URL pathname only (ignore query/hash) to avoid network requests
32-
const pathname = new URL(imageUrl).pathname.toLowerCase()
33-
if (pathname.endsWith(".jpg") || pathname.endsWith(".jpeg")) ogImageType = "image/jpeg"
34-
else if (pathname.endsWith(".png")) ogImageType = "image/png"
35-
else if (pathname.endsWith(".webp")) ogImageType = "image/webp"
36-
else if (pathname.endsWith(".gif")) ogImageType = "image/gif"
35+
const pathname = new URL(imageUrl).pathname
36+
ogImageType = getMimeFromUrlPath(pathname) || ogImageType
3737
}
3838
3939
// Prefer a context-specific image alt when possible
@@ -54,60 +54,38 @@ if (isApiReference && product && isVersioned) {
5454
}
5555
5656
// Generate structured data
57-
const canonicalURLObj = typeof canonicalURL === "string" ? new URL(canonicalURL) : canonicalURL
57+
const canonicalURLObj: URL = typeof canonicalURL === "string" ? new URL(canonicalURL) : (canonicalURL as URL)
5858
5959
// Canonical override support (non-versioned pages only)
60-
const resolvedCanonicalHref = (() => {
61-
const candidate = metadata?.canonical
62-
if (!candidate) return canonicalURL
63-
try {
64-
// If candidate is absolute, use it; otherwise resolve against site
65-
const url = new URL(candidate, Astro.site)
66-
return url.toString()
67-
} catch {
68-
return canonicalURL
69-
}
70-
})()
60+
const resolvedCanonicalHref = resolveCanonical(metadata?.canonical, Astro.site, canonicalURLObj)
7161
7262
// Contextual Open Graph type
73-
const ogType = (() => {
74-
const websiteExact = new Set([
75-
"/",
76-
"/ccip",
77-
"/data-feeds",
78-
"/vrf",
79-
"/chainlink-functions",
80-
"/chainlink-automation",
81-
"/chainlink-local",
82-
"/resources",
83-
])
84-
const isWebsite = websiteExact.has(currentPage) || currentPage.startsWith("/ccip/directory")
85-
return isWebsite ? "website" : "article"
86-
})()
63+
const ogType = getOgType(currentPage)
8764
8865
// Use quickstart frontmatter if available, otherwise use regular metadata
8966
// Suppress default structured data for CCIP directory pages (handled by page-level generators)
90-
const structuredDataObjects = isCcipDirectoryPath
91-
? []
92-
: quickstartFrontmatter
93-
? generateStructuredData(
94-
quickstartFrontmatter,
95-
contentTitle,
96-
canonicalURLObj,
97-
currentPage,
98-
undefined,
99-
undefined,
100-
howToSteps
101-
)
102-
: generateStructuredData(
103-
metadata,
104-
contentTitle,
105-
canonicalURLObj,
106-
currentPage,
107-
metadata?.estimatedTime,
108-
versionInfo,
109-
howToSteps
110-
)
67+
const structuredDataObjects =
68+
suppressDefaultStructuredData || isCcipDirectoryPath
69+
? []
70+
: quickstartFrontmatter
71+
? generateStructuredData(
72+
quickstartFrontmatter,
73+
contentTitle,
74+
canonicalURLObj,
75+
currentPage,
76+
undefined,
77+
undefined,
78+
howToSteps
79+
)
80+
: generateStructuredData(
81+
metadata,
82+
contentTitle,
83+
canonicalURLObj,
84+
currentPage,
85+
metadata?.estimatedTime,
86+
versionInfo,
87+
howToSteps
88+
)
11189
---
11290

11391
<!-- Page Metadata -->{/* Conditional canonical: Let VersionSelectorHead handle it for versioned pages */}

src/layouts/BaseLayout.astro

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@ interface Props {
1515
pageTitle?: string
1616
howToSteps?: { name: string; slug: string }[]
1717
structuredData?: object | object[]
18+
suppressDefaultStructuredData?: boolean
1819
}
19-
const { title, metadata, quickstartFrontmatter, pageTitle, howToSteps, structuredData } = Astro.props
20+
const { title, metadata, quickstartFrontmatter, pageTitle, howToSteps, structuredData, suppressDefaultStructuredData } =
21+
Astro.props
2022
const canonicalURLStr = new URL(Astro.url.pathname, Astro.site).href.replace(/\/+$/, "")
2123
const canonicalURL = new URL(canonicalURLStr)
2224
---
@@ -30,6 +32,7 @@ const canonicalURL = new URL(canonicalURLStr)
3032
quickstartFrontmatter={quickstartFrontmatter}
3133
{pageTitle}
3234
howToSteps={howToSteps}
35+
suppressDefaultStructuredData={suppressDefaultStructuredData}
3336
/>
3437
{structuredData && <StructuredData data={structuredData} />}
3538
<style>

src/layouts/CcipDirectoryLayout.astro

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,17 @@ interface Props {
1717
structuredData?: object | object[]
1818
pageTitleOverride?: string
1919
metadataOverride?: Metadata
20+
suppressDefaultStructuredData?: boolean
2021
}
21-
const { frontmatter, headings, environment, structuredData, pageTitleOverride, metadataOverride } = Astro.props
22+
const {
23+
frontmatter,
24+
headings,
25+
environment,
26+
structuredData,
27+
pageTitleOverride,
28+
metadataOverride,
29+
suppressDefaultStructuredData,
30+
} = Astro.props
2231
2332
const titleHeading: MarkdownHeading = {
2433
text: frontmatter.title,
@@ -45,6 +54,7 @@ const layoutMetadata: Metadata | undefined = metadataOverride ?? frontmatter.met
4554
metadata={layoutMetadata}
4655
pageTitle={effectiveTitle}
4756
structuredData={structuredData}
57+
suppressDefaultStructuredData={suppressDefaultStructuredData}
4858
>
4959
<Drawer client:only="react" />
5060
<StickyHeader client:media="(max-width: 50em)" {initialHeadings} />

src/utils/seo/og.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/**
2+
* Determine Open Graph type for a given pathname.
3+
* - Returns "website" for home and product landing pages
4+
* - Returns "article" for everything else
5+
*
6+
* Keep this list in sync when adding new product roots.
7+
*/
8+
export function getOgType(pathname: string): "website" | "article" {
9+
const websiteExact = new Set<string>([
10+
"/",
11+
"/ccip",
12+
"/data-feeds",
13+
"/vrf",
14+
"/chainlink-functions",
15+
"/chainlink-automation",
16+
"/chainlink-local",
17+
"/resources",
18+
])
19+
if (websiteExact.has(pathname) || pathname.startsWith("/ccip/directory")) return "website"
20+
return "article"
21+
}

src/utils/seo/url.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/**
2+
* Convert a path or URL string to an absolute URL string.
3+
* - If already absolute (http/https), returns as-is
4+
* - If relative and a site URL is provided, resolves against it
5+
* - If site is missing, returns the original string
6+
*
7+
* @param pathOrUrl Relative path (e.g. "/images/og.png") or absolute URL
8+
* @param site The site base URL (Astro.site) – may be undefined in some contexts
9+
* @returns Absolute URL string when resolvable; otherwise the input
10+
*/
11+
export function toAbsoluteUrl(pathOrUrl: string, site: URL | undefined): string {
12+
try {
13+
if (/^https?:\/\//i.test(pathOrUrl)) return pathOrUrl
14+
if (site) return new URL(pathOrUrl, site).toString()
15+
return pathOrUrl
16+
} catch {
17+
return pathOrUrl
18+
}
19+
}
20+
21+
/**
22+
* Resolve a canonical URL override.
23+
* - If candidate is provided, resolves it against the site
24+
* - Falls back to the provided default URL when invalid or missing
25+
*
26+
* @param candidate Optional canonical override from frontmatter
27+
* @param site Site base URL (Astro.site) – may be undefined in some contexts
28+
* @param defaultUrl The default canonical (already absolute URL/string)
29+
* @returns A valid absolute canonical URL or the default URL
30+
*/
31+
export function resolveCanonical(
32+
candidate: string | undefined,
33+
site: URL | undefined,
34+
defaultUrl: URL | string
35+
): string | URL {
36+
if (!candidate) return defaultUrl
37+
try {
38+
return new URL(candidate, site).toString()
39+
} catch {
40+
return defaultUrl
41+
}
42+
}
43+
44+
/**
45+
* Infer an image MIME type from a URL pathname (query/hash ignored).
46+
* Supports: .jpg/.jpeg, .png, .webp, .gif
47+
*
48+
* @param pathname URL pathname (e.g. "/images/og.png")
49+
* @returns MIME type string like "image/png" or undefined when unknown
50+
*/
51+
export function getMimeFromUrlPath(pathname: string): string | undefined {
52+
const lower = pathname.toLowerCase()
53+
if (lower.endsWith(".jpg") || lower.endsWith(".jpeg")) return "image/jpeg"
54+
if (lower.endsWith(".png")) return "image/png"
55+
if (lower.endsWith(".webp")) return "image/webp"
56+
if (lower.endsWith(".gif")) return "image/gif"
57+
return undefined
58+
}

0 commit comments

Comments
 (0)