@@ -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- / < m e t a \s + p r o p e r t y = " o g : t i t l e " \s + c o n t e n t = " [ ^ " ] * " [ ^ > ] * > / ,
39- `<meta property="og:title" content="${ data . success . title } - Meme Wiki" />` ,
40- )
41- . replace (
42- / < m e t a \s + p r o p e r t y = " o g : d e s c r i p t i o n " \s + c o n t e n t = " [ ^ " ] * " [ ^ > ] * > / ,
43- `<meta property="og:description" content="${ data . success . usageContext } " />` ,
44- )
45- . replace (
46- / < m e t a \s + p r o p e r t y = " o g : i m a g e " \s + c o n t e n t = " [ ^ " ] * " [ ^ > ] * > / ,
47- `<meta property="og:image" content="${ data . success . imgUrl } " />` ,
48- )
49- . replace (
50- / < m e t a \s + p r o p e r t y = " o g : u r l " \s + c o n t e n t = " [ ^ " ] * " [ ^ > ] * > / ,
51- `<meta property="og:url" content="${ url . origin } /meme/${ memeId } " />` ,
52- )
53- . replace (
54- / < m e t a \s + p r o p e r t y = " t w i t t e r : t i t l e " \s + c o n t e n t = " [ ^ " ] * " [ ^ > ] * > / ,
55- `<meta property="twitter:title" content="${ data . success . title } - Meme Wiki" />` ,
56- )
57- . replace (
58- / < m e t a \s + p r o p e r t y = " t w i t t e r : d e s c r i p t i o n " \s + c o n t e n t = " [ ^ " ] * " [ ^ > ] * > / ,
59- `<meta property="twitter:description" content="${ data . success . usageContext } " />` ,
60- )
61- . replace (
62- / < m e t a \s + p r o p e r t y = " t w i t t e r : i m a g e " \s + c o n t e n t = " [ ^ " ] * " [ ^ > ] * > / ,
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