Skip to content

Commit 957bd10

Browse files
committed
feat: 밈 썸네일 이미지 캐싱
- 캐싱을 적용하여 Meme 데이터를 가져오는 기능을 추가함 - 메타 태그 업데이트 로직을 별도의 함수로 분리하여 코드 가독성을 향상시킴 - 응답에 캐싱 헤더를 추가하여 성능 개선을 도모함
1 parent bfb50a8 commit 957bd10

File tree

1 file changed

+79
-61
lines changed

1 file changed

+79
-61
lines changed

apps/web/middleware.ts

Lines changed: 79 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -2,84 +2,102 @@ export const config = {
22
matcher: ['/meme/:memeId'],
33
};
44

5-
export default async function middleware(request: Request) {
6-
const url = new URL(request.url);
7-
const memeId = url.pathname.split('/').pop();
5+
const CACHE_REVALIDATE_TIME = 3600; // 1시간
6+
const API_ENDPOINT = 'https://api.meme-wiki.net/api/memes';
7+
8+
// 메타 데이터 타입 정의
9+
interface MemeMetadata {
10+
success: {
11+
title: string;
12+
usageContext: string;
13+
imgUrl: string;
14+
};
15+
}
816

9-
// /meme/{id} 경로에 대한 처리
17+
async function fetchMemeData(memeId: string): Promise<MemeMetadata | null> {
1018
try {
11-
const response = await fetch(
12-
`https://api.meme-wiki.net/api/memes/${memeId}`,
13-
{
14-
headers: {
15-
Accept: 'application/json',
16-
},
19+
const response = await fetch(`${API_ENDPOINT}/${memeId}`, {
20+
headers: {
21+
Accept: 'application/json',
22+
'Cache-Control': `public, max-age=${CACHE_REVALIDATE_TIME}`,
1723
},
18-
);
24+
});
1925

2026
if (!response.ok) {
21-
const res = await fetch(new URL('/', request.url));
22-
const html = await res.text();
23-
return new Response(html, {
24-
status: 200,
25-
headers: {
26-
'content-type': 'text/html;charset=UTF-8',
27-
},
28-
});
27+
return null;
2928
}
3029

31-
const data = await response.json();
30+
return await response.json();
31+
} catch (error) {
32+
console.error('Error fetching meme data:', error);
33+
return null;
34+
}
35+
}
36+
37+
function updateMetaTags(
38+
html: string,
39+
data: MemeMetadata['success'],
40+
url: URL,
41+
memeId: string,
42+
): string {
43+
const metaTags = {
44+
'og:title': `${data.title} - Meme Wiki`,
45+
'og:description': data.usageContext,
46+
'og:image': data.imgUrl,
47+
'og:url': `${url.origin}/meme/${memeId}`,
48+
'twitter:title': `${data.title} - Meme Wiki`,
49+
'twitter:description': data.usageContext,
50+
'twitter:image': data.imgUrl,
51+
};
52+
53+
let modifiedHtml = html;
54+
55+
// 한 번의 순회로 모든 메타 태그 업데이트
56+
Object.entries(metaTags).forEach(([property, content]) => {
57+
modifiedHtml = modifiedHtml.replace(
58+
new RegExp(
59+
`<meta\\s+property="${property}"\\s+content="[^"]*"[^>]*>`,
60+
'i',
61+
),
62+
`<meta property="${property}" content="${content}" />`,
63+
);
64+
});
65+
66+
return modifiedHtml;
67+
}
3268

69+
export default async function middleware(request: Request) {
70+
const url = new URL(request.url);
71+
const memeId = url.pathname.split('/').pop();
72+
73+
if (!memeId) {
74+
return Response.redirect(new URL('/', request.url));
75+
}
76+
77+
try {
78+
// 메메 데이터 가져오기 (캐싱 적용)
79+
const memeData = await fetchMemeData(memeId);
80+
81+
if (!memeData) {
82+
return Response.redirect(new URL('/', request.url));
83+
}
84+
85+
// 기본 HTML 가져오기
3386
const res = await fetch(new URL('/', request.url));
3487
const html = await res.text();
3588

36-
const modifiedHtml = html
37-
.replace(
38-
/<meta\s+property="og:title"\s+content="[^"]*"[^>]*>/,
39-
`<meta property="og:title" content="${data.success.title} - Meme Wiki" />`,
40-
)
41-
.replace(
42-
/<meta\s+property="og:description"\s+content="[^"]*"[^>]*>/,
43-
`<meta property="og:description" content="${data.success.usageContext}" />`,
44-
)
45-
.replace(
46-
/<meta\s+property="og:image"\s+content="[^"]*"[^>]*>/,
47-
`<meta property="og:image" content="${data.success.imgUrl}" />`,
48-
)
49-
.replace(
50-
/<meta\s+property="og:url"\s+content="[^"]*"[^>]*>/,
51-
`<meta property="og:url" content="${url.origin}/meme/${memeId}" />`,
52-
)
53-
.replace(
54-
/<meta\s+property="twitter:title"\s+content="[^"]*"[^>]*>/,
55-
`<meta property="twitter:title" content="${data.success.title} - Meme Wiki" />`,
56-
)
57-
.replace(
58-
/<meta\s+property="twitter:description"\s+content="[^"]*"[^>]*>/,
59-
`<meta property="twitter:description" content="${data.success.usageContext}" />`,
60-
)
61-
.replace(
62-
/<meta\s+property="twitter:image"\s+content="[^"]*"[^>]*>/,
63-
`<meta property="twitter:image" content="${data.success.imgUrl}" />`,
64-
);
89+
// 메타 태그 업데이트
90+
const modifiedHtml = updateMetaTags(html, memeData.success, url, memeId);
6591

92+
// 응답 반환 (캐싱 헤더 포함)
6693
return new Response(modifiedHtml, {
6794
headers: {
6895
'content-type': 'text/html;charset=UTF-8',
69-
'x-middleware-cache': 'no-cache',
70-
'cache-control': 'no-cache, no-store, must-revalidate',
71-
pragma: 'no-cache',
72-
expires: '0',
96+
'cache-control': `public, s-maxage=${CACHE_REVALIDATE_TIME}, stale-while-revalidate`,
7397
},
7498
});
7599
} catch (error) {
76-
const res = await fetch(new URL('/', request.url));
77-
const html = await res.text();
78-
return new Response(html, {
79-
status: 200,
80-
headers: {
81-
'content-type': 'text/html;charset=UTF-8',
82-
},
83-
});
100+
console.error('Middleware error:', error);
101+
return Response.redirect(new URL('/', request.url));
84102
}
85103
}

0 commit comments

Comments
 (0)