From d11cf6f62df55836c40e3177b0553ab3340f1d8d Mon Sep 17 00:00:00 2001 From: Sam Becker Date: Tue, 17 Feb 2026 18:11:54 -0600 Subject: [PATCH] Use presigned urls when statically pre-rendering --- app/api/storage/presigned-url/[key]/route.ts | 4 +-- .../components/ImagePhotoGrid.tsx | 33 +++++++++++++------ src/platforms/storage/aws-s3.ts | 2 +- src/platforms/storage/cloudflare-r2.ts | 2 +- src/platforms/storage/index.ts | 22 ++++++++++++- src/platforms/storage/minio.ts | 2 +- 6 files changed, 49 insertions(+), 16 deletions(-) diff --git a/app/api/storage/presigned-url/[key]/route.ts b/app/api/storage/presigned-url/[key]/route.ts index ea5016be..314c8764 100644 --- a/app/api/storage/presigned-url/[key]/route.ts +++ b/app/api/storage/presigned-url/[key]/route.ts @@ -1,5 +1,5 @@ import { auth } from '@/auth/server'; -import { getSignedUrl } from '@/platforms/storage'; +import { getSignedUrlForKey } from '@/platforms/storage'; export async function GET( _: Request, @@ -10,7 +10,7 @@ export async function GET( const session = await auth(); if (session?.user && key) { - const url = await getSignedUrl(key, 'PUT'); + const url = await getSignedUrlForKey(key, 'PUT'); return new Response( url, { headers: { 'content-type': 'text/plain' } }, diff --git a/src/image-response/components/ImagePhotoGrid.tsx b/src/image-response/components/ImagePhotoGrid.tsx index f1b893a7..3756cda5 100644 --- a/src/image-response/components/ImagePhotoGrid.tsx +++ b/src/image-response/components/ImagePhotoGrid.tsx @@ -7,6 +7,8 @@ import { doAllPhotosHaveOptimizedFiles, getOptimizedPhotoUrl, } from '@/photo/storage'; +import { fetchBase64ImageFromUrl } from '@/utility/image'; +import { getSignedUrlForUrl } from '@/platforms/storage'; export default async function ImagePhotoGrid({ photos, @@ -53,7 +55,23 @@ export default async function ImagePhotoGrid({ const doOptimizedFilesExist = await doAllPhotosHaveOptimizedFiles(photos); - const renderPhoto = ({ id, url }: Photo, width: number, height: number) => + const photoDataUrls = await Promise.all(photos.map(async({ id, url }) => { + const optimizedUrl = getOptimizedPhotoUrl({ + imageUrl: url, + size: nextImageWidth, + addBypassSecret: IS_PREVIEW, + compatibilityMode: !doOptimizedFilesExist, + }); + const presignedUrl = await getSignedUrlForUrl(optimizedUrl, 'GET'); + const data = await fetchBase64ImageFromUrl(presignedUrl); + return { id, data }; + })); + + const renderPhoto = ( + { id, data }: typeof photoDataUrls[number], + width: number, + height: number, + ) =>
- {renderPhoto(photos[0], cellWidth, cellHeight * 2)} + {renderPhoto(photoDataUrls[0], cellWidth, cellHeight * 2)}
{/* Small images (R) */}
- {photos.slice(1).map(photo => + {photoDataUrls.slice(1).map(photo => renderPhoto(photo, cellWidth, cellHeight), )}
- : photos.slice(0, count).map(photo => + : photoDataUrls.slice(0, count).map(photo => renderPhoto(photo, cellWidth, cellHeight), )} diff --git a/src/platforms/storage/aws-s3.ts b/src/platforms/storage/aws-s3.ts index 411a82a5..b64e8d59 100644 --- a/src/platforms/storage/aws-s3.ts +++ b/src/platforms/storage/aws-s3.ts @@ -84,7 +84,7 @@ export const awsS3Delete = async (Key: string) => { })); }; -export const awsS3GetSignedUrl = async ( +export const awsS3GetSignedUrl = ( Key: string, method: 'GET' | 'PUT', expiresIn: number, diff --git a/src/platforms/storage/cloudflare-r2.ts b/src/platforms/storage/cloudflare-r2.ts index ebb38650..50aa7458 100644 --- a/src/platforms/storage/cloudflare-r2.ts +++ b/src/platforms/storage/cloudflare-r2.ts @@ -104,7 +104,7 @@ export const cloudflareR2Delete = async (Key: string) => { })); }; -export const cloudflareR2GetSignedUrl = async ( +export const cloudflareR2GetSignedUrl = ( Key: string, method: 'GET' | 'PUT', expiresIn: number, diff --git a/src/platforms/storage/index.ts b/src/platforms/storage/index.ts index 3a05bda8..6a773a51 100644 --- a/src/platforms/storage/index.ts +++ b/src/platforms/storage/index.ts @@ -251,7 +251,8 @@ export const getStorageUrlsForPrefix = async (prefix = '') => { }); }; -export const getSignedUrl = async ( +// Used primarily for uploading files +export const getSignedUrlForKey = async ( key: string, method: 'GET' | 'PUT', expiresIn = 3600, @@ -266,5 +267,24 @@ export const getSignedUrl = async ( } }; +// Used for safely fetching files via presigned URLs +export const getSignedUrlForUrl = ( + url: string, + method: 'GET' | 'PUT', + expiresIn = 3600, +) => { + const { fileName } = getFileNamePartsFromStorageUrl(url); + switch (storageTypeFromUrl(url)) { + case 'cloudflare-r2': + return cloudflareR2GetSignedUrl(fileName, method, expiresIn); + case 'minio': + return minioGetSignedUrl(fileName, method, expiresIn); + case 'aws-s3': + return awsS3GetSignedUrl(fileName, method, expiresIn); + default: + return url; + } +}; + export const testStorageConnection = () => getStorageUrlsForPrefix(); diff --git a/src/platforms/storage/minio.ts b/src/platforms/storage/minio.ts index 552386ef..e2f52e73 100644 --- a/src/platforms/storage/minio.ts +++ b/src/platforms/storage/minio.ts @@ -93,7 +93,7 @@ export const minioDelete = async (Key: string): Promise => { await minioClient().send(deleteObjectCommand); }; -export const minioGetSignedUrl = async ( +export const minioGetSignedUrl = ( Key: string, method: 'GET' | 'PUT', expiresIn: number,