Skip to content

Commit

Permalink
feat: 공유 페이지에서 동적인 og 이미지 생성
Browse files Browse the repository at this point in the history
  • Loading branch information
dmdgpdi committed Dec 29, 2024
1 parent e14980d commit 2835e91
Show file tree
Hide file tree
Showing 2 changed files with 110 additions and 1 deletion.
41 changes: 40 additions & 1 deletion frontend/techpick/src/app/(unsigned)/share/[uuid]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import {
loginButtonStyle,
} from './page.css';
import { SignUpLinkButton } from './SignUpLinkButton';

import type { Metadata, ResolvingMetadata } from 'next';
const EmptyPickRecordImage = dynamic(
() =>
import('@/components/EmptyPickRecordImage').then(
Expand All @@ -34,6 +34,45 @@ const EmptyPickRecordImage = dynamic(
}
);

export async function generateMetadata(
{
params,
}: {
params: { uuid: string };
},
parent: ResolvingMetadata
): Promise<Metadata> {
const { uuid } = params;
const sharedFolder = await getShareFolderById(uuid);
const { pickList } = sharedFolder;

const imageUrls = pickList
.map((pick) => pick.linkInfo.imageUrl)
.filter((url) => url && url !== '')
.slice(0, 16); // 최대 16개까지 허용

let ogImageUrl: string;

if (imageUrls.length === 0) {
ogImageUrl = `${process.env.NEXT_PUBLIC_IMAGE_URL}/image/og_image.png`;
} else {
const apiUrl = new URL(
`${process.env.NEXT_PUBLIC_IMAGE_URL}/api/generate-og-image`
);
apiUrl.searchParams.set('imageUrls', JSON.stringify(imageUrls));
ogImageUrl = apiUrl.toString();
}

const previousImages = (await parent).openGraph?.images || [];
return {
title: `${sharedFolder.folderName} 폴더 공유 페이지`,
description: `${pickList.length}개의 북마크가 공유되었습니다.`,
openGraph: {
images: [ogImageUrl, ...previousImages],
},
};
}

export default async function Page({ params }: { params: { uuid: string } }) {
const { uuid } = params;
const sharedFolder = await getShareFolderById(uuid);
Expand Down
70 changes: 70 additions & 0 deletions frontend/techpick/src/app/api/generate-og-image/route.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/* eslint-disable jsx-a11y/alt-text */
import { NextRequest } from 'next/server';
import { ImageResponse } from '@vercel/og';

export const runtime = 'edge';

const styles = {
1: { width: '1200px', height: '630px' },
2: { width: '600px', height: '630px' },
4: { width: '600px', height: '315px' },
8: { width: '300px', height: '315px' },
16: { width: '300px', height: '157.5px' },
};

const getImageStyle = (index: number) => {
return styles[index as keyof typeof styles] || styles[16];
};

export async function GET(req: NextRequest) {
const { searchParams } = new URL(req.url);
const imageUrls: string[] = JSON.parse(searchParams.get('imageUrls') || '[]');

const width = 1200;
const height = 630;

// 이미지 개수를 1, 2, 4, 8, 16 중 가장 가까운 수로 조정
const adjustedCount = [1, 2, 4, 8, 16].reduce((prev, curr) =>
Math.abs(curr - imageUrls.length) < Math.abs(prev - imageUrls.length)
? curr
: prev
);

const images = await Promise.all(
imageUrls.slice(0, adjustedCount).map(async (url: string) => {
try {
const res = await fetch(url);
if (!res.ok) throw new Error('Failed to fetch image');
return url;
} catch {
return '/image/og_image.png';
}
})
);
const imageCount = images.length;

return new ImageResponse(
(
<div
style={{
display: 'flex',
flexWrap: 'wrap',
width: '1200px',
height: '630px',
}}
>
{images.map((url: string, index: number) => (
<img
key={index}
src={url}
style={{
...getImageStyle(imageCount),
objectFit: 'cover',
}}
/>
))}
</div>
),
{ width, height }
);
}

0 comments on commit 2835e91

Please sign in to comment.