11"use client" ;
22
3- import React , { useState , useEffect } from "react" ;
3+ import React , { useState , memo } from "react" ;
44import { Prism as SyntaxHighlighter } from "react-syntax-highlighter" ;
55import {
66 oneDark ,
@@ -17,23 +17,65 @@ interface CodeBlockProps {
1717 filename ?: string ;
1818}
1919
20- export default function CodeBlock ( {
20+ // SyntaxHighlighter를 별도 컴포넌트로 분리
21+ const CodeHighlighter = memo ( ( {
22+ language,
23+ code,
24+ isDark
25+ } : {
26+ language : string ;
27+ code : string ;
28+ isDark : boolean ;
29+ } ) => {
30+ return (
31+ < SyntaxHighlighter
32+ language = { language }
33+ style = { isDark ? oneDark : oneLight }
34+ customStyle = { {
35+ margin : 0 ,
36+ padding : "1rem" ,
37+ borderRadius : 0 ,
38+ fontSize : "13px" ,
39+ whiteSpace : "pre-wrap" ,
40+ wordBreak : "break-all" ,
41+ overflowWrap : "anywhere" ,
42+ maxWidth : "100%" ,
43+ overflowX : "visible" ,
44+ background : isDark
45+ ? "rgba(30, 30, 30, 0.95)"
46+ : "rgba(250, 250, 250, 0.95)" ,
47+ transition : "background 0.3s ease" ,
48+ } }
49+ wrapLines = { true }
50+ wrapLongLines = { true }
51+ lineProps = { {
52+ style : { wordBreak : "break-all" , whiteSpace : "pre-wrap" } ,
53+ } }
54+ className = "whitespace-pre-wrap break-all"
55+ PreTag = { ( { children, ...props } ) => (
56+ < pre { ...props } className = "transition-colors duration-300" >
57+ { children }
58+ </ pre >
59+ ) }
60+ >
61+ { code }
62+ </ SyntaxHighlighter >
63+ ) ;
64+ } ) ;
65+
66+ CodeHighlighter . displayName = "CodeHighlighter" ;
67+
68+ function CodeBlock ( {
2169 language,
2270 code,
2371 filename,
2472} : CodeBlockProps ) {
2573 const [ isCopied , setIsCopied ] = useState ( false ) ;
2674 const [ isCollapsed , setIsCollapsed ] = useState ( false ) ;
27- const [ mounted , setMounted ] = useState ( false ) ;
2875 const { resolvedTheme } = useTheme ( ) ;
29-
30- // 서버/클라이언트 하이드레이션 불일치 방지
31- useEffect ( ( ) => {
32- setMounted ( true ) ;
33- } , [ ] ) ;
34-
35- // 실제 사용할 테마 (마운트 전에는 기본값 사용)
36- const isDark = mounted ? resolvedTheme === "dark" : false ;
76+
77+ // 테마를 직접 체크 (서버와 클라이언트 모두에서 작동)
78+ const isDark = resolvedTheme === "dark" ;
3779
3880 const handleCopy = async ( ) => {
3981 await navigator . clipboard . writeText ( code ) ;
@@ -70,12 +112,7 @@ export default function CodeBlock({
70112 } ;
71113
72114 return (
73- < motion . div
74- className = "relative spacing-section rounded-lg overflow-hidden border border-primary/20 shadow-md"
75- initial = { { opacity : 0 , y : 10 } }
76- animate = { { opacity : 1 , y : 0 } }
77- transition = { { duration : 0.4 } }
78- >
115+ < div className = "relative spacing-section rounded-lg overflow-hidden border border-primary/20 shadow-md" >
79116 < div
80117 className = "flex items-center justify-between bg-gradient-to-r from-primary/20 to-primary/5 text-primary
81118 px-3 sm:px-4 py-2 sm:py-2.5 text-xs sm:text-sm font-mono border-b border-primary/15"
@@ -149,59 +186,18 @@ export default function CodeBlock({
149186 < div className = "absolute top-0 right-0 bottom-0 w-4 bg-gradient-to-l from-black/5 to-transparent pointer-events-none" > </ div >
150187 < div className = "absolute top-0 left-0 bottom-0 w-4 bg-gradient-to-r from-black/5 to-transparent pointer-events-none" > </ div >
151188
152- { /* 마운트 전까지는 로딩 상태 표시 */ }
153- { ! mounted ? (
154- < div
155- className = "p-4 bg-muted/30 text-muted-foreground font-mono text-xs space-y-2 animate-pulse"
156- style = { { minHeight : "8rem" } }
157- >
158- { code
159- . split ( "\n" )
160- . slice ( 0 , 8 )
161- . map ( ( _ , idx ) => (
162- < div
163- key = { idx }
164- className = "h-4 bg-muted-foreground/20 rounded"
165- style = { { width : `${ Math . floor ( Math . random ( ) * 50 ) + 50 } %` } }
166- > </ div >
167- ) ) }
168- </ div >
169- ) : (
170- < SyntaxHighlighter
171- language = { language }
172- style = { isDark ? oneDark : oneLight }
173- customStyle = { {
174- margin : 0 ,
175- padding : "1rem" ,
176- borderRadius : 0 ,
177- fontSize : "13px" ,
178- whiteSpace : "pre-wrap" , // 자동 줄바꿈
179- wordBreak : "break-all" , // 단어 중간에서도 줄바꿈
180- overflowWrap : "anywhere" , // 어디서든 줄바꿈 허용
181- maxWidth : "100%" ,
182- overflowX : "visible" , // 좌우 스크롤 제거
183- background : isDark
184- ? "rgba(30, 30, 30, 0.95)"
185- : "rgba(250, 250, 250, 0.95)" ,
186- transition : "background 0.3s ease" ,
187- } }
188- wrapLines = { true }
189- wrapLongLines = { true }
190- lineProps = { {
191- style : { wordBreak : "break-all" , whiteSpace : "pre-wrap" } ,
192- } }
193- className = "whitespace-pre-wrap break-all"
194- PreTag = { ( { children, ...props } ) => (
195- < pre { ...props } className = "transition-colors duration-300" >
196- { children }
197- </ pre >
198- ) }
199- >
200- { code }
201- </ SyntaxHighlighter >
202- ) }
189+ { /* 항상 CodeHighlighter 렌더링 */ }
190+ < CodeHighlighter language = { language } code = { code } isDark = { isDark } />
203191 </ div >
204192 </ motion . div >
205- </ motion . div >
193+ </ div >
206194 ) ;
207195}
196+
197+ export default memo ( CodeBlock , ( prevProps , nextProps ) => {
198+ return (
199+ prevProps . code === nextProps . code &&
200+ prevProps . language === nextProps . language &&
201+ prevProps . filename === nextProps . filename
202+ ) ;
203+ } ) ;
0 commit comments