From d42eff1cac7b96c6fa34f460c0aded88772ecef5 Mon Sep 17 00:00:00 2001 From: DevMiner Date: Wed, 1 Jul 2026 23:31:10 +0200 Subject: [PATCH] fix(team): stop hydration crash from date-dependent card ordering --- src/components/contributors/Card.tsx | 12 +++++++++--- src/routes/team.tsx | 15 +++++++++++---- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/components/contributors/Card.tsx b/src/components/contributors/Card.tsx index 4d68a8e..70223be 100644 --- a/src/components/contributors/Card.tsx +++ b/src/components/contributors/Card.tsx @@ -94,7 +94,12 @@ export const Card: ParentComponent = (props) => { cachedImage, } = props; const borderColor = color || FALLBACK_COLOR; - const name = getCardName(display); + // getCardName(display) is date-dependent; flag for detecting if on server/client + // and dynamically triggering shiny selectiong + const [mounted, setMounted] = createSignal(false); + const name = createMemo(() => + mounted() ? getCardName(display) : display[0].name + ); let card: HTMLDivElement = null as any; let placeholder: HTMLDivElement = null as any; // placeholder for the card when focused to keep its position in list @@ -366,6 +371,7 @@ export const Card: ParentComponent = (props) => { let resizeObserver: ResizeObserver | null = null; onMount(() => { + setMounted(true); if (typeof window === "undefined" || !card) return; const setInitialStyles = () => { @@ -509,7 +515,7 @@ export const Card: ParentComponent = (props) => { variant="section-title" color="text-background-90" > - {name} + {name()} {/* roles */}
@@ -529,7 +535,7 @@ export const Card: ParentComponent = (props) => {
{name} { - const nameA = getCardName(a.display).toLowerCase(); - const nameB = getCardName(b.display).toLowerCase(); + const nameA = a.name.toLowerCase(); + const nameB = b.name.toLowerCase(); return nameA.localeCompare(nameB); }) .map((contributor) => { @@ -277,6 +278,9 @@ export default function TeamPage() { const { translator } = useI18n(); const [focusedCard, setFocusedCard] = createSignal(null); + // only compute date-dependent state (shiny slimes) after mount, so the server + // render and the first client render match and hydration doesn't mismatch + const [mounted, setMounted] = createSignal(false); const [sponsors] = createResource(fetchSponsors); const activeSponsors = createMemo(() => sponsors()?.active ?? []); @@ -284,7 +288,9 @@ export default function TeamPage() { const envMissing = createMemo(() => sponsors()?.envMissing ?? false); const activeCount = createMemo(() => activeSponsors().length); const pastCount = createMemo(() => pastSponsors().length); - const shinyContribs = createMemo(() => getShinyContribs(sortedContribs)); + const shinyContribs = createMemo(() => + mounted() ? getShinyContribs(sortedContribs) : [] + ); const filteredContribs = createMemo(() => finalContribs().filter((contrib) => { const search = searchTerm().toLowerCase(); @@ -324,6 +330,7 @@ export default function TeamPage() { onMount(() => { if (typeof window === "undefined") return; + setMounted(true); document.addEventListener("keydown", handleEscapeKey); document.addEventListener("mouseup", handleClickOutside);