Dynamic star count on website (#306)
This commit is contained in:
parent
cd8ce2f752
commit
6313812f3f
@ -9,9 +9,45 @@ import { NewsletterSignup } from "@/components/newsletter-signup";
|
||||
import { SiteFooter } from "@/components/site-footer";
|
||||
import { featureGroups } from "@/lib/feature-pages";
|
||||
|
||||
const githubStarCount = "2.1k";
|
||||
const GITHUB_REPO = "every-app/open-seo";
|
||||
// Used if GitHub is unreachable at build time so the header never renders empty.
|
||||
const FALLBACK_STAR_COUNT = "2.1k";
|
||||
|
||||
const mobileNavItems = [
|
||||
// Round to the nearest hundred and render in thousands, e.g. 3140 -> "3.1k".
|
||||
function formatStarCount(count: number): string {
|
||||
if (count < 1000) return String(count);
|
||||
return `${(Math.round(count / 100) / 10).toString()}k`;
|
||||
}
|
||||
|
||||
async function fetchGithubStarCount(): Promise<string> {
|
||||
try {
|
||||
const res = await fetch(`https://api.github.com/repos/${GITHUB_REPO}`, {
|
||||
headers: {
|
||||
Accept: "application/vnd.github+json",
|
||||
// GitHub rejects requests without a User-Agent.
|
||||
"User-Agent": "openseo-landing",
|
||||
},
|
||||
});
|
||||
if (!res.ok) return FALLBACK_STAR_COUNT;
|
||||
const data = (await res.json()) as { stargazers_count?: number };
|
||||
return typeof data.stargazers_count === "number"
|
||||
? formatStarCount(data.stargazers_count)
|
||||
: FALLBACK_STAR_COUNT;
|
||||
} catch {
|
||||
return FALLBACK_STAR_COUNT;
|
||||
}
|
||||
}
|
||||
|
||||
// Memoized for the duration of a build so prerendering every marketing page
|
||||
// only hits GitHub once instead of once per page.
|
||||
let starCountPromise: Promise<string> | null = null;
|
||||
function loadGithubStarCount(): Promise<string> {
|
||||
starCountPromise ??= fetchGithubStarCount();
|
||||
return starCountPromise;
|
||||
}
|
||||
|
||||
function getMobileNavItems(githubStarCount: string) {
|
||||
return [
|
||||
{
|
||||
label: "Product",
|
||||
links: [
|
||||
@ -37,7 +73,8 @@ const mobileNavItems = [
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
];
|
||||
}
|
||||
|
||||
function GitHubIcon({ size = 18 }: { size?: number }) {
|
||||
return (
|
||||
@ -85,12 +122,20 @@ function CloseIcon({ size = 28 }: { size?: number }) {
|
||||
}
|
||||
|
||||
export const Route = createFileRoute("/_marketing")({
|
||||
// Runs at prerender/SSR time, so the count is baked into the static HTML that
|
||||
// Cloudflare serves from the edge — no per-viewer request for it.
|
||||
loader: async () => ({ githubStarCount: await loadGithubStarCount() }),
|
||||
// The value is fixed per build; never refetch it on client navigation.
|
||||
staleTime: Infinity,
|
||||
component: MarketingLayout,
|
||||
});
|
||||
|
||||
function MarketingLayout() {
|
||||
const { githubStarCount } = Route.useLoaderData();
|
||||
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
||||
const { pathname } = useLocation();
|
||||
|
||||
const mobileNavItems = getMobileNavItems(githubStarCount);
|
||||
// The home route owns the full viewport width (and its own footer/CTA band);
|
||||
// every other marketing page gets the shared marketing canvas and footer.
|
||||
const isHome = pathname === "/";
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user