diff --git a/app/home-image/route.tsx b/app/home-image/route.tsx index a8a2231a..68df76be 100644 --- a/app/home-image/route.tsx +++ b/app/home-image/route.tsx @@ -5,10 +5,9 @@ import { } from '@/image-response'; import HomeImageResponse from '@/image-response/HomeImageResponse'; import { getIBMPlexMono } from '@/app/font'; -import { ImageResponse } from 'next/og'; import { getImageResponseCacheControlHeaders } from '@/image-response/cache'; -import { isNextImageReadyBasedOnPhotos } from '@/photo'; import { APP_OG_IMAGE_QUERY_OPTIONS } from '@/feed'; +import { safePhotoImageResponse } from '@/platforms/safe-photo-image-response'; export const dynamic = 'force-static'; @@ -29,17 +28,15 @@ export async function GET() { const { width, height } = IMAGE_OG_DIMENSION_SMALL; - // Make sure next/image can be reached from absolute urls, - // which may not exist on first pre-render - const isNextImageReady = await isNextImageReadyBasedOnPhotos(photos); - - return new ImageResponse( - , - { width, height, headers, fonts }, + return safePhotoImageResponse( + photos, + isNextImageReady => ( + + ), { width, height, headers, fonts }, ); } diff --git a/app/p/[photoId]/image/route.tsx b/app/p/[photoId]/image/route.tsx index b70c6a89..6f8b6024 100644 --- a/app/p/[photoId]/image/route.tsx +++ b/app/p/[photoId]/image/route.tsx @@ -2,10 +2,9 @@ import { getPhotoCached } from '@/photo/cache'; import { IMAGE_OG_DIMENSION } from '@/image-response'; import PhotoImageResponse from '@/image-response/PhotoImageResponse'; import { getIBMPlexMono } from '@/app/font'; -import { ImageResponse } from 'next/og'; import { getImageResponseCacheControlHeaders } from '@/image-response/cache'; -import { isNextImageReadyBasedOnPhotos } from '@/photo'; import { staticallyGeneratePhotosIfConfigured } from '@/app/static'; +import { safePhotoImageResponse } from '@/platforms/safe-photo-image-response'; export const generateStaticParams = staticallyGeneratePhotosIfConfigured( 'image', @@ -30,19 +29,18 @@ export async function GET( if (!photo) { return new Response('Photo not found', { status: 404 }); } const { width, height } = IMAGE_OG_DIMENSION; - - // Make sure next/image can be reached from absolute urls, - // which may not exist on first pre-render - const isNextImageReady = await isNextImageReadyBasedOnPhotos([photo]); - return new ImageResponse( - , + return safePhotoImageResponse( + [photo], + isNextImageReady => ( + + ), { width, height, fonts, headers }, ); } diff --git a/app/recents/image/route.tsx b/app/recents/image/route.tsx index ef607dca..d7a40faa 100644 --- a/app/recents/image/route.tsx +++ b/app/recents/image/route.tsx @@ -6,11 +6,10 @@ import { import RecentsImageResponse from '@/image-response/RecentsImageResponse'; import { getIBMPlexMono } from '@/app/font'; -import { ImageResponse } from 'next/og'; import { getImageResponseCacheControlHeaders } from '@/image-response/cache'; import { getAppText } from '@/i18n/state/server'; import { SHOW_RECENTS } from '@/app/config'; -import { isNextImageReadyBasedOnPhotos } from '@/photo'; +import { safePhotoImageResponse } from '@/platforms/safe-photo-image-response'; export const dynamic = 'force-static'; @@ -36,18 +35,17 @@ export async function GET() { const { width, height } = IMAGE_OG_DIMENSION_SMALL; - // Make sure next/image can be reached from absolute urls, - // which may not exist on first pre-render - const isNextImageReady = await isNextImageReadyBasedOnPhotos(photos); - - return new ImageResponse( - , + return safePhotoImageResponse( + photos, + isNextImageReady => ( + + ), { width, height, fonts, headers }, ); } diff --git a/app/template-image-tight/route.tsx b/app/template-image-tight/route.tsx index d1ee4201..61b3fcd3 100644 --- a/app/template-image-tight/route.tsx +++ b/app/template-image-tight/route.tsx @@ -6,9 +6,8 @@ import { import TemplateImageResponse from '@/image-response/TemplateImageResponse'; import { getIBMPlexMono } from '@/app/font'; -import { ImageResponse } from 'next/og'; import { getImageResponseCacheControlHeaders } from '@/image-response/cache'; -import { isNextImageReadyBasedOnPhotos } from '@/photo'; +import { safePhotoImageResponse } from '@/platforms/safe-photo-image-response'; export async function GET() { const [ @@ -26,12 +25,9 @@ export async function GET() { const { width, height } = IMAGE_OG_DIMENSION; - // Make sure next/image can be reached from absolute urls, - // which may not exist on first pre-render - const isNextImageReady = await isNextImageReadyBasedOnPhotos(photos); - - return new ImageResponse( - ( + return safePhotoImageResponse( + photos, + isNextImageReady => ( ( .filter(Boolean) .map(keyword => keyword.toLocaleLowerCase()); -export const isNextImageReadyBasedOnPhotos = async ( - photos: Photo[], -): Promise => - photos.length > 0 && fetch(getNextImageUrlForRequest({ - imageUrl: photos[0].url, - size: 640, - addBypassSecret: IS_PREVIEW, - })) - .then(response => response.ok) - .catch(() => false); - export const downloadFileNameForPhoto = (photo: Photo) => photo.title ? `${parameterize(photo.title)}.${photo.extension}` diff --git a/src/platforms/safe-photo-image-response.ts b/src/platforms/safe-photo-image-response.ts new file mode 100644 index 00000000..3744f628 --- /dev/null +++ b/src/platforms/safe-photo-image-response.ts @@ -0,0 +1,32 @@ +import { Photo } from '@/photo'; +import { ImageResponse } from 'next/og'; +import { JSX } from 'react'; +import { getNextImageUrlForRequest } from './next-image'; +import { IS_PREVIEW } from '@/app/config'; + +const isNextImageReadyBasedOnPhotos = async ( + photos: Photo[], +): Promise => + photos.length > 0 && + fetch(getNextImageUrlForRequest({ + imageUrl: photos[0].url, + size: 640, + addBypassSecret: IS_PREVIEW, + })) + .then(response => response.ok) + .catch(() => false); + +export const safePhotoImageResponse = async ( + photos: Photo[], + jsx: (isNextImageReady: boolean) => JSX.Element, + options: ConstructorParameters[1], +) => { + // Make sure next/image can be reached from absolute urls, + // which may not exist on first pre-render + const isNextImageReady = await isNextImageReadyBasedOnPhotos(photos); + + return new ImageResponse( + jsx(isNextImageReady), + options, + ); +};