Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions astro.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,18 @@ const canonicalUrlsWithLanguageVariants = extractCanonicalUrlsWithLanguageVarian
export default defineConfig({
site: "https://docs.chain.link",
trailingSlash: "never",
// Prefetch internal links so client-side navigations (ClientRouter) feel instant.
// "hover" (not "viewport") avoids prefetching the hundreds of links in the docs sidebar at once.
//
// NOTE (Astro 5 -> 7 upgrade): `prefetch` and `<ClientRouter />` are stable in Astro 5 and remain
// the supported API in Astro 6/7, so this config carries over unchanged. The one thing to revisit
// is `patches/astro+5.18.2.patch` (dev-server module-graph perf fix): it is pinned to 5.18.2 and
// will fail to apply after the upgrade. Check whether the upstream fix has landed by then; if not,
// regenerate the patch against the new version with `npx patch-package astro`.
prefetch: {
prefetchAll: true,
defaultStrategy: "hover",
},
redirects: {
"/ccip/directory": "/ccip/directory/mainnet",
"/ccip/supported-networks": "/ccip/directory/mainnet",
Expand Down
21 changes: 21 additions & 0 deletions patches/astro+5.18.2.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
diff --git a/node_modules/astro/dist/vite-plugin-astro-server/vite.js b/node_modules/astro/dist/vite-plugin-astro-server/vite.js
index 81bb5cf..e2f060c 100644
--- a/node_modules/astro/dist/vite-plugin-astro-server/vite.js
+++ b/node_modules/astro/dist/vite-plugin-astro-server/vite.js
@@ -60,6 +60,16 @@ async function* crawlGraph(loader, _id, isRootFile, scanned = /* @__PURE__ */ ne
}
}
function isImportedBy(parent, entry) {
+ // Vite 6's backward-compatible ModuleNode rebuilds a union Set (and looks up a compat
+ // node for every importer) each time `importers` is read. With ~3.7k icons importing
+ // one module that is ~27M iterations per request. Read the per-environment Sets directly.
+ const client = entry._clientModule;
+ const ssr = entry._ssrModule;
+ if (client || ssr) {
+ if (client) for (const importer of client.importers) if (importer.id === parent) return true;
+ if (ssr) for (const importer of ssr.importers) if (importer.id === parent) return true;
+ return false;
+ }
for (const importer of entry.importers) {
if (importer.id === parent) {
return true;
2 changes: 1 addition & 1 deletion src/components/CRETemplate/CRETemplateOverview.astro
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ const { frontmatter } = Astro.props
</div>

<script>
document.addEventListener("DOMContentLoaded", () => {
document.addEventListener("astro:page-load", () => {
const cliCommand = document.getElementById("cli-command")
const cliCopyBtn = document.getElementById("cli-copy-btn") as HTMLButtonElement | null
const togglePills = document.querySelectorAll<HTMLButtonElement>(".lang-toggle-pill")
Expand Down
2 changes: 1 addition & 1 deletion src/components/CRETemplate/CRETemplatePanel.astro
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ const { frontmatter, headings } = Astro.props
</div>

<script>
document.addEventListener("DOMContentLoaded", () => {
document.addEventListener("astro:page-load", () => {
// Copy button functionality
const copyBtns = document.querySelectorAll<HTMLButtonElement>(".cli-copy-btn")
copyBtns.forEach((btn) => {
Expand Down
2 changes: 1 addition & 1 deletion src/components/CcipVersionToggle/CcipVersionGuard.astro
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ interface Props {
const { redirects } = Astro.props
---

<script is:inline define:vars={{ redirects, storageKey: CHAIN_TYPE_STORAGE_KEY }}>
<script is:inline data-astro-rerun define:vars={{ redirects, storageKey: CHAIN_TYPE_STORAGE_KEY }}>
try {
// Read the raw value exactly like initializeChainType() so both agree on the chain family.
// typeof check: inherited keys ("toString", "__proto__") are never string targets.
Expand Down
2 changes: 1 addition & 1 deletion src/components/ChangelogSnippet/ChangelogCard.astro
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ const formattedDate = formatDate(item["date-of-release"])
})
}

document.addEventListener("DOMContentLoaded", () => {
document.addEventListener("astro:page-load", () => {
swapDataAttributeImages()
initExpandableCards()
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,7 @@ const languageDisplay = getLanguageDisplayName(lang)
blockId,
}}
>
document.addEventListener("DOMContentLoaded", () => {
document.addEventListener("astro:page-load", () => {
// Only process THIS specific container to avoid contamination
const container = document.getElementById(blockId)
if (!container) return
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -448,7 +448,7 @@ const languageDisplay = getLanguageDisplayName(currentLang)
// Check every 200ms for language changes (lightweight)
setInterval(syncCheck, 200)

document.addEventListener("DOMContentLoaded", () => {
document.addEventListener("astro:page-load", () => {
// Only process THIS specific container to avoid contamination
const container = document.getElementById(blockId)
if (!container) return
Expand Down
4 changes: 2 additions & 2 deletions src/components/HeadCommon.astro
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ const ccipChainVersionRedirects = getCcipChainVersionRedirects(Astro.url.pathnam
<link rel="preconnect" href="https://www.google-analytics.com" crossorigin />

<!-- Set CRE SDK Language in dataLayer -->
<script is:inline type="text/javascript">
<script is:inline data-astro-rerun type="text/javascript">
;(function () {
var path = window.location.pathname
if (!(path === "/cre" || path.startsWith("/cre/"))) return
Expand All @@ -93,7 +93,7 @@ const ccipChainVersionRedirects = getCcipChainVersionRedirects(Astro.url.pathnam
</script>

<!-- Set CCIP Chain Family in dataLayer -->
<script is:inline type="text/javascript">
<script is:inline data-astro-rerun type="text/javascript">
;(function () {
if (!window.location.pathname.startsWith("/ccip")) return

Expand Down
2 changes: 1 addition & 1 deletion src/components/Header/Header.astro
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const pageSdkLangData = Object.fromEntries(pageSdkLangMap)
---

<!-- Expose SDK lang data to client-side for mobile navigation -->
<script is:inline define:vars={{ pageSdkLangData }}>
<script is:inline data-astro-rerun define:vars={{ pageSdkLangData }}>
// Make CRE SDK language metadata available globally for navigation filtering
window.__PAGE_SDK_LANG_MAP__ = pageSdkLangData
</script>
Expand Down
94 changes: 94 additions & 0 deletions src/components/NavigationProgress.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
---
/**
* Top-of-page progress bar for client-side navigation.
*
* With <ClientRouter /> there is no browser loading indicator, so a slow navigation looks like a
* dead click. This restores that feedback: the bar appears when a navigation starts and completes
* when the new page is swapped in.
*
* `transition:persist` keeps the element across swaps, so the script's element references stay
* valid and the bar never restarts mid-navigation.
*/
---

<div id="nav-progress" transition:persist aria-hidden="true">
<div id="nav-progress-bar"></div>
</div>

<style>
#nav-progress {
position: fixed;
top: 0;
left: 0;
right: 0;
height: 3px;
z-index: 9999;
pointer-events: none;
opacity: 0;
transition: opacity 200ms ease;
}

#nav-progress[data-active] {
opacity: 1;
}

#nav-progress-bar {
height: 100%;
width: 0;
background: var(--brand, #375bd2);
box-shadow: 0 0 8px var(--brand, #375bd2);
transition: width 200ms ease;
}

@media (prefers-reduced-motion: reduce) {
#nav-progress,
#nav-progress-bar {
transition: none;
}
}
</style>

<script>
const wrap = document.getElementById("nav-progress")
const bar = document.getElementById("nav-progress-bar")

// If the navigation never completes (cancelled, or the router bailed), stop showing the bar.
const SAFETY_TIMEOUT_MS = 8000

let hideTimer: ReturnType<typeof setTimeout> | undefined
let safetyTimer: ReturnType<typeof setTimeout> | undefined

function start() {
if (!wrap || !bar) return
clearTimeout(hideTimer)
clearTimeout(safetyTimer)

wrap.setAttribute("data-active", "")

// Reset to 0 without animating, force a reflow, then ease toward 90%. The bar never reaches
// 100% until the page is actually swapped, so it reads as "still working" rather than "done".
// The reflow is what makes the 0 -> 90% change animate; do not replace it with
// requestAnimationFrame, which is throttled in background tabs and would leave the bar at 0.
bar.style.transition = "none"
bar.style.width = "0"
void bar.offsetWidth
bar.style.transition = ""
bar.style.width = "90%"

safetyTimer = setTimeout(finish, SAFETY_TIMEOUT_MS)
}

function finish() {
if (!wrap || !bar) return
clearTimeout(safetyTimer)

bar.style.width = "100%"
hideTimer = setTimeout(() => {
wrap.removeAttribute("data-active")
bar.style.width = "0"
}, 200)
}

document.addEventListener("astro:before-preparation", start)
document.addEventListener("astro:after-swap", finish)
</script>
2 changes: 1 addition & 1 deletion src/components/PageTabs.astro
Original file line number Diff line number Diff line change
Expand Up @@ -466,7 +466,7 @@ const displayPage = hasActivePage ? (Array.isArray(currentPageGroup) ? currentPa
})
}

window.addEventListener("load", initDropdown)
document.addEventListener("astro:page-load", initDropdown)

// Update padding based on header visibility
const section = document.querySelector(".tabs-section") as HTMLElement
Expand Down
73 changes: 72 additions & 1 deletion src/layouts/BaseLayout.astro
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
---
import { ClientRouter } from "astro:transitions"
import HeadCommon from "~/components/HeadCommon.astro"
import HeadSEO from "~/components/HeadSEO.astro"
import StructuredData from "~/components/StructuredData.astro"
import Header from "~/components/Header/Header.astro"
import NavigationProgress from "~/components/NavigationProgress.astro"
import { NewsletterCTA } from "~/components/Footer/NewsletterCTA"
import Footer from "~/components/Footer/Footer.astro"
import { Metadata, QuickstartsFrontmatter } from "~/content.config.ts"
Expand All @@ -27,6 +29,7 @@ const canonicalURL = new URL(canonicalURLStr)

<html dir="ltr" lang="en-us" class="initial" transition:animate="none">
<head>
<ClientRouter />
<HeadCommon {title} />
<HeadSEO
metadata={metadata}
Expand All @@ -48,7 +51,7 @@ const canonicalURL = new URL(canonicalURLStr)
scroll-behavior: smooth;
}
</style>
<script is:inline>
<script is:inline data-astro-rerun>
try {
const savedLang = localStorage.getItem("docs-language-preference")
if (savedLang === "ts") {
Expand All @@ -64,8 +67,76 @@ const canonicalURL = new URL(canonicalURLStr)
</script>
<slot name="head" />
<slot name="head-scripts" />
<script is:inline data-astro-rerun>
// Cancel client-side routing to canonical CRE pages.
//
// A canonical CRE page (e.g. /cre/foo) renders an empty body and redirects to the
// language-specific URL (/cre/foo-go or /cre/foo-ts). Under ClientRouter the router would
// swap in that empty body and only then run the redirect, flashing a blank page. Detect the
// marker the page emits during preparation and cancel, so the redirect happens before paint.
//
// `astro:before-swap` is not cancelable, so this hooks `astro:before-preparation` and wraps
// its loader: the loader has already parsed the incoming document by the time it resolves,
// so we can inspect it without a second fetch.
;(function () {
if (window.__docsCanonicalRedirectGuard) return
window.__docsCanonicalRedirectGuard = true

document.addEventListener("astro:before-preparation", (event) => {
const originalLoader = event.loader
event.loader = async function () {
await originalLoader()

const marker = event.newDocument?.querySelector("meta[name='astro-canonical-redirect']")
if (!marker) return

let lang = "ts"
try {
const stored = localStorage.getItem("docs-language-preference")
lang = stored === "go" || stored === '"go"' ? "go" : "ts"
} catch (e) {
// localStorage unavailable: fall back to the TypeScript variant.
}

const target = lang === "go" ? marker.getAttribute("data-go") : marker.getAttribute("data-ts")
if (!target) return

// Cancel the client-side navigation. The router then falls back to a full page load,
// which runs the page's own redirect before anything paints.
event.preventDefault()
}
})
})()
</script>
<script is:inline data-astro-rerun>
// Safety net for Radix scroll locks (mobile nav dialog, etc.).
//
// Radix sets `pointer-events: none` + `data-scroll-locked` on <body> while a dialog is open
// and removes them on close. With Astro's ClientRouter, navigating from a link inside the
// dialog unmounts it without running that cleanup, so the lock is left behind and the whole
// page becomes non-interactive. Clear any stale lock after each navigation.
;(function () {
if (window.__docsScrollLockGuard) return
window.__docsScrollLockGuard = true

function clearStaleScrollLock() {
var body = document.body
if (!body) return
if (body.hasAttribute("data-scroll-locked")) {
body.removeAttribute("data-scroll-locked")
body.style.removeProperty("pointer-events")
body.style.removeProperty("overflow")
body.style.removeProperty("padding-right")
}
}

document.addEventListener("astro:after-swap", clearStaleScrollLock)
document.addEventListener("astro:page-load", clearStaleScrollLock)
})()
</script>
</head>
<body>
<NavigationProgress />
<Header />
<slot />
<NewsletterCTA client:visible />
Expand Down
2 changes: 1 addition & 1 deletion src/pages/certification.astro
Original file line number Diff line number Diff line change
Expand Up @@ -835,7 +835,7 @@ const formattedContentTitle = `Courses | Chainlink Certifications`

<script>
// Accordion functionality
document.addEventListener("DOMContentLoaded", function () {
document.addEventListener("astro:page-load", function () {
var popularResCols = document.querySelectorAll(".acad-acc-item")
popularResCols.forEach(function (popularResCol) {
popularResCol.addEventListener("click", function () {
Expand Down
2 changes: 1 addition & 1 deletion src/pages/cre-templates/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -605,7 +605,7 @@ const pageDescription =
</style>

<script>
document.addEventListener("DOMContentLoaded", () => {
document.addEventListener("astro:page-load", () => {
const sectionHeader = document.getElementById("section-header")
const floatingFilter = document.getElementById("floating-filter")
const filterPills = document.querySelectorAll<HTMLButtonElement>(".filter-pill")
Expand Down
17 changes: 17 additions & 0 deletions src/pages/cre/[...id].astro
Original file line number Diff line number Diff line change
Expand Up @@ -125,10 +125,26 @@ if (isCanonical) {
<DocsLayout frontmatter={currentEntry.data} {headings}>
{isCanonical && <meta name="robots" content="noindex, follow" slot="head-scripts" />}

{
// Marker read by BaseLayout's astro:before-swap handler. A canonical CRE page renders an
// empty body and redirects to the language-specific URL; client-side routing would swap in
// that empty body and flash before the redirect runs. The handler cancels the swap and does
// a full navigation instead, so the redirect happens before first paint.
isCanonical && (
<meta
name="astro-canonical-redirect"
data-go={`/cre/${goEntry!.id}`}
data-ts={`/cre/${tsEntry!.id}`}
slot="head-scripts"
/>
)
}

{
isCanonical && (
<script
is:inline
data-astro-rerun
slot="head-scripts"
define:vars={{
goUrl: `/cre/${goEntry!.id}`,
Expand Down Expand Up @@ -157,6 +173,7 @@ if (isCanonical) {
!isCanonical && goEntry && tsEntry && (
<script
is:inline
data-astro-rerun
slot="head-scripts"
define:vars={{ currentLang: entry!.data.sdkLang }}
set:html={`(function(){
Expand Down
4 changes: 2 additions & 2 deletions src/scripts/copyToClipboard/copyToClipboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ function initializeClipboard() {
})
}

// Initialize clipboard when the DOM is loaded
document.addEventListener("DOMContentLoaded", () => {
// Initialize clipboard on first load and after every client-side navigation
document.addEventListener("astro:page-load", () => {
initializeClipboard()

// Function to initialize copy buttons within code blocks dynamically
Expand Down
Loading
Loading