11"use client" ;
22
3- import React , { useState , memo } from "react" ;
3+ import React , { useState , useEffect } from "react" ;
44import { Prism as SyntaxHighlighter } from "react-syntax-highlighter" ;
55import {
66 oneDark ,
@@ -17,65 +17,23 @@ interface CodeBlockProps {
1717 filename ?: string ;
1818}
1919
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 ( {
20+ export default function CodeBlock ( {
6921 language,
7022 code,
7123 filename,
7224} : CodeBlockProps ) {
7325 const [ isCopied , setIsCopied ] = useState ( false ) ;
7426 const [ isCollapsed , setIsCollapsed ] = useState ( false ) ;
27+ const [ mounted , setMounted ] = useState ( false ) ;
7528 const { resolvedTheme } = useTheme ( ) ;
76-
77- // 테마를 직접 체크 (서버와 클라이언트 모두에서 작동)
78- const isDark = resolvedTheme === "dark" ;
29+
30+ // 서버/클라이언트 하이드레이션 불일치 방지
31+ useEffect ( ( ) => {
32+ setMounted ( true ) ;
33+ } , [ ] ) ;
34+
35+ // 실제 사용할 테마 (마운트 전에는 기본값 사용)
36+ const isDark = mounted ? resolvedTheme === "dark" : false ;
7937
8038 const handleCopy = async ( ) => {
8139 await navigator . clipboard . writeText ( code ) ;
@@ -112,7 +70,12 @@ function CodeBlock({
11270 } ;
11371
11472 return (
115- < div className = "relative spacing-section rounded-lg overflow-hidden border border-primary/20 shadow-md" >
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+ >
11679 < div
11780 className = "flex items-center justify-between bg-gradient-to-r from-primary/20 to-primary/5 text-primary
11881 px-3 sm:px-4 py-2 sm:py-2.5 text-xs sm:text-sm font-mono border-b border-primary/15"
@@ -186,18 +149,59 @@ function CodeBlock({
186149 < div className = "absolute top-0 right-0 bottom-0 w-4 bg-gradient-to-l from-black/5 to-transparent pointer-events-none" > </ div >
187150 < div className = "absolute top-0 left-0 bottom-0 w-4 bg-gradient-to-r from-black/5 to-transparent pointer-events-none" > </ div >
188151
189- { /* 항상 CodeHighlighter 렌더링 */ }
190- < CodeHighlighter language = { language } code = { code } isDark = { isDark } />
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+ ) }
191203 </ div >
192204 </ motion . div >
193- </ div >
205+ </ motion . div >
194206 ) ;
195207}
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