fix(website): show real contributor count on every page shell

The contributor badge rendered 0 on Playbook, Cookbook, home and custom
pages, and reverted to 0 on hydration everywhere else.

Two causes:
- Shells that bypass PageShell (LearningArticleLayout, PlaybookIndex,
  PlaybookArticleBody, CookbookIndex, HomePage, TopNav, Custom) defaulted
  contributorsTotal to 0 instead of the site-data value.
- site-data read .all-contributorsrc with node:fs at module scope. Those
  shells are client:load hydrated, so the read threw in the browser and
  the count reset to 0 after hydration.

The count is now read once in astro.config.mjs and inlined through
vite.define as __CONTRIBUTORS_TOTAL__, so it is a literal in both the
server render and the client bundle.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 80686fef-efe3-4cdd-8cd6-bfa61a5d0af6
This commit is contained in:
Aaron Powell
2026-08-17 12:31:25 +10:00
parent 8d31163c00
commit 31f586489e
10 changed files with 47 additions and 19 deletions
+24
View File
@@ -1,9 +1,30 @@
import sitemap from "@astrojs/sitemap";
import react from "@astrojs/react";
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { defineConfig } from "astro/config";
import remarkGithubAdmonitionsToDirectives from "remark-github-admonitions-to-directives";
import pagefindResources from "./src/integrations/pagefind-resources";
// The contributor count is read from the repo's all-contributors manifest at
// build time and inlined as a literal into both the server render and the
// client bundle. It cannot be read at module scope in src/ because the shells
// that display it are `client:load` hydrated: `node:fs` is unavailable in the
// browser, so the read would fail there and the badge would reset to 0 on
// hydration even though the server-rendered HTML had the right number.
function readContributorsTotal() {
try {
const here = path.dirname(fileURLToPath(import.meta.url));
const rc = JSON.parse(
fs.readFileSync(path.resolve(here, "..", ".all-contributorsrc"), "utf8"),
);
return Array.isArray(rc.contributors) ? rc.contributors.length : 0;
} catch {
return 0;
}
}
// Playbook course content mirrored from external workshop repos is authored in
// GitHub admonition syntax (`> [!NOTE]`). This remark plugin rewrites those
// callouts into directives before rendering, so the same syntax used in the
@@ -70,6 +91,9 @@ export default defineConfig({
},
trailingSlash: "always",
vite: {
define: {
__CONTRIBUTORS_TOTAL__: JSON.stringify(readContributorsTotal()),
},
// @primer/react-brand's default entrypoint is CJS, so Node's ESM loader
// cannot detect its named exports during SSR. The package also ships a
// proper ESM build; alias to it so named imports resolve in both the