From 24d90e9d7599aca7ab14ab0ae695d308ce532748 Mon Sep 17 00:00:00 2001 From: Pablo Pettinari Date: Fri, 8 May 2026 18:47:23 +0200 Subject: [PATCH 1/2] fix(contributors): use static cache to keep consuming pages out of ISR `unstable_cache` with a finite `revalidate` opts every consuming page into ISR. App-router pages that also read `public/content/` (e.g. the developers hub, blog listing) then break on Netlify when the serverless re-render runs without those files in the function bundle, silently caching empty data. Drop the revalidating `getGitHubContributors` wrapper and route `getAppPageContributorInfo` through `getStaticGitHubContributors`. The upstream Trigger.dev job refreshes contributor data weekly, so the daily revalidate was overhead anyway -- contributors now refresh on deploy. --- src/lib/data/index.ts | 6 ------ src/lib/utils/contributors.ts | 4 ++-- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/lib/data/index.ts b/src/lib/data/index.ts index 42a0af4f863..a5afe5c062b 100644 --- a/src/lib/data/index.ts +++ b/src/lib/data/index.ts @@ -193,12 +193,6 @@ export const getTranslationGlossary = createCachedGetter( CACHE_REVALIDATE_DAY ) -export const getGitHubContributors = createCachedGetter( - dataLayer.getGitHubContributors, - ["github-contributors"], - CACHE_REVALIDATE_DAY -) - export const getVideoThumbnails = createCachedGetter( dataLayer.getVideoThumbnails, ["video-thumbnails"], diff --git a/src/lib/utils/contributors.ts b/src/lib/utils/contributors.ts index f28b8ffd914..da6ce833c18 100644 --- a/src/lib/utils/contributors.ts +++ b/src/lib/utils/contributors.ts @@ -13,7 +13,7 @@ import { import { getAppPageLastCommitDate } from "./gh" import { getLocaleTimestamp } from "./time" -import { getGitHubContributors, getStaticGitHubContributors } from "@/lib/data" +import { getStaticGitHubContributors } from "@/lib/data" /** Sort team members to the end, preserving relative order within each group. */ const sortTeamToEnd = (contributors: FileContributor[]): FileContributor[] => @@ -54,7 +54,7 @@ export const getAppPageContributorInfo = async ( ) => { // TODO: Incorporate Crowdin contributor information - const contributorsData = await getGitHubContributors() + const contributorsData = await getStaticGitHubContributors() const gitHubContributors = contributorsData?.appPages[pagePath] ?? [] const uniqueGitHubContributors = gitHubContributors.filter( From 8570abc25c8f9b0bcfae67f8a4a9544406b3e722 Mon Sep 17 00:00:00 2001 From: Pablo Pettinari Date: Mon, 11 May 2026 14:18:10 +0200 Subject: [PATCH 2/2] test(e2e): assert tutorials listing renders internal + external entries Guard against the regression where ISR re-renders the page in Netlify's serverless runtime, fs.readFile of public/content/ fails, and internalTutorials silently collapses to []. External tutorials would keep rendering from their static JSON, so the test asserts both sources independently. When this suite runs against a deploy preview (PLAYWRIGHT_TEST_BASE_URL set in .github/workflows/tests.yml), it exercises the same serverless render path that hides the bug locally. --- tests/e2e/tutorials.spec.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 tests/e2e/tutorials.spec.ts diff --git a/tests/e2e/tutorials.spec.ts b/tests/e2e/tutorials.spec.ts new file mode 100644 index 00000000000..b2bb6788bb0 --- /dev/null +++ b/tests/e2e/tutorials.spec.ts @@ -0,0 +1,20 @@ +import { expect, test } from "@playwright/test" + +import externalTutorials from "@/data/externalTutorials.json" +import internalTutorialSlugs from "@/data/internalTutorials.json" + +const PAGE_URL = "/developers/tutorials/" + +test.describe("Tutorials Listing Page", () => { + test("lists both internal and external tutorials", async ({ page }) => { + await page.goto(PAGE_URL) + + const internalSlug = internalTutorialSlugs[0] + await expect( + page.locator(`a[href*="/developers/tutorials/${internalSlug}"]`).first() + ).toBeVisible() + + const externalUrl = externalTutorials.find((t) => t.lang === "en")!.url + await expect(page.locator(`a[href="${externalUrl}"]`).first()).toBeVisible() + }) +})