fix(org): favicon fallback, OG logo styling, and Discord embed CDN URLs (#714)

* fix(org): serve OG images from CDN for Discord embeds

Discord times out on slow dynamic OG generation (~3-5s). Persist generated
images to R2, point meta tags at the media CDN, and warm the API endpoint
on org-site page loads so crawlers get a fast static PNG URL.

Co-authored-by: Rotimi Best <[email protected]>

* docs: add org-site Open Graph and favicon guides

Document user-facing sharing behavior and contributor architecture
for dynamic OG images, favicon fallback, R2/CDN caching, and embed
cache invalidation.

Co-authored-by: Rotimi Best <[email protected]>

* fix(org-site): address PR review on OG CDN fallback and docs

- Fall back to API OG URL when CDN object is not yet available (HEAD check)
- Cancel warm-fetch response body to avoid connection pool exhaustion
- Align tenant-router OG edge cache TTL with one-hour API revalidation
- Fix favicon docs navigation and add Custom Domain tab screenshot
- Replace Chiberry Artistry example with AI Academy

Co-authored-by: Rotimi Best <[email protected]>

* fix(org-site): accept docker hostnames for OG warm URL

Use isValidHttpUrl for PRIVATE_SERVER_URL so internal values like
http://api:3081 work for SSR warm fetches in Docker Compose.

Co-authored-by: Rotimi Best <[email protected]>

---------

Co-authored-by: Cursor Agent <[email protected]>
Co-authored-by: Rotimi Best <[email protected]>
This commit is contained in:
Rotimi Best
2026-07-09 13:38:49 +01:00
committed by GitHub
co-authored by Rotimi Best Cursor Agent
parent bd2585f8fd
commit 35f5216839
17 changed files with 557 additions and 39 deletions
+9 -1
View File
@@ -323,6 +323,7 @@ export default {
const upstreamHost = isApiCall ? env.API_UPSTREAM_HOST : env.DASHBOARD_UPSTREAM_HOST;
const upstreamPath = isProxiedApiCall ? url.pathname.slice(PROXY_PREFIX.length) || '/' : url.pathname;
const isOrgSiteOgImage = isProxiedApiCall && upstreamPath.startsWith('/org-site/og/');
const upstreamUrl = new URL(request.url);
upstreamUrl.protocol = 'https:';
@@ -346,13 +347,14 @@ export default {
// cold browser cache (new device, or post-deploy hash change) stampedes the
// origin for every chunk.
const isImmutableAsset = url.pathname.startsWith('/_app/immutable/');
const edgeCacheTtl = isImmutableAsset ? 31536000 : isOrgSiteOgImage ? 3600 : undefined;
const upstreamResponse = await fetch(upstreamUrl.toString(), {
method: request.method,
headers: upstreamHeaders,
body: request.body,
redirect: 'manual',
cf: isImmutableAsset ? { cacheEverything: true, cacheTtl: 31536000 } : undefined
cf: edgeCacheTtl ? { cacheEverything: true, cacheTtl: edgeCacheTtl } : undefined
});
if (isImmutableAsset) {
@@ -361,6 +363,12 @@ export default {
return response;
}
if (isOrgSiteOgImage && upstreamResponse.ok) {
const response = new Response(upstreamResponse.body, upstreamResponse);
response.headers.set('Cache-Control', 'public, max-age=3600, stale-while-revalidate=86400');
return response;
}
return upstreamResponse;
}
} satisfies ExportedHandler<Env>;