Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/og/ogImage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ export function computeOgPath(pathname: string): string {
if (clean === '/speakers') return '/og/pages/speakers.png'
if (clean === '/' || clean === '') return '/og/default.png'
if (clean.startsWith('/schedule')) return '/og/pages/schedule.png'
if (clean === '/blog') return '/og/pages/blog.png'
// Individual /blog/<slug> articles pass an explicit metaImage (see
// src/pages/blog/[...slug].astro); a stray unknown /blog/* route falls
// back to default.
if (clean.startsWith('/blog')) return '/og/default.png'
const staticPages = ['team', 'jobs', 'location', 'anecdotes', 'jeu', 'coc', '404']
const seg = clean.replace(/^\//, '').split('/')[0]
Expand Down
6 changes: 5 additions & 1 deletion src/pages/blog/[...slug].astro
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,13 @@ export async function getStaticPaths() {

const { entry } = Astro.props
const { Content } = await render(entry)
const ogImage = `${import.meta.env.SITE}/og/blog/${entry.id}.png`
---

<LayoutWithTitle title={entry.data.title} description={entry.data.date.toLocaleString('fr-FR', { dateStyle: 'long' })}>
<LayoutWithTitle
title={entry.data.title}
description={entry.data.date.toLocaleString('fr-FR', { dateStyle: 'long' })}
metaImage={ogImage}>
<MarkdownWrapper>
<Content />
</MarkdownWrapper>
Expand Down
52 changes: 52 additions & 0 deletions src/pages/og/blog/[slug].png.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { createHash } from 'node:crypto'
import type { APIRoute } from 'astro'
import { getCollection } from 'astro:content'
import {
getFlamingoDataUri,
getMonochromeLogoDataUri,
pngResponse,
readOgCache,
renderOg,
writeOgCache,
} from '../../../og/render'
import { pageTemplate } from '../../../og/templates'

export async function getStaticPaths() {
const entries = await getCollection('blog')
return entries.map((entry) => ({
params: { slug: entry.id },
props: {
title: entry.data.title,
date: entry.data.date.toISOString(),
},
}))
}

export const GET: APIRoute = async ({ params, props }) => {
const contentHash = createHash('sha256')
.update(props.title + props.date)
.digest('hex')
.slice(0, 8)
const cacheKey = `blog/${params.slug}-${contentHash}.png`
const cached = await readOgCache(cacheKey)
if (cached) return pngResponse(cached)
Comment on lines +25 to +32
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The OG cache key is only based on the slug (blog/<slug>.png). In CI, .og-cache is restored across builds, and reads are enabled, so updating a post’s title/date/content won’t invalidate the cached PNG and the OG image can become stale in production.

Consider versioning the cache key with some representation of the post inputs (e.g., hash of props.title + props.date, or a content digest passed from getStaticPaths) so content edits force a re-render while keeping the public URL stable.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot apply changes based on this feedback

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 90b24e6. The cache key now includes an 8-character SHA-256 hash of props.title + props.date, so any edit to a post's title or date produces a new key and forces a re-render, while the public URL (/og/blog/<slug>.png) remains unchanged.


const [logo, flamingo] = await Promise.all([getMonochromeLogoDataUri(), getFlamingoDataUri()])
const date = new Date(props.date).toLocaleDateString('fr-FR', {
day: 'numeric',
month: 'long',
year: 'numeric',
})
const png = await renderOg(
pageTemplate(
logo,
{
eyebrow: `Blog · ${date}`,
title: props.title,
},
flamingo
)
)
await writeOgCache(cacheKey, png)
return pngResponse(png)
}
5 changes: 5 additions & 0 deletions src/pages/og/pages/[page].png.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ const PAGES: Record<string, PageSpec> = {
title: 'Code de conduite',
description: 'Une conférence respectueuse, inclusive et bienveillante pour tou·te·s.',
},
blog: {
eyebrow: 'Blog',
title: 'Blog',
description: "Coulisses, chiffres et décisions de l'équipe Sunny Tech.",
},
'404': {
eyebrow: 'Erreur 404',
title: 'Page introuvable',
Expand Down
Loading