Skip to content

Commit 3900df1

Browse files
committed
Fix: 코드블럭 깜빡임 현상 수정
1 parent f2c03d3 commit 3900df1

File tree

1 file changed

+65
-69
lines changed

1 file changed

+65
-69
lines changed

components/code-block.tsx

Lines changed: 65 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"use client";
22

3-
import React, { useState, useEffect } from "react";
3+
import React, { useState, memo } from "react";
44
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
55
import {
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

Comments
 (0)