From 71dfc276ed871a28b67e4fd4385929a369d84986 Mon Sep 17 00:00:00 2001 From: Nicholas Kissel Date: Tue, 18 Aug 2026 12:38:43 -0700 Subject: [PATCH] Add live openings-count pill to the Careers footer link The footer's Careers entry now shows a small pine-tinted pill with the number of open roles, sourced from the same Work at a Startup feed the /careers job board renders so the badge cannot drift from the listings. The count is fetched server-side in BaseLayout (4s timeout) and baked into the HTML, so the pill renders without depending on the visitor's browser reaching workatastartup.com (commonly blocked by content blockers). The Footer then refreshes it client-side to keep statically-built pages current; any failure degrades to no pill. Co-Authored-By: Claude Opus 4.8 --- src/components/Footer.jsx | 39 ++++++++++++++++++++++++++++++++---- src/layouts/BaseLayout.astro | 10 ++++++++- src/lib/careers.ts | 22 ++++++++++++++++++++ 3 files changed, 66 insertions(+), 5 deletions(-) create mode 100644 src/lib/careers.ts diff --git a/src/components/Footer.jsx b/src/components/Footer.jsx index 27bfbd0..faba171 100644 --- a/src/components/Footer.jsx +++ b/src/components/Footer.jsx @@ -1,5 +1,7 @@ "use client"; +import { useEffect, useState } from "react"; import { usePathname } from "@/hooks/usePathname"; +import { fetchOpeningsCount } from "@/lib/careers"; import { Button } from "@/components/Button"; import routes from "@/generated/routes.json"; import clsx from "clsx"; @@ -137,7 +139,28 @@ export function PageNextPrevious({ navigation }) { ); } -function SmallPrint() { +// Live opening count for the Careers pill. `initialOpenings` is fetched +// server-side (BaseLayout) so the pill is in the initial HTML — no dependency on +// the visitor's browser reaching workatastartup.com. The client refresh keeps a +// statically-built page current; a blocked or failed refresh keeps the seed. +const CAREERS_HREF = "/careers"; + +function useOpeningsCount(initial) { + const [count, setCount] = useState(initial ?? null); + + useEffect(() => { + const controller = new AbortController(); + fetchOpeningsCount(controller.signal).then((next) => { + if (next !== null) setCount(next); + }); + return () => controller.abort(); + }, []); + + return count; +} + +function SmallPrint({ initialOpenings }) { + const openings = useOpeningsCount(initialOpenings); return (
@@ -220,13 +243,21 @@ function SmallPrint() {

Company

    {footer.company.map((item) => ( -
  • +
  • {item.name} + {item.href === CAREERS_HREF && openings > 0 && ( + + {openings} + + )}
  • ))}
@@ -308,7 +339,7 @@ function SmallPrint() { // a dark override wrapper instead. const DARK_THEMED_PATH_PREFIXES = ['/learn']; -export function Footer({ initialPathname = "" }) { +export function Footer({ initialPathname = "", initialOpenings = null }) { // usePathname returns "" during SSR; fall back to the server-provided path // so docs pages do not flash a light footer before hydration. const pathname = usePathname() || initialPathname; @@ -331,7 +362,7 @@ export function Footer({ initialPathname = "" }) { - +
); diff --git a/src/layouts/BaseLayout.astro b/src/layouts/BaseLayout.astro index fee77ba..2260e9d 100644 --- a/src/layouts/BaseLayout.astro +++ b/src/layouts/BaseLayout.astro @@ -4,6 +4,7 @@ import '@fortawesome/fontawesome-svg-core/styles.css'; import { ViewTransitions } from 'astro:transitions'; import { EmbedDetector } from '@/components/EmbedDetector'; import { Footer } from '@/components/Footer'; +import { fetchOpeningsCount } from '@/lib/careers'; import { Providers } from '@/components/Providers'; import { TooltipProvider } from '@rivet-gg/components'; import CopyCodeScript from '@/components/CopyCodeScript.astro'; @@ -68,6 +69,13 @@ const effectiveCanonicalUrl = canonicalUrl || `https://rivet.dev${ensureTrailing // RSS feed is only relevant for blog and changelog pages const showRssFeed = currentPath.startsWith('/blog') || currentPath.startsWith('/changelog'); + +// Open-role count for the footer Careers pill, fetched server-side so the pill +// is baked into the HTML and does not depend on the visitor's browser reaching +// workatastartup.com. Bounded so a slow feed cannot stall the response; a null +// result (timeout/error) simply renders no pill. The Footer refreshes this +// client-side to keep statically-built pages current. +const footerOpenings = await fetchOpeningsCount(AbortSignal.timeout(4000)); --- @@ -201,7 +209,7 @@ const showRssFeed = currentPath.startsWith('/blog') || currentPath.startsWith('/ -