fix(website): render releases from local notes, drop GitHub gate (#21238)

The releases page gated visible notes on fetchLatestGithubReleaseTag();
when that unauthenticated GitHub call was rate-limited at build (common
on shared Cloudflare build IPs, no GITHUB_TOKEN), it returned null and
getVisibleReleaseNotes returned [], rendering 'No releases are visible
yet for the current published version' — the exact prod symptom.
force-static made each deploy a coin flip.

Committed MDX (+images) is the single source of truth, so gate
visibility on nothing: render all local notes deterministically with no
network call. Removes the dev/prod divergence and deletes the now-dead
fetch-latest-release-tag.ts and get-visible-releases.ts.
This commit is contained in:
Abdullah.
2026-06-05 13:59:40 +05:00
committed by GitHub
parent cd540098f1
commit b60a91a075
3 changed files with 7 additions and 74 deletions
@@ -1,8 +1,6 @@
import { fetchCommunityStats } from '@/lib/community/fetch-community-stats';
import { getRouteI18n, type LocaleRouteParams } from '@/lib/i18n/server';
import { mergeSocialLinkLabels } from '@/lib/community/merge-social-link-labels';
import { fetchLatestGithubReleaseTag } from '@/lib/releases/fetch-latest-release-tag';
import { getVisibleReleaseNotes } from '@/lib/releases/get-visible-releases';
import { loadLocalReleaseNotes } from '@/lib/releases/load-local-release-notes';
import { ReleasesHero } from '@/app/[locale]/releases/_components/ReleasesHero';
import { Menu, MENU_DATA } from '@/sections/Menu';
@@ -25,22 +23,17 @@ type ReleasesPageProps = {
};
export default async function ReleasesPage({ params }: ReleasesPageProps) {
const allNotes = loadLocalReleaseNotes();
const [, latestTag, stats] = await Promise.all([
const notes = loadLocalReleaseNotes();
const [, stats] = await Promise.all([
getRouteI18n(params),
fetchLatestGithubReleaseTag(),
fetchCommunityStats(),
]);
const menuSocialLinks = mergeSocialLinkLabels(MENU_DATA.socialLinks, stats);
const visibleNotes =
process.env.NODE_ENV === 'development'
? allNotes
: getVisibleReleaseNotes(allNotes, latestTag);
return (
<>
{visibleNotes.length > 0 ? (
<JsonLd data={buildReleaseListJsonLd(visibleNotes)} />
{notes.length > 0 ? (
<JsonLd data={buildReleaseListJsonLd(notes)} />
) : null}
{/*
* Above-the-fold milestone scene texture. Preload kicks off the
@@ -59,26 +52,22 @@ export default async function ReleasesPage({ params }: ReleasesPageProps) {
<ReleasesHero />
<ReleaseNotesSection>
{allNotes.length === 0 ? (
{notes.length === 0 ? (
<ReleaseNotesEmptyMessage>
Releases were not found. Add MDX under{' '}
<strong>packages/twenty-website/src/content/releases</strong> and
images under{' '}
<strong>packages/twenty-website/public/images/releases</strong>.
</ReleaseNotesEmptyMessage>
) : visibleNotes.length === 0 ? (
<ReleaseNotesEmptyMessage>
No releases are visible yet for the current published version.
</ReleaseNotesEmptyMessage>
) : (
visibleNotes.map((note, index) => (
notes.map((note, index) => (
<Fragment key={note.slug}>
<ReleaseNotesReleaseEntry
content={note.content}
date={note.date}
release={note.release}
/>
{index < visibleNotes.length - 1 ? <ReleaseNotesDivider /> : null}
{index < notes.length - 1 ? <ReleaseNotesDivider /> : null}
</Fragment>
))
)}
@@ -1,24 +0,0 @@
export async function fetchLatestGithubReleaseTag(): Promise<string | null> {
try {
const headers: Record<string, string> = {
Accept: 'application/vnd.github+json',
'X-GitHub-Api-Version': '2022-11-28',
};
if (process.env.GITHUB_TOKEN) {
headers.Authorization = `Bearer ${process.env.GITHUB_TOKEN}`;
}
const response = await fetch(
'https://api.github.com/repos/twentyhq/twenty/releases/latest',
{ headers },
);
if (!response.ok) return null;
const data = (await response.json()) as { tag_name?: string };
return data.tag_name ?? null;
} catch {
return null;
}
}
@@ -1,32 +0,0 @@
import type { LocalReleaseNote } from '@/lib/releases/types';
import { getFormattedReleaseNumber } from '@/lib/releases/get-formatted-release-number';
function safeReleaseNumber(version: string): number | null {
try {
return getFormattedReleaseNumber(version);
} catch {
return null;
}
}
export function getVisibleReleaseNotes(
notes: LocalReleaseNote[],
latestPublishedTag: string | null,
): LocalReleaseNote[] {
if (!latestPublishedTag) {
return [];
}
const publishedNumber = safeReleaseNumber(latestPublishedTag);
if (publishedNumber === null) {
return [];
}
return notes.filter((note) => {
const noteNumber = safeReleaseNumber(note.release);
if (noteNumber === null) {
return false;
}
return noteNumber <= publishedNumber;
});
}