Skip to content

Commit c549ef0

Browse files
committed
add json-ld metadata
1 parent 81302fa commit c549ef0

File tree

1 file changed

+90
-142
lines changed

1 file changed

+90
-142
lines changed

src/utils/structuredData.ts

Lines changed: 90 additions & 142 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,13 @@
77
*/
88

99
import type { Metadata, QuickstartsFrontmatter } from "~/content.config.ts"
10-
import { productChainLinks, chainNames, type ProductData } from "~/components/QuickLinks/data/productChainLinks.ts"
10+
11+
/**
12+
* Base URLs - Environment-aware constants
13+
* These should match the production URLs for structured data consistency
14+
*/
15+
export const DOCS_BASE_URL = "https://docs.chain.link"
16+
export const CHAINLINK_BASE_URL = "https://chain.link"
1117

1218
// Schema.org compliant types
1319
export type SchemaType = "TechArticle" | "HowTo" | "APIReference" | "BreadcrumbList" | "Organization" | "WebSite"
@@ -38,10 +44,10 @@ export interface VersionInfo {
3844
export const CHAINLINK_ORGANIZATION = {
3945
"@type": "Organization",
4046
name: "Chainlink Labs",
41-
url: "https://chain.link",
47+
url: CHAINLINK_BASE_URL,
4248
logo: {
4349
"@type": "ImageObject",
44-
url: "https://docs.chain.link/images/logo.png",
50+
url: `${DOCS_BASE_URL}/images/logo.png`,
4551
width: 200,
4652
height: 200,
4753
},
@@ -58,7 +64,7 @@ export const CHAINLINK_ORGANIZATION = {
5864
{
5965
"@type": "ContactPoint",
6066
contactType: "customer support",
61-
url: "https://chain.link/support",
67+
url: `${CHAINLINK_BASE_URL}/support`,
6268
},
6369
{
6470
"@type": "ContactPoint",
@@ -82,10 +88,10 @@ export const CHAINLINK_ORGANIZATION = {
8288
export const CHAINLINK_PUBLISHER = {
8389
"@type": "Organization",
8490
name: "Chainlink Documentation",
85-
url: "https://docs.chain.link",
91+
url: DOCS_BASE_URL,
8692
logo: {
8793
"@type": "ImageObject",
88-
url: "https://docs.chain.link/images/logo.png",
94+
url: `${DOCS_BASE_URL}/images/logo.png`,
8995
width: 200,
9096
height: 200,
9197
},
@@ -281,75 +287,6 @@ export function detectQuickstartProducts(products?: string[]): string[] {
281287
return products.map((product) => productMap[product.toLowerCase()]).filter(Boolean)
282288
}
283289

284-
/**
285-
* Get supported networks for a single Chainlink product
286-
* Returns comprehensive list of all networks the product supports
287-
*/
288-
export function getSupportedNetworks(pathname?: string): string[] {
289-
const product = detectChainlinkProduct(pathname)
290-
if (!product || !productChainLinks[product]) return []
291-
292-
const productData = productChainLinks[product] as ProductData
293-
if (!productData.chains) return []
294-
295-
// Get all supported chain keys and map to display names
296-
const supportedChains = Object.keys(productData.chains)
297-
return supportedChains
298-
.map((chainKey) => chainNames[chainKey] || chainKey)
299-
.filter(Boolean)
300-
.sort()
301-
}
302-
303-
/**
304-
* Get intersection of supported networks for multiple products
305-
* Returns networks that ALL specified products support
306-
*/
307-
export function getIntersectionOfSupportedNetworks(products: string[]): string[] {
308-
if (products.length === 0) return []
309-
if (products.length === 1) {
310-
// Single product - get all its networks
311-
const product = products[0]
312-
if (!productChainLinks[product]) return []
313-
314-
const productData = productChainLinks[product] as ProductData
315-
if (!productData.chains) return []
316-
317-
const supportedChains = Object.keys(productData.chains)
318-
return supportedChains
319-
.map((chainKey) => chainNames[chainKey] || chainKey)
320-
.filter(Boolean)
321-
.sort()
322-
}
323-
324-
// Multiple products - find intersection
325-
const networkSets = products.map((product) => {
326-
if (!productChainLinks[product]) return new Set<string>()
327-
328-
const productData = productChainLinks[product] as ProductData
329-
if (!productData.chains) return new Set<string>()
330-
331-
const chains = Object.keys(productData.chains)
332-
.map((chainKey) => chainNames[chainKey] || chainKey)
333-
.filter(Boolean)
334-
335-
return new Set(chains)
336-
})
337-
338-
// Start with first product's networks
339-
const intersection = networkSets[0]
340-
341-
// Keep only networks that exist in ALL products
342-
for (let i = 1; i < networkSets.length; i++) {
343-
for (const network of intersection) {
344-
if (!networkSets[i].has(network)) {
345-
intersection.delete(network)
346-
}
347-
}
348-
}
349-
350-
return Array.from(intersection).sort()
351-
}
352-
353290
/**
354291
* Extract target platform from metadata content
355292
* Returns Schema.org compliant target platform
@@ -431,8 +368,6 @@ export function extractToolsAndPrerequisites(
431368
remix: "Remix IDE",
432369
metamask: "MetaMask",
433370
foundry: "Foundry",
434-
truffle: "Truffle",
435-
ganache: "Ganache",
436371
web3: "Web3.js",
437372
ethers: "Ethers.js",
438373
viem: "Viem",
@@ -520,7 +455,6 @@ export function generateTechArticle(
520455
const { isLearningResource, category } = detectContentType(pathname)
521456
const difficulty = extractDifficulty(metadata?.excerpt, pathname)
522457
const product = detectChainlinkProduct(pathname)
523-
const supportedNetworks = getSupportedNetworks(pathname)
524458

525459
const baseArticle = {
526460
"@context": "https://schema.org",
@@ -540,8 +474,8 @@ export function generateTechArticle(
540474
url: metadata?.image
541475
? metadata.image.startsWith("http")
542476
? metadata.image
543-
: `https://docs.chain.link${metadata.image}`
544-
: "https://docs.chain.link/images/logo.png",
477+
: `${DOCS_BASE_URL}${metadata.image}`
478+
: `${DOCS_BASE_URL}/images/logo.png`,
545479
width: 1200,
546480
height: 630,
547481
},
@@ -556,13 +490,15 @@ export function generateTechArticle(
556490
? `Smart contract and blockchain development using Chainlink ${product}`
557491
: "Smart contract and blockchain development using Chainlink",
558492
},
559-
// Add supported networks as mentions if available
560-
...(supportedNetworks.length > 0 && {
561-
mentions: supportedNetworks.map((network) => ({
562-
"@type": "Thing",
563-
name: network,
564-
description: `${network} blockchain network`,
565-
})),
493+
// Add Chainlink product mention for better topical SEO
494+
...(product && {
495+
mentions: [
496+
{
497+
"@type": "Thing",
498+
name: product,
499+
description: `Chainlink ${product}`,
500+
},
501+
],
566502
}),
567503
}
568504

@@ -600,7 +536,6 @@ export function generateHowTo(
600536
const { tools, prerequisites } = extractToolsAndPrerequisites(metadata?.excerpt, pathname)
601537
const duration = parseTimeToISO8601(estimatedTime)
602538
const product = detectChainlinkProduct(pathname)
603-
const supportedNetworks = getSupportedNetworks(pathname)
604539

605540
return {
606541
"@context": "https://schema.org",
@@ -621,8 +556,8 @@ export function generateHowTo(
621556
url: metadata?.image
622557
? metadata.image.startsWith("http")
623558
? metadata.image
624-
: `https://docs.chain.link${metadata.image}`
625-
: "https://docs.chain.link/images/logo.png",
559+
: `${DOCS_BASE_URL}${metadata.image}`
560+
: `${DOCS_BASE_URL}/images/logo.png`,
626561
width: 1200,
627562
height: 630,
628563
},
@@ -657,13 +592,15 @@ export function generateHowTo(
657592
? `Building decentralized applications with Chainlink ${product}`
658593
: "Building decentralized applications with Chainlink",
659594
},
660-
// Add supported networks as mentions if available
661-
...(supportedNetworks.length > 0 && {
662-
mentions: supportedNetworks.map((network) => ({
663-
"@type": "Thing",
664-
name: network,
665-
description: `${network} blockchain network`,
666-
})),
595+
// Add Chainlink product mention for better topical SEO
596+
...(product && {
597+
mentions: [
598+
{
599+
"@type": "Thing",
600+
name: product,
601+
description: `Chainlink ${product}`,
602+
},
603+
],
667604
}),
668605
}
669606
}
@@ -682,7 +619,6 @@ export function generateAPIReference(
682619
const programmingModel = extractProgrammingModel(metadata?.excerpt, pathname)
683620
const targetPlatform = extractTargetPlatform(metadata?.excerpt, pathname)
684621
const product = detectChainlinkProduct(pathname)
685-
const supportedNetworks = getSupportedNetworks(pathname)
686622

687623
// Use provided version info or extract from metadata/pathname
688624
const versionMatch = pathname.match(/v(\d+\.\d+\.\d+)/)
@@ -712,8 +648,8 @@ export function generateAPIReference(
712648
url: metadata?.image
713649
? metadata.image.startsWith("http")
714650
? metadata.image
715-
: `https://docs.chain.link${metadata.image}`
716-
: "https://docs.chain.link/images/logo.png",
651+
: `${DOCS_BASE_URL}${metadata.image}`
652+
: `${DOCS_BASE_URL}/images/logo.png`,
717653
width: 1200,
718654
height: 630,
719655
},
@@ -729,34 +665,49 @@ export function generateAPIReference(
729665
? `${product} - Decentralized oracle network for smart contracts`
730666
: "Decentralized oracle network for smart contracts",
731667
},
732-
// Add supported networks as mentions if available
733-
...(supportedNetworks.length > 0 && {
734-
mentions: supportedNetworks.map((network) => ({
735-
"@type": "Thing",
736-
name: network,
737-
description: `${network} blockchain network`,
738-
})),
668+
// Add Chainlink product mention for better topical SEO
669+
...(product && {
670+
mentions: [
671+
{
672+
"@type": "Thing",
673+
name: product,
674+
description: `Chainlink ${product}`,
675+
},
676+
],
739677
}),
678+
740679
// Add version-specific structured data if available
741680
...(versionInfo && {
742681
version,
743-
...(versionInfo.availableVersions && {
744-
isPartOf: {
745-
"@type": "SoftwareApplication",
746-
name: product ? `Chainlink ${product}` : "Chainlink Protocol",
747-
versionList: versionInfo.availableVersions.map((v) => ({
748-
"@type": "SoftwareVersion",
749-
version: v,
750-
releaseDate: v === versionInfo.version ? releaseDate : undefined,
751-
status:
752-
v === versionInfo.version && versionInfo.isLatest
753-
? "Latest"
754-
: versionInfo.isDeprecated
755-
? "Deprecated"
756-
: "Stable",
757-
})),
758-
},
759-
}),
682+
isPartOf: {
683+
"@type": "SoftwareApplication",
684+
name: product ? `Chainlink ${product}` : "Chainlink Protocol",
685+
description: product
686+
? `${product} - Decentralized oracle network for smart contracts`
687+
: "Decentralized oracle network for smart contracts",
688+
operatingSystem: "Blockchain",
689+
applicationCategory: "DeveloperApplication",
690+
url: `${DOCS_BASE_URL}/${product?.toLowerCase()}/api-reference/`,
691+
...(releaseDate && {
692+
datePublished: releaseDate,
693+
dateModified: releaseDate,
694+
}),
695+
// Valid Schema.org properties only
696+
...(versionInfo.availableVersions &&
697+
versionInfo.availableVersions.length > 1 && {
698+
// Use 'version' property (valid for CreativeWork parent)
699+
version,
700+
// Use 'isRelatedTo' for version relationships (valid for SoftwareApplication via Service inheritance)
701+
isRelatedTo: versionInfo.availableVersions
702+
.filter((v) => v !== version)
703+
.slice(0, 3) // Limit for performance
704+
.map((v) => ({
705+
"@type": "SoftwareApplication",
706+
name: `${product ? `Chainlink ${product}` : "Chainlink Protocol"} ${v}`,
707+
url: version && version !== v ? resolvedCanonicalUrl.replace(version, v) : resolvedCanonicalUrl,
708+
})),
709+
}),
710+
},
760711
mainEntityOfPage: {
761712
"@type": "WebPage",
762713
"@id": resolvedCanonicalUrl,
@@ -845,7 +796,6 @@ export function generateQuickstartHowTo(
845796

846797
// Detect products from curated quickstart metadata
847798
const detectedProducts = detectQuickstartProducts(frontmatter.products)
848-
const supportedNetworks = getIntersectionOfSupportedNetworks(detectedProducts)
849799
const primaryProduct = detectedProducts.length > 0 ? detectedProducts[0] : null
850800

851801
// Add requires to prerequisites if not already detected
@@ -872,7 +822,7 @@ export function generateQuickstartHowTo(
872822
...(duration && { totalTime: duration }),
873823
image: {
874824
"@type": "ImageObject",
875-
url: `https://docs.chain.link/images/quickstarts/feature/${frontmatter.image}`,
825+
url: `${DOCS_BASE_URL}/images/quickstarts/feature/${frontmatter.image}`,
876826
width: 1200,
877827
height: 630,
878828
},
@@ -910,23 +860,21 @@ export function generateQuickstartHowTo(
910860
// Quickstart-specific properties
911861
genre: "Quickstart Guide",
912862
...(frontmatter.githubSourceCodeUrl && {
913-
codeRepository: frontmatter.githubSourceCodeUrl,
863+
workExample: {
864+
"@type": "SoftwareSourceCode",
865+
name: `${frontmatter.title} - Source Code`,
866+
description: "Complete source code and examples for this tutorial",
867+
codeRepository: frontmatter.githubSourceCodeUrl,
868+
url: frontmatter.githubSourceCodeUrl,
869+
},
870+
}),
871+
// Add Chainlink product mentions for better topical SEO
872+
...(frontmatter.products && {
873+
mentions: frontmatter.products.map((product) => ({
874+
"@type": "Thing",
875+
name: product === "general" ? "Chainlink" : product.toUpperCase(),
876+
})),
914877
}),
915-
// Add supported networks as mentions if available, otherwise use frontmatter products
916-
...(supportedNetworks.length > 0
917-
? {
918-
mentions: supportedNetworks.map((network) => ({
919-
"@type": "Thing",
920-
name: network,
921-
description: `${network} blockchain network`,
922-
})),
923-
}
924-
: frontmatter.products && {
925-
mentions: frontmatter.products.map((product) => ({
926-
"@type": "Thing",
927-
name: product === "general" ? "Chainlink" : product.toUpperCase(),
928-
})),
929-
}),
930878
}
931879
}
932880

0 commit comments

Comments
 (0)