Skip to content

Commit d70d656

Browse files
authored
redirect leading slahes (#2881)
1 parent e54b743 commit d70d656

File tree

66 files changed

+495
-300
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

66 files changed

+495
-300
lines changed

SEO_METADATA_GUIDE.md

Lines changed: 28 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,9 @@ Every page automatically receives:
6161
```html
6262
<title>Page Title | Chainlink Documentation</title>
6363
<meta name="description" content="..." />
64-
<meta name="keywords" content="..." />
6564
<link rel="canonical" href="..." />
6665
<meta property="og:title" content="..." />
66+
<meta property="og:type" content="article" />
6767
<meta property="og:description" content="..." />
6868
<meta name="twitter:card" content="summary_large_image" />
6969
```
@@ -78,12 +78,26 @@ Every page automatically receives:
7878
"name": "Deploy Your First Smart Contract",
7979
"description": "Learn to deploy smart contracts in 10 minutes",
8080
"totalTime": "PT10M",
81-
"author": { "@type": "Organization", "name": "Chainlink" },
82-
"publisher": { "@type": "Organization", "name": "Chainlink" }
81+
"author": { "@type": "Organization", "name": "Chainlink Labs" },
82+
"publisher": { "@type": "Organization", "name": "Chainlink Documentation" }
8383
}
8484
</script>
8585
```
8686

87+
### Canonical URLs
88+
89+
- Non-versioned pages can override canonical with `metadata.canonical`.
90+
- Relative values are resolved to absolute URLs using the site base.
91+
- Versioned API reference pages set canonicals/alternates via the version head component.
92+
- Latest version: `rel="canonical"`; other versions: `rel="alternate"`.
93+
- `hreflang` is not used for versioning.
94+
95+
### Open Graph Type
96+
97+
- `og:type` is set contextually:
98+
- `website`: for `/`, `/ccip`, `/ccip/directory*`, `/data-feeds`, `/vrf`, `/chainlink-functions`, `/chainlink-automation`, `/chainlink-local`, `/resources`.
99+
- `article`: for all other pages.
100+
87101
## Content Types & Templates
88102

89103
### Quickstarts
@@ -157,7 +171,7 @@ metadata:
157171
**Generated Schema**: TechArticle + LearningResource
158172

159173
- `educationalLevel`: From `difficulty` field
160-
- `teaches`: Auto-generated from content analysis
174+
- `teaches`: Derived from content type and product (e.g., "Concept for CCIP")
161175
- `learningResourceType`: "Concept"
162176

163177
### Guides
@@ -317,15 +331,20 @@ excerpt: "ccip cross-chain token transfer tutorial hardhat solidity intermediate
317331

318332
### Image Guidelines
319333

320-
**Path Format**: `/images/[product]/[descriptive-name].png`
334+
**Path Format**:
335+
336+
- Standard pages: `metadata.image` should be an absolute path like `/images/[product]/[descriptive-name].png`.
337+
- Quickstarts: `frontmatter.image` is a filename resolved to `/images/quickstarts/feature/{filename}`.
321338

322339
**Requirements:**
323340

324341
- Minimum 1200x630px for social sharing
325-
- Use WebP format when possible
342+
- Prefer PNG or JPG for broad preview compatibility
343+
- WebP is acceptable, but some bots preview PNG/JPG more reliably
344+
- Avoid animated GIFs for social previews (often heavy; many platforms show only the first frame)
326345
- Include alt text context in filename
327346

328-
**Note**: Images are optional - the system automatically falls back to the Chainlink logo for social sharing if no custom image is provided.
347+
**Note**: The system falls back to the Chainlink logo if no custom image is provided. The current implementation sets `og:image:type` to `image/png`; using PNG avoids type mismatches.
329348

330349
### Content Hierarchy
331350

@@ -339,21 +358,6 @@ excerpt: "ccip cross-chain token transfer tutorial hardhat solidity intermediate
339358

340359
## Validation & Testing
341360

342-
### Build-Time Validation
343-
344-
Run the validation script to check structured data compliance:
345-
346-
```bash
347-
npm run validate-structured-data
348-
```
349-
350-
This script validates:
351-
352-
- Required Schema.org properties
353-
- ISO 8601 duration formats
354-
- URL validity
355-
- Content type classification
356-
357361
### Manual Testing Tools
358362

359363
**Google Rich Results Test:**
@@ -449,8 +453,8 @@ Run `npm run build` and check for errors in console.
449453
**Reality**: Minimal impact
450454

451455
- JSON-LD scripts are small (typically <2KB)
452-
- Processed at build time, not runtime
453-
- No additional HTTP requests
456+
- Processed at build time (no client-side runtime cost)
457+
- Social image existence is validated at build time; no client-side requests
454458
- Automatic fallbacks prevent missing data
455459

456460
---
@@ -477,12 +481,6 @@ When updating existing content for better SEO:
477481
- [ ] Add `metadata.lastModified` when updating content
478482
- [ ] Add `metadata.version` for API references
479483

480-
### Validation
481-
482-
- [ ] Test with validation script: `npm run validate-structured-data`
483-
- [ ] Check Rich Results Test
484-
- [ ] Verify social sharing preview
485-
486484
## Current Implementation Status
487485

488486
**✅ Fully Implemented Features:**

astro.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ const redirectSources = new Set(
2828
// https://astro.build/config
2929
export default defineConfig({
3030
site: "https://docs.chain.link",
31+
trailingSlash: "never",
3132
redirects: {
3233
"/ccip/directory": "/ccip/directory/mainnet",
3334
"/ccip/supported-networks": "/ccip/directory/mainnet",
-425 KB
Binary file not shown.

src/components/CCIP/Chain/Chain.astro

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { getTokenIconUrl } from "~/features/utils"
1616
import ChainTokenGrid from "./ChainTokenGrid"
1717
import { generateChainStructuredData } from "~/utils/ccipStructuredData"
1818
import StructuredData from "~/components/StructuredData.astro"
19+
import { DOCS_BASE_URL } from "~/utils/structuredData"
1920
2021
interface Props {
2122
environment: Environment
@@ -49,21 +50,32 @@ const searchLanes = getSearchLanes({ environment })
4950
5051
// Generate dynamic metadata for this specific chain
5152
const environmentText = environment === Environment.Mainnet ? "Mainnet" : "Testnet"
53+
const logoPath = network.logo || ""
54+
const logoPathname = (() => {
55+
try {
56+
return new URL(logoPath, Astro.site).pathname.toLowerCase()
57+
} catch {
58+
return logoPath.toLowerCase()
59+
}
60+
})()
61+
const socialImage = logoPathname.endsWith(".svg") ? "/files/ccip-directory.jpg" : logoPath
62+
5263
const chainMetadata = {
5364
title: `${network.name} CCIP Network - ${environmentText} Cross-Chain Configuration`,
5465
description: `CCIP configuration for ${network.name} on ${environmentText}. View ${allTokens.length > 0 ? `${allTokens.length} supported tokens` : "supported tokens"}, active cross-chain lanes, fees, and technical specifications.`,
55-
image: network.logo,
66+
image: socialImage,
5667
excerpt: `${network.name} CCIP network configuration ${environmentText.toLowerCase()} cross-chain interoperability ${network.chainType.toLowerCase()} supported tokens lanes fees technical specifications`,
5768
}
5869
59-
// Generate structured data for this chain
60-
const currentURL = new URL(Astro.request.url).href
70+
// Generate structured data for this chain (use production base for JSON-LD URLs)
71+
const currentPath = new URL(Astro.request.url).pathname
72+
const canonicalForJsonLd = `${DOCS_BASE_URL}${currentPath}`
6173
const chainStructuredData = generateChainStructuredData(
6274
network,
6375
environment,
6476
allTokens.length,
6577
lanes.length,
66-
currentURL
78+
canonicalForJsonLd
6779
)
6880
---
6981

@@ -79,8 +91,15 @@ const chainStructuredData = generateChainStructuredData(
7991
}}
8092
{headings}
8193
environment={environment}
94+
structuredData={chainStructuredData}
95+
pageTitleOverride={chainMetadata.title}
96+
metadataOverride={{
97+
description: chainMetadata.description,
98+
image: chainMetadata.image,
99+
excerpt: chainMetadata.excerpt,
100+
}}
101+
suppressDefaultStructuredData={true}
82102
>
83-
<StructuredData data={chainStructuredData} />
84103
<ChainHero
85104
chains={networks}
86105
tokens={allTokens}

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

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ import {
1313
import { getTokenIconUrl } from "~/features/utils"
1414
import NetworkGrid from "./NetworkGrid"
1515
import TokenGrid from "../TokenGrid/TokenGrid"
16+
import StructuredData from "~/components/StructuredData.astro"
17+
import { generateDirectoryStructuredData } from "~/utils/ccipStructuredData"
18+
import { DOCS_BASE_URL } from "~/utils/structuredData"
1619
1720
export type Props = {
1821
environment: Environment
@@ -41,9 +44,27 @@ const allTokens = tokens.map((token) => {
4144
}
4245
})
4346
const searchLanes = getSearchLanes({ environment })
47+
48+
// Generate directory-level structured data (DataCatalog/Dataset)
49+
// Use production base for canonical JSON-LD URLs (avoid local IPv6/port)
50+
const currentPath = new URL(Astro.request.url).pathname
51+
const canonicalForJsonLd = `${DOCS_BASE_URL}${currentPath}`
52+
const directoryStructuredData = generateDirectoryStructuredData(environment, networks, tokens, canonicalForJsonLd)
4453
---
4554

46-
<CcipDirectoryLayout frontmatter={entry.data} {headings} environment={environment}>
55+
<CcipDirectoryLayout
56+
frontmatter={entry.data}
57+
{headings}
58+
environment={environment}
59+
structuredData={directoryStructuredData}
60+
pageTitleOverride={`CCIP Directory - ${environment === Environment.Mainnet ? "Mainnet" : "Testnet"}`}
61+
metadataOverride={{
62+
...(entry.data.metadata || {}),
63+
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"}.`,
64+
image: "/files/ccip-directory.jpg",
65+
}}
66+
suppressDefaultStructuredData={true}
67+
>
4768
<Hero chains={networks} tokens={allTokens} environment={environment} client:load lanes={searchLanes} />
4869
<section class="layout">
4970
<div>

