Optimize safe url generation for og images

This commit is contained in:
Sam Becker 2026-02-18 09:23:23 -06:00
parent af672dfcf4
commit 1994f3bfba
3 changed files with 34 additions and 28 deletions

View File

@ -3,7 +3,7 @@
import { Photo } from '@/photo';
import { NextImageSize } from '@/platforms/next-image';
import { IS_PREVIEW } from '@/app/config';
import { getOptimizedUrlsForPhotos } from '@/photo/storage';
import { getDataUrlsForPhotos } from '@/photo/storage';
export default async function ImagePhotoGrid({
photos,
@ -52,7 +52,7 @@ export default async function ImagePhotoGrid({
const cellHeight= height / rows -
(rows - 1) * gap / rows;
const photoUrls = await getOptimizedUrlsForPhotos(
const photoUrls = await getDataUrlsForPhotos(
photos,
optimizedSuffix,
nextImageWidth,
@ -60,7 +60,7 @@ export default async function ImagePhotoGrid({
);
const renderPhoto = (
{ id, url }: typeof photoUrls[number],
{ id, urlData }: typeof photoUrls[number],
width: number,
height: number,
) =>
@ -75,7 +75,7 @@ export default async function ImagePhotoGrid({
}}
>
<img {...{
src: url,
src: urlData,
style: {
...imageStyle,
width: '100%',

View File

@ -10,6 +10,7 @@ import {
uploadFileFromClient,
} from '@/platforms/storage';
import { Photo } from '..';
import { fetchBase64ImageFromUrl } from '@/utility/image';
const PREFIX_PHOTO = 'photo';
const PREFIX_UPLOAD = 'upload';
@ -157,39 +158,35 @@ export const getStorageUrlsForPhoto = async ({ url }: Photo) => {
);
};
export const getOptimizedUrlsForPhotos = async (
export const getDataUrlsForPhotos = async (
photos: Photo[],
optimizedSuffix: OptimizedSuffix,
nextImageWidth: NextImageSize,
addBypassSecret: boolean,
) =>
Promise.all(photos
.map(async({ id, url: _url }) => {
.map(async({ id, url }) => {
// Check for optimized image first
const optimizedUrl =await getSignedUrlForUrl(
getOptimizedPhotoUrlForSuffix(_url, optimizedSuffix),
getOptimizedPhotoUrlForSuffix(url, optimizedSuffix),
'GET',
);
const optimizedUrlExists = await fetch(optimizedUrl)
.then(res => res.ok)
.catch(() => false);
const optimizedUrlData = await fetchBase64ImageFromUrl(optimizedUrl);
if (optimizedUrlExists) {
return { id, url: optimizedUrl };
if (optimizedUrlData) {
return { id, urlData: optimizedUrlData };
} else {
// Fall back on `next/image` if optimized image is not available
const nextImageUrl = getOptimizedPhotoUrl({
imageUrl: _url,
imageUrl: url,
size: nextImageWidth,
addBypassSecret,
});
const nextImageUrlExists = await fetch(nextImageUrl)
.then(res => res.ok)
.catch(() => false);
return { id, url: nextImageUrlExists ? nextImageUrl : undefined };
const nextImageUrlData = await fetchBase64ImageFromUrl(nextImageUrl);
return { id, urlData: nextImageUrlData };
}
}))
.then(urls =>(urls.every(url => Boolean(url))
.then(urls =>(urls.every(({ urlData }) => Boolean(urlData))
? urls
// If any url is defined, return an empty array
: []) as { id: string, url: string }[]);
// If any url is undefined, return an empty array
: []) as { id: string, urlData: string }[]);

View File

@ -1,15 +1,24 @@
import { getFileNamePartsFromStorageUrl } from '@/platforms/storage';
export const removeBase64Prefix = (base64: string) => {
return base64.match(/^data:image\/[a-z]{3,4};base64,(.+)$/)?.[1] ?? base64;
};
export const fetchBase64ImageFromUrl = (
export const fetchBase64ImageFromUrl = async (
url: string,
contentType?: string,
fetchOptions?: RequestInit,
) =>
fetch(url, fetchOptions)
) => {
const { fileExtension } = getFileNamePartsFromStorageUrl(url);
const contentType = fileExtension === 'png' ? 'image/png' : 'image/jpeg';
return fetch(url, fetchOptions)
.then(async response => {
if (response.ok) {
const blob = await response.arrayBuffer();
// eslint-disable-next-line max-len
return `data:${contentType ?? response.headers.get('content-type')};base64,${Buffer.from(blob).toString('base64')}`;
});
return `data:${contentType};base64,${Buffer.from(blob).toString('base64')}`;
} else {
return undefined;
}
})
.catch(() => undefined);
};