From 8b0de489ff6f2de6a424bcb67e94d9db30f2e5f2 Mon Sep 17 00:00:00 2001 From: michaelcanova Date: Fri, 9 Oct 2026 11:18:08 -0400 Subject: [PATCH] [RFP] Updating image scaling for mobile/social media --- app/api/og/route.tsx | 233 +++++++++--------- app/grant/[id]/[slug]/page.tsx | 5 +- .../items/FeedItemGrantWithApplicants.tsx | 2 +- components/Funding/GrantDetailsInline.tsx | 8 +- 4 files changed, 132 insertions(+), 116 deletions(-) diff --git a/app/api/og/route.tsx b/app/api/og/route.tsx index 10809fca2..fd2ae6301 100644 --- a/app/api/og/route.tsx +++ b/app/api/og/route.tsx @@ -3,151 +3,158 @@ import { NextRequest } from 'next/server'; export const runtime = 'edge'; +const OG_IMAGE_SIZE = { width: 1200, height: 630 }; + export async function GET(request: NextRequest) { const { searchParams } = new URL(request.url); const title = searchParams.get('title'); const description = searchParams.get('description'); const author = searchParams.get('author'); const type = searchParams.get('type') || 'article'; + const image = searchParams.get('image'); + + if (image) { + return new ImageResponse( +
+ +
, + OG_IMAGE_SIZE + ); + } return new ImageResponse( - ( +
+ {/* Background pattern */} +
+ + {/* Logo and brand */}
- {/* Background pattern */} -
+
- {/* Logo and brand */} + {/* Content type badge */} + {type && (
- ResearchHub + {type}
+ )} - {/* Content type badge */} - {type && ( -
- {type} -
- )} - - {/* Title */} - {title && ( -

- {title} -

- )} + {/* Title */} + {title && ( +

+ {title} +

+ )} - {/* Description */} - {description && ( -

- {description} -

- )} + {/* Description */} + {description && ( +

+ {description} +

+ )} - {/* Author */} - {author && ( + {/* Author */} + {author && ( +
-
- {author.charAt(0).toUpperCase()} -
- By {author} + {author.charAt(0).toUpperCase()}
- )} -
- ), - { - width: 1200, - height: 630, - } + By {author} +
+ )} +
, + OG_IMAGE_SIZE ); } diff --git a/app/grant/[id]/[slug]/page.tsx b/app/grant/[id]/[slug]/page.tsx index 1b0b345c5..102bb8630 100644 --- a/app/grant/[id]/[slug]/page.tsx +++ b/app/grant/[id]/[slug]/page.tsx @@ -2,6 +2,7 @@ import { Metadata } from 'next'; import { notFound } from 'next/navigation'; import { PostService } from '@/services/post.service'; import { getWorkMetadata } from '@/lib/metadata-helpers'; +import { SITE_CONFIG } from '@/lib/metadata'; import { GrantContentSwitcher } from '@/components/Funding/GrantContentSwitcher'; interface Props { @@ -25,8 +26,10 @@ async function getGrant(id: string) { export async function generateMetadata({ params }: Props): Promise { const { id, slug } = await params; const grant = await getGrant(id); + const shareImage = + grant.image && `${SITE_CONFIG.url}/api/og?image=${encodeURIComponent(grant.image)}`; return getWorkMetadata({ - work: grant, + work: { ...grant, image: shareImage }, url: `/grant/${id}/${slug}`, }); } diff --git a/components/Feed/items/FeedItemGrantWithApplicants.tsx b/components/Feed/items/FeedItemGrantWithApplicants.tsx index e0de09367..530f6ef24 100644 --- a/components/Feed/items/FeedItemGrantWithApplicants.tsx +++ b/components/Feed/items/FeedItemGrantWithApplicants.tsx @@ -180,7 +180,7 @@ export const FeedItemGrantWithApplicants: FC = src={content.previewImage} alt={content.title} fill - className="object-cover transition-transform duration-300 group-hover:scale-105" + className="object-contain object-top sm:object-cover sm:object-center transition-transform duration-300 group-hover:scale-105" sizes="(max-width: 768px) 100vw, 660px" /> ) : ( diff --git a/components/Funding/GrantDetailsInline.tsx b/components/Funding/GrantDetailsInline.tsx index 19ce0f5e9..b110f7d81 100644 --- a/components/Funding/GrantDetailsInline.tsx +++ b/components/Funding/GrantDetailsInline.tsx @@ -13,7 +13,13 @@ export function GrantDetailsInline({ content, imageUrl }: GrantDetailsInlineProp
{imageUrl && (
- +
)}