src/components/CCIP/Token/Token.astro

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
import Drawer from "~/components/CCIP/Drawer/Drawer"
2525
import { generateTokenStructuredData } from "~/utils/ccipStructuredData"
2626
import StructuredData from "~/components/StructuredData.astro"
27+
import { DOCS_BASE_URL } from "~/utils/structuredData"
2728
2829
interface Props {
2930
token: string
@@ -84,8 +85,10 @@ const tokenMetadata = {
8485
excerpt: `${token} cross-chain token CCIP ${environmentText.toLowerCase()} supported networks pool types rate limits transfer mechanisms blockchain interoperability`,
8586
}
8687
87-
// Generate structured data for this token page
88-
const tokenStructuredData = generateTokenStructuredData(token, environment, chainsOfToken.length, Astro.url.href)
88+
// Generate structured data for this token page (use production base for JSON-LD URLs)
89+
const currentPath = new URL(Astro.request.url).pathname
90+
const canonicalForJsonLd = `${DOCS_BASE_URL}${currentPath}`
91+
const tokenStructuredData = generateTokenStructuredData(token, environment, chainsOfToken.length, canonicalForJsonLd)
8992
---
9093

9194
<CcipDirectoryLayout
@@ -100,6 +103,14 @@ const tokenStructuredData = generateTokenStructuredData(token, environment, chai
100103
}}
101104
{headings}
102105
environment={environment}
106+
structuredData={tokenStructuredData}
107+
pageTitleOverride={tokenMetadata.title}
108+
metadataOverride={{
109+
description: tokenMetadata.description,
110+
image: tokenMetadata.image,
111+
excerpt: tokenMetadata.excerpt,
112+
}}
113+
suppressDefaultStructuredData={true}
103114
>
104115
<ChainHero
105116
chains={networks}
@@ -158,8 +169,6 @@ const tokenStructuredData = generateTokenStructuredData(token, environment, chai
158169
</section>
159170
</CcipDirectoryLayout>
160171

161-
<StructuredData data={tokenStructuredData} />
162-
163172
<style scoped="false">
164173
.layout {
165174
--doc-padding: var(--space-6x);

0 commit comments

Comments
 (0)