From 48b96e8a799963315f0a8b61cde5527b873b0d65 Mon Sep 17 00:00:00 2001 From: Danny White <3104761+dnywh@users.noreply.github.com> Date: Thu, 19 Mar 2026 13:45:11 +1100 Subject: [PATCH] fix(www): use imgSocial for blog OG images (#43842) ## What kind of change does this PR introduce? - Bug fix that resolves DEPR-396 - Additional improvements to blog post image handling ## What is the current behavior? Blog post Open Graph metadata often prefers `imgThumb` over `imgSocial`, so social previews on X, iMessage, and similar surfaces can render the on-site thumbnail instead of the intended social image. The image selection and path-normalization rules are also duplicated across blog surfaces, which makes the precedence rules easy to drift. ## What is the new behavior? - Centralizes blog image handling in `apps/www/lib/blog-images.ts` - Uses `imgSocial` first, then `imgThumb`, for blog OG/Twitter metadata - Uses `imgThumb` first, then `imgSocial`, then the placeholder, for blog thumbnails and post hero images - Normalizes relative blog image paths into absolute URLs for metadata - Adds warning-only validation during content reads/builds for partial or malformed `imgSocial` / `imgThumb` config - Updates a few recent blog posts so `imgThumb` is a thumbnail-only asset instead of duplicating the social image ## Additional context - Replaces #42319 with the additional above fixes - Added unit coverage for the shared image helper --------- Co-authored-by: Alan Daniel --- ...-01-15-read-replicas-vs-bigger-compute.mdx | 4 +- ...se-is-now-an-official-claude-connector.mdx | 2 +- .../2026-02-06-x-twitter-oauth-2-provider.mdx | 2 +- apps/www/app/blog/[slug]/page.tsx | 5 +- apps/www/components/Blog/BlogGridItem.tsx | 20 +-- apps/www/components/Blog/BlogPostRenderer.tsx | 16 +- apps/www/components/Blog/FeaturedThumb.tsx | 20 +-- apps/www/components/Events/EventGridItem.tsx | 8 +- apps/www/layouts/comparison.tsx | 12 +- apps/www/lib/blog-images.test.ts | 91 +++++++++++ apps/www/lib/blog-images.ts | 142 ++++++++++++++++++ apps/www/lib/posts.tsx | 11 ++ .../thumb.png | Bin 52373 -> 0 bytes apps/www/scripts/generateStaticContent.mjs | 72 +++++++++ 14 files changed, 351 insertions(+), 54 deletions(-) create mode 100644 apps/www/lib/blog-images.test.ts create mode 100644 apps/www/lib/blog-images.ts delete mode 100644 apps/www/public/images/blog/2026-02-03-supabase-is-now-an-official-claude-connector/thumb.png diff --git a/apps/www/_blog/2026-01-15-read-replicas-vs-bigger-compute.mdx b/apps/www/_blog/2026-01-15-read-replicas-vs-bigger-compute.mdx index 47e22c22323..e4ad71ff345 100644 --- a/apps/www/_blog/2026-01-15-read-replicas-vs-bigger-compute.mdx +++ b/apps/www/_blog/2026-01-15-read-replicas-vs-bigger-compute.mdx @@ -15,11 +15,9 @@ tags: - infrastructure toc_depth: 3 imgSocial: 'https://zhfonblqamxferhoguzj.supabase.co/functions/v1/generate-og?template=announcement&layout=text-only©=When+to+use%0A%5BRead+Replicas%5D%0Avs.+bigger+compute' -imgThumb: 'https://zhfonblqamxferhoguzj.supabase.co/functions/v1/generate-og?template=announcement&layout=text-only©=When+to+use%0A%5BRead+Replicas%5D%0Avs.+bigger+compute' +imgThumb: 'https://zhfonblqamxferhoguzj.supabase.co/functions/v1/generate-og?template=ruler&layout=icon-only&icon=icon-database.svg' --- -# When to use Read Replicas vs. bigger compute - When your database starts slowing down, you face a choice: make your existing database bigger, or spread the load across multiple databases. Both approaches work. Neither is universally correct. The right answer depends on your workload, your budget, and where the bottleneck actually is. This post walks through how to diagnose what is causing your database to slow down, when vertical scaling (bigger compute) makes sense, when horizontal read scaling (Read Replicas) is the better path, and how to make the decision with real numbers. diff --git a/apps/www/_blog/2026-02-03-supabase-is-now-an-official-claude-connector.mdx b/apps/www/_blog/2026-02-03-supabase-is-now-an-official-claude-connector.mdx index ba4e7a09588..afcb6ea4b8b 100644 --- a/apps/www/_blog/2026-02-03-supabase-is-now-an-official-claude-connector.mdx +++ b/apps/www/_blog/2026-02-03-supabase-is-now-an-official-claude-connector.mdx @@ -11,7 +11,7 @@ tags: - mcp - integrations imgSocial: '2026-02-03-supabase-is-now-an-official-claude-connector/og.png' -imgThumb: '2026-02-03-supabase-is-now-an-official-claude-connector/thumb.png' +imgThumb: 'https://zhfonblqamxferhoguzj.supabase.co/functions/v1/generate-og?template=partnerships&layout=icon-only&icon=supabase.svg&icon2=1770136381461-Claude.svg' toc_depth: 2 --- diff --git a/apps/www/_blog/2026-02-06-x-twitter-oauth-2-provider.mdx b/apps/www/_blog/2026-02-06-x-twitter-oauth-2-provider.mdx index bdcd9261c87..3faf08ee7f1 100644 --- a/apps/www/_blog/2026-02-06-x-twitter-oauth-2-provider.mdx +++ b/apps/www/_blog/2026-02-06-x-twitter-oauth-2-provider.mdx @@ -3,7 +3,7 @@ title: 'X / Twitter OAuth 2.0 is now available for Supabase Auth' description: 'You can now add "Sign in with X" to your application using the new X / Twitter (OAuth 2.0) provider in Supabase Auth.' author: fady imgSocial: x-twitter-oauth-2-provider/og.png -imgThumb: x-twitter-oauth-2-provider/og.png +imgThumb: https://zhfonblqamxferhoguzj.supabase.co/functions/v1/generate-og?template=ruler&layout=icon-only©=Introducing+Supabase%0Afor+%5BPlatforms%5D&icon=social-x.svg categories: - product tags: diff --git a/apps/www/app/blog/[slug]/page.tsx b/apps/www/app/blog/[slug]/page.tsx index 243f343da55..e2f68cd513b 100644 --- a/apps/www/app/blog/[slug]/page.tsx +++ b/apps/www/app/blog/[slug]/page.tsx @@ -3,6 +3,7 @@ import { draftMode } from 'next/headers' import BlogPostClient from './BlogPostClient' import { SITE_ORIGIN } from '@/lib/constants' +import { getAbsoluteBlogSocialImage } from '@/lib/blog-images' import { getAllPostSlugs, getPostdata, getSortedPosts } from '@/lib/posts' import type { Blog, BlogData, PostReturnType } from '@/types/post' @@ -40,7 +41,6 @@ export async function generateMetadata({ params }: { params: Promise }): } } - const { isEnabled: isDraft } = await draftMode() const matter = (await import('gray-matter')).default // Try to get static markdown post first @@ -48,8 +48,7 @@ export async function generateMetadata({ params }: { params: Promise }): const postContent = await getPostdata(slug, '_blog') const parsedContent = matter(postContent) as unknown as MatterReturn const blogPost = parsedContent.data - const blogImage = blogPost.imgThumb || blogPost.imgSocial - const metaImageUrl = blogImage && blogImage.startsWith('http') ? blogImage : undefined + const metaImageUrl = getAbsoluteBlogSocialImage(blogPost, SITE_ORIGIN) return { title: blogPost.title, diff --git a/apps/www/components/Blog/BlogGridItem.tsx b/apps/www/components/Blog/BlogGridItem.tsx index 8489fe4d785..cda53ea0c2d 100644 --- a/apps/www/components/Blog/BlogGridItem.tsx +++ b/apps/www/components/Blog/BlogGridItem.tsx @@ -3,6 +3,11 @@ import Image from 'next/image' import Link from 'next/link' import authors from '@/lib/authors.json' +import { + BLOG_GRID_IMAGE_SIZES, + BLOG_PLACEHOLDER_IMAGE, + getBlogThumbnailImage, +} from '@/lib/blog-images' import type Author from '@/types/author' import type PostTypes from '@/types/post' @@ -24,15 +29,7 @@ const BlogGridItem = ({ post }: Props) => { } } - const resolveImagePath = (img: string | undefined): string | null => { - if (!img) return null - return img.startsWith('/') || img.startsWith('http') ? img : `/images/blog/${img}` - } - - const imageUrl = - resolveImagePath(post.imgThumb) || - resolveImagePath(post.imgSocial) || - '/images/blog/blog-placeholder.png' + const imageUrl = getBlogThumbnailImage(post) ?? BLOG_PLACEHOLDER_IMAGE return ( {
{`${post.title} {

{dayjs(post.date).format('D MMM YYYY')}

{post.readingTime && ( <> -

•

+

·

{post.readingTime}

)} diff --git a/apps/www/components/Blog/BlogPostRenderer.tsx b/apps/www/components/Blog/BlogPostRenderer.tsx index 395610377ad..65d1c69725f 100644 --- a/apps/www/components/Blog/BlogPostRenderer.tsx +++ b/apps/www/components/Blog/BlogPostRenderer.tsx @@ -12,6 +12,7 @@ import type { ComponentType } from 'react' import type { PostReturnType, ProcessedBlogData, StaticAuthor, Tag } from 'types/post' import { Badge } from 'ui' +import { BLOG_POST_HERO_IMAGE_SIZES, getBlogThumbnailImage } from '@/lib/blog-images' import mdxComponents from '@/lib/mdx/mdxComponents' const ShareArticleActions = dynamic(() => import('@/components/Blog/ShareArticleActions')) @@ -131,11 +132,9 @@ const BlogPostRenderer = ({
) - const imageUrl = blogMetaData.imgThumb - ? blogMetaData.imgThumb.startsWith('/') || blogMetaData.imgThumb.startsWith('http') - ? blogMetaData.imgThumb - : `/images/blog/${blogMetaData.imgThumb}` - : '' + const imageUrl = getBlogThumbnailImage(blogMetaData, { + fallbackToPlaceholder: false, + }) return ( <> @@ -168,7 +167,7 @@ const BlogPostRenderer = ({

{blogMetaData.title}

{dayjs(blogMetaData.date).format('DD MMM YYYY')}

-

•

+

·

{(blogMetaData as any).readingTime}

{authors.length > 0 && ( @@ -231,14 +230,13 @@ const BlogPostRenderer = ({ allowFullScreen={true} /> ) : ( - blogMetaData.imgThumb && ( + imageUrl && (
{blogMetaData.title}
diff --git a/apps/www/components/Blog/FeaturedThumb.tsx b/apps/www/components/Blog/FeaturedThumb.tsx index 0fc6b6e52f3..72244182e97 100644 --- a/apps/www/components/Blog/FeaturedThumb.tsx +++ b/apps/www/components/Blog/FeaturedThumb.tsx @@ -2,6 +2,11 @@ import Image from 'next/image' import Link from 'next/link' import authors from '@/lib/authors.json' +import { + BLOG_FEATURED_IMAGE_SIZES, + BLOG_PLACEHOLDER_IMAGE, + getBlogThumbnailImage, +} from '@/lib/blog-images' import type PostTypes from '@/types/post' function FeaturedThumb(blog: PostTypes) { @@ -22,15 +27,7 @@ function FeaturedThumb(blog: PostTypes) { } function renderFeaturedThumb(blog: PostTypes, author: any[]) { - const resolveImagePath = (img: string | undefined): string | null => { - if (!img) return null - return img.startsWith('/') || img.startsWith('http') ? img : `/images/blog/${img}` - } - - const imageUrl = - resolveImagePath(blog.imgThumb) || - resolveImagePath(blog.imgSocial) || - '/images/blog/blog-placeholder.png' + const imageUrl = getBlogThumbnailImage(blog) ?? BLOG_PLACEHOLDER_IMAGE return (
@@ -42,8 +39,7 @@ function renderFeaturedThumb(blog: PostTypes, author: any[]) { blog thumbnail
{blog.formattedDate} - • + · {blog.readingTime}
diff --git a/apps/www/components/Events/EventGridItem.tsx b/apps/www/components/Events/EventGridItem.tsx index a16bee905ea..0d137a6c6e2 100644 --- a/apps/www/components/Events/EventGridItem.tsx +++ b/apps/www/components/Events/EventGridItem.tsx @@ -1,12 +1,12 @@ -import React from 'react' +import dayjs from 'dayjs' // import authors from 'lib/authors.json' import Image from 'next/image' import Link from 'next/link' +import React from 'react' + // import type Author from '~/types/author' import type PostTypes from '../../types/post' -import dayjs from 'dayjs' - interface Props { event: PostTypes } @@ -58,7 +58,7 @@ const EventGridItem = ({ event }: Props) => {

{dayjs(event.date).format('D MMM YYYY')}

{event.readingTime && ( <> -

•

+

·

{event.readingTime}

)} diff --git a/apps/www/layouts/comparison.tsx b/apps/www/layouts/comparison.tsx index a5a9949371b..4873f609cec 100644 --- a/apps/www/layouts/comparison.tsx +++ b/apps/www/layouts/comparison.tsx @@ -3,10 +3,10 @@ import { MDXRemote } from 'next-mdx-remote' import { NextSeo } from 'next-seo' import Image from 'next/image' import Link from 'next/link' -import { useRouter } from 'next/router' import React from 'react' import CTABanner from '~/components/CTABanner' import DefaultLayout from '~/components/Layouts/Default' +import { getAbsoluteBlogSocialImage } from '~/lib/blog-images' import { generateReadingTime } from '~/lib/helpers' interface Props { @@ -31,8 +31,6 @@ const LayoutComparison = ({ components, props }: Props) => { ) } - const { basePath } = useRouter() - const NextCard = (props: any) => { const { post, label, className } = props return ( @@ -77,12 +75,8 @@ const LayoutComparison = ({ components, props }: Props) => { }), }, images: (() => { - const img = props.blog.imgSocial || props.blog.imgThumb - if (!img) return [] - const url = - img.startsWith('/') || img.startsWith('http') - ? img - : `https://supabase.com${basePath}/images/blog/${img}` + const url = getAbsoluteBlogSocialImage(props.blog, 'https://supabase.com') + if (!url) return [] return [{ url }] })(), }} diff --git a/apps/www/lib/blog-images.test.ts b/apps/www/lib/blog-images.test.ts new file mode 100644 index 00000000000..09f2ecf879b --- /dev/null +++ b/apps/www/lib/blog-images.test.ts @@ -0,0 +1,91 @@ +import { describe, expect, it, vi } from 'vitest' + +import { + BLOG_PLACEHOLDER_IMAGE, + getAbsoluteBlogSocialImage, + getBlogSocialImage, + getBlogThumbnailImage, + resolveBlogImagePath, + validateBlogFrontmatterImages, +} from './blog-images' + +describe('blog image helpers', () => { + it('prefers imgThumb for on-site blog thumbnails', () => { + expect( + getBlogThumbnailImage({ + imgThumb: 'example/thumb.png', + imgSocial: 'example/og.png', + }) + ).toBe('/images/blog/example/thumb.png') + }) + + it('falls back to imgSocial for on-site blog thumbnails', () => { + expect( + getBlogThumbnailImage({ + imgSocial: 'example/og.png', + }) + ).toBe('/images/blog/example/og.png') + }) + + it('prefers imgSocial for social metadata', () => { + expect( + getBlogSocialImage({ + imgThumb: 'example/thumb.png', + imgSocial: 'example/og.png', + }) + ).toBe('/images/blog/example/og.png') + }) + + it('returns absolute URLs unchanged and prefixes relative paths', () => { + expect(resolveBlogImagePath('https://example.com/og.png')).toBe('https://example.com/og.png') + expect(resolveBlogImagePath('/images/blog/example/og.png')).toBe('/images/blog/example/og.png') + expect(resolveBlogImagePath('example/og.png')).toBe('/images/blog/example/og.png') + }) + + it('builds absolute social image URLs', () => { + expect( + getAbsoluteBlogSocialImage( + { + imgSocial: 'example/og.png', + }, + 'https://supabase.com' + ) + ).toBe('https://supabase.com/images/blog/example/og.png') + }) + + it('uses the placeholder when neither image is present', () => { + expect(getBlogThumbnailImage({})).toBe(BLOG_PLACEHOLDER_IMAGE) + expect(getBlogThumbnailImage({}, { fallbackToPlaceholder: false })).toBeUndefined() + }) + + it('warns when blog frontmatter is missing one of the image fields', () => { + const warn = vi.fn() + + validateBlogFrontmatterImages( + { + imgThumb: 'example/thumb.png', + }, + '/tmp/example-post.mdx', + warn + ) + + expect(warn).toHaveBeenCalledWith(expect.stringContaining('missing "imgSocial"')) + }) + + it('warns when a blog image uses the prefixed public path', () => { + const warn = vi.fn() + + validateBlogFrontmatterImages( + { + imgThumb: '/images/blog/example/thumb.png', + imgSocial: 'example/og.png', + }, + '/tmp/example-prefixed-post.mdx', + warn + ) + + expect(warn).toHaveBeenCalledWith( + expect.stringContaining('should not include the "/images/blog/" prefix') + ) + }) +}) diff --git a/apps/www/lib/blog-images.ts b/apps/www/lib/blog-images.ts new file mode 100644 index 00000000000..af5145eee7d --- /dev/null +++ b/apps/www/lib/blog-images.ts @@ -0,0 +1,142 @@ +import { SITE_ORIGIN } from './constants' + +type BlogImageFields = { + imgSocial?: string + imgThumb?: string +} + +type ValidationWarn = (message?: unknown, ...optionalParams: unknown[]) => void + +const warnedBlogImageIssues = new Set() + +export const BLOG_PLACEHOLDER_IMAGE = '/images/blog/blog-placeholder.png' +export const BLOG_GRID_IMAGE_SIZES = '(max-width: 1023px) 100vw, (max-width: 1279px) 50vw, 33vw' +export const BLOG_FEATURED_IMAGE_SIZES = '(max-width: 1023px) 100vw, 42vw' +export const BLOG_POST_HERO_IMAGE_SIZES = '(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw' + +function isAbsoluteUrl(image: string) { + return image.startsWith('http://') || image.startsWith('https://') +} + +function warnOnce(key: string, message: string, warn: ValidationWarn) { + if (warnedBlogImageIssues.has(key)) return + + warnedBlogImageIssues.add(key) + warn(message) +} + +export function resolveBlogImagePath(image: string) { + if (isAbsoluteUrl(image) || image.startsWith('/')) { + return image + } + + return `/images/blog/${image}` +} + +export function toAbsoluteBlogImageUrl(image: string, siteOrigin: string = SITE_ORIGIN) { + const resolvedImage = resolveBlogImagePath(image) + + if (isAbsoluteUrl(resolvedImage)) { + return resolvedImage + } + + return `${siteOrigin}${resolvedImage}` +} + +export function getBlogThumbnailImage( + { imgThumb, imgSocial }: BlogImageFields, + options: { fallbackToPlaceholder?: boolean } = {} +) { + const image = imgThumb || imgSocial + + if (!image) { + return options.fallbackToPlaceholder === false ? undefined : BLOG_PLACEHOLDER_IMAGE + } + + return resolveBlogImagePath(image) +} + +export function getBlogSocialImage({ imgThumb, imgSocial }: BlogImageFields) { + const image = imgSocial || imgThumb + return image ? resolveBlogImagePath(image) : undefined +} + +export function getAbsoluteBlogSocialImage( + { imgThumb, imgSocial }: BlogImageFields, + siteOrigin: string = SITE_ORIGIN +) { + const image = imgSocial || imgThumb + return image ? toAbsoluteBlogImageUrl(image, siteOrigin) : undefined +} + +export function validateBlogFrontmatterImages( + frontmatter: BlogImageFields, + filePath: string, + warn: ValidationWarn = console.warn +) { + const hasImgSocial = typeof frontmatter.imgSocial !== 'undefined' + const hasImgThumb = typeof frontmatter.imgThumb !== 'undefined' + + if (hasImgSocial && !hasImgThumb) { + warnOnce( + `${filePath}:imgThumb:missing`, + `[blog images] ${filePath}: missing "imgThumb". Adding it keeps on-site thumbnails separate from social previews.`, + warn + ) + } + + if (hasImgThumb && !hasImgSocial) { + warnOnce( + `${filePath}:imgSocial:missing`, + `[blog images] ${filePath}: missing "imgSocial". Adding it keeps social previews separate from on-site thumbnails.`, + warn + ) + } + + const imageFields = [ + ['imgSocial', frontmatter.imgSocial], + ['imgThumb', frontmatter.imgThumb], + ] as const + + for (const [fieldName, imageValue] of imageFields) { + if (typeof imageValue === 'undefined') { + continue + } + + if (typeof imageValue !== 'string') { + warnOnce( + `${filePath}:${fieldName}:invalid-type`, + `[blog images] ${filePath}: "${fieldName}" should be a string URL or a relative blog image path.`, + warn + ) + continue + } + + const trimmedValue = imageValue.trim() + + if (!trimmedValue) { + warnOnce( + `${filePath}:${fieldName}:empty`, + `[blog images] ${filePath}: "${fieldName}" is empty. Remove it or provide a valid image path.`, + warn + ) + continue + } + + if (trimmedValue.startsWith('/images/blog/')) { + warnOnce( + `${filePath}:${fieldName}:prefixed`, + `[blog images] ${filePath}: "${fieldName}" should not include the "/images/blog/" prefix. Use a relative path like "my-post/og.png" instead.`, + warn + ) + } + + if (trimmedValue.startsWith('./') || trimmedValue.startsWith('../')) { + warnOnce( + `${filePath}:${fieldName}:relative-dot`, + `[blog images] ${filePath}: "${fieldName}" should use a clean relative blog path, not "${trimmedValue}".`, + warn + ) + } + } +} diff --git a/apps/www/lib/posts.tsx b/apps/www/lib/posts.tsx index 494a7c138e1..6615fe0bc2d 100644 --- a/apps/www/lib/posts.tsx +++ b/apps/www/lib/posts.tsx @@ -1,6 +1,7 @@ import fs from 'fs' import matter from 'gray-matter' import path from 'path' +import { validateBlogFrontmatterImages } from './blog-images' import { generateReadingTime } from './helpers' type Directories = '_blog' | '_case-studies' | '_customers' | '_alternatives' | '_events' @@ -66,6 +67,10 @@ export const getSortedPosts = ({ content: string } + if (directory === '_blog') { + validateBlogFrontmatterImages(data as { imgSocial?: string; imgThumb?: string }, fullPath) + } + const options: Intl.DateTimeFormatOptions = { month: 'long', day: 'numeric', year: 'numeric' } const formattedDate = new Date(data.date).toLocaleDateString('en-IN', options) @@ -169,6 +174,12 @@ export const getPostdata = async (slug: string, directory: string) => { const fullPath = path.join(postDirectory, found) const postContent = fs.readFileSync(fullPath, 'utf8') + + if (directory === '_blog') { + const { data } = matter(postContent) as unknown as { data: { [key: string]: any } } + validateBlogFrontmatterImages(data as { imgSocial?: string; imgThumb?: string }, fullPath) + } + return postContent } diff --git a/apps/www/public/images/blog/2026-02-03-supabase-is-now-an-official-claude-connector/thumb.png b/apps/www/public/images/blog/2026-02-03-supabase-is-now-an-official-claude-connector/thumb.png deleted file mode 100644 index 09a103cd53d66c5e93a4074e087f3a8fb51c948e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 52373 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 diff --git a/apps/www/scripts/generateStaticContent.mjs b/apps/www/scripts/generateStaticContent.mjs index ad6dcc55614..92c0f35b7c6 100644 --- a/apps/www/scripts/generateStaticContent.mjs +++ b/apps/www/scripts/generateStaticContent.mjs @@ -13,6 +13,76 @@ dayjs.extend(advancedFormat) // Constants const FILENAME_SUBSTRING = 11 // based on YYYY-MM-DD format +const warnedBlogImageIssues = new Set() + +const warnBlogImageIssue = (key, message) => { + if (warnedBlogImageIssues.has(key)) return + + warnedBlogImageIssues.add(key) + console.warn(message) +} + +const validateBlogFrontmatterImages = (frontmatter, filePath) => { + const hasImgSocial = typeof frontmatter.imgSocial !== 'undefined' + const hasImgThumb = typeof frontmatter.imgThumb !== 'undefined' + + if (hasImgSocial && !hasImgThumb) { + warnBlogImageIssue( + `${filePath}:imgThumb:missing`, + `[blog images] ${filePath}: missing "imgThumb". Adding it keeps on-site thumbnails separate from social previews.` + ) + } + + if (hasImgThumb && !hasImgSocial) { + warnBlogImageIssue( + `${filePath}:imgSocial:missing`, + `[blog images] ${filePath}: missing "imgSocial". Adding it keeps social previews separate from on-site thumbnails.` + ) + } + + const imageFields = [ + ['imgSocial', frontmatter.imgSocial], + ['imgThumb', frontmatter.imgThumb], + ] + + for (const [fieldName, imageValue] of imageFields) { + if (typeof imageValue === 'undefined') { + continue + } + + if (typeof imageValue !== 'string') { + warnBlogImageIssue( + `${filePath}:${fieldName}:invalid-type`, + `[blog images] ${filePath}: "${fieldName}" should be a string URL or a relative blog image path.` + ) + continue + } + + const trimmedValue = imageValue.trim() + + if (!trimmedValue) { + warnBlogImageIssue( + `${filePath}:${fieldName}:empty`, + `[blog images] ${filePath}: "${fieldName}" is empty. Remove it or provide a valid image path.` + ) + continue + } + + if (trimmedValue.startsWith('/images/blog/')) { + warnBlogImageIssue( + `${filePath}:${fieldName}:prefixed`, + `[blog images] ${filePath}: "${fieldName}" should not include the "/images/blog/" prefix. Use a relative path like "my-post/og.png" instead.` + ) + } + + if (trimmedValue.startsWith('./') || trimmedValue.startsWith('../')) { + warnBlogImageIssue( + `${filePath}:${fieldName}:relative-dot`, + `[blog images] ${filePath}: "${fieldName}" should use a clean relative blog path, not "${trimmedValue}".` + ) + } + } +} /** * Fixes Safari dates sorting bug @@ -53,6 +123,8 @@ const getStaticBlogPosts = () => { const fileContents = fsSync.readFileSync(fullPath, 'utf8') const { data } = matter(fileContents) + validateBlogFrontmatterImages(data, fullPath) + const options = { month: 'long', day: 'numeric', year: 'numeric' } const formattedDate = new Date(data.date).toLocaleDateString('en-IN', options)