
with svg 생성 api와 redirect api
작년 여름에 마지막으로 깃허브 프로필을 수정하고 그 뒤로 건들지 않았었는데 이번에 좀 더 심플하게 바꿔보려고 했다.
기존에는 velog를 쓰고 있었기 때문에 velog-stats 레포지토리를 사용해서 velog 최신 글을 깃허브 리드미에 뱃지 형태로 띄워주는 기능을 쓰고 있었다. 이 기능이랑 비슷하게 내 블로그의 최신 글로 연결되는 뱃지를 만들어보고 싶다는 생각을 했다.
리드미용 블로그 최신 글 뱃지 만들기
일단 어떻게 하는지 감이 안잡혀서 기존 벨로그 최신글 연결 레포지토리도 살펴보고 리드미에 어떻게 작성하는지를 살펴봤다.
아래는 이전에 사용하던 벨로그 최신 글로 리다이렉트되는 뱃지이다. 이 뱃지는 velog-reademe-stats Repository에서 사용해볼 수 있다.
해당 레포지토리를 살펴보면 vercel에 호스팅을 하고 있고, 뱃지를 생성하는 API와 리다이렉트 해주는 API를 두 개 쓰고 있는 것을 알았다.
그렇다면 해야할 것은 1. SVG 생성 API 2. 최신 글 리다이렉트 주소 API를 만드는 것이다.
완성본 미리보기
내가 만든 뱃지는 아래와 같다.
SVG 생성하기
짧은 시간에 svg 구조를 만드는 방법을 배우는 건 너무 비효율적이니 Claude를 사용해서 SVG의 구조를 만들었다. svg를 반환하는 api를 만들어야하니 /api/posts/recent의 get method로 만들었다.
아래는 뱃지를 생성하는 함수이다.
// 메인 배지 SVG 생성 함수
function generateBadgeSVG(
title: string,
subTitle: string,
date: string
): string {
// 배지 크기 계산
const width = 400;
const height = subTitle ? 110 : 80; // 부제목 유무에 따라 높이 조정
// 투명 배경으로 SVG 시작
let svg = `<svg xmlns="<http://www.w3.org/2000/svg>" width="${width}" height="${height}" fill="none">
<!-- 투명 배경 설정 -->
<defs>
<clipPath id="roundedRect">
<rect width="${width}" height="${height}" rx="10" ry="10"/>
</clipPath>
</defs>
<!-- 배경 및 테두리 -->
<g clip-path="url(#roundedRect)">
<rect width="${width}" height="${height}" fill="white"/>
</g>
<rect width="${width}" height="${height}" fill="none" stroke="${DEEP_GREEN}" stroke-width="1" rx="10" ry="10"/>
<!-- 배지 제목 영역 - 딥그린 색상 적용 -->
<text x="20" y="30" font-family="Arial, Helvetica, sans-serif" font-size="12" font-weight="bold" fill="${DEEP_GREEN}">최신 글 | ShipFriend Tech Blog</text>
<!-- 구분선 - 딥그린 색상 적용 -->
<line x1="20" y1="40" x2="${width - 20}" y2="40" stroke="${DEEP_GREEN}" stroke-width="1"/>
<!-- 포스트 제목 -->
<text x="20" y="65" font-family="Arial, Helvetica, sans-serif" font-size="16" font-weight="bold" fill="#333">${escapeXML(title)}</text>`;
if (subTitle) {
// 부제목이 있는 경우
svg += `
<!-- 부제목 -->
<text x="20" y="90" font-family="Arial, Helvetica, sans-serif" font-size="14" fill="#666">${escapeXML(subTitle)}</text>
<!-- 날짜 (맨 아래) -->
<text x="${width - 20}" y="${height - 20}" font-family="Arial, Helvetica, sans-serif" font-size="12" fill="#888" text-anchor="end">${date}</text>`;
} else {
// 부제목이 없는 경우
svg += `
<!-- 날짜 (중간 아래) -->
<text x="${width - 20}" y="${height - 20}" font-family="Arial, Helvetica, sans-serif" font-size="12" fill="#888" text-anchor="end">${date}</text>`;
}
svg += `</svg>`;
return svg;
}
이렇게 생성하고 나서는 아래와 같은 식으로 최신 글을 DB에서 가져오고, SVG를 생성할 때 넣어주고 나서 response로는 Content-Type을 image/svg+xml로 해두고 svg를 반환하도록 만들었다.
await dbConnect();
const latestPost = await Post.findOne({})
.select('title subTitle slug date')
.sort({ date: -1 })
.limit(limit);
const svg = generateBadgeSVG(title, subTitle, dateStr);
return new Response(svg, {
headers: {
'Content-Type': 'image/svg+xml',
'Cache-Control': 'no-cache, no-store, must-revalidate, max-age=0',
},
});
리다이렉트 API
위 처럼 만든 svg는 정말 이미지와 같기 때문에 이미지를 클릭했을 때 이동할 수 있는 기능을 만드려면 항상 최신글로 리다이렉트해주는 api를 만드는 것이 중요하다.
혹시 나중에 블로그 주소를 이전할 수도 있으니 리다이렉트하는 url은 환경변수를 사용하도록 만들었다. 최신 글을 가져와 해당 글에 slug를 사용해서 리다이렉트 url을 만든다.
NextResponse 객체의 redirect 메서드를 사용해서 최신 글로 리다이렉트 시킨다. 이 api는 /api/redirect/recent로 만들었고 해당 api에 접근하며 항상 최신 글 주소로 리다이렉트해준다.
await dbConnect();
const pageUrl = process.env.NEXTAUTH_URL;
// 최신 글 1개 가져오기
const latestPost = await Post.findOne({}).sort({ date: -1 }).select('slug');
// 최신 글로 리다이렉션
return NextResponse.redirect(
new URL(`${pageUrl}/posts/${latestPost.slug}`)
);
캬
금방 만들었지만 만들길 잘 한 것 같다.
