| title | impact | impactDescription | tags |
|---|---|---|---|
| Show/Hide에 Activity Component 사용 | MEDIUM | state/DOM 보존 | rendering, activity, visibility, state-preservation |
Show/Hide에 Activity Component 사용
리소스 집약적인 components의 visibility 토글을 관리하는 솔루션으로 React의 <Activity> component를 활용하세요. 이 접근법은 visibility 전환 중 component state와 DOM 요소 모두를 유지합니다.
사용 예시:
import { Activity } from "react";
function Dropdown({ isOpen }: Props) {
return (
<Activity mode={isOpen ? "visible" : "hidden"}>
<ExpensiveMenu />
</Activity>
);
}이 기법은 빈번한 show/hide 사이클을 거치는 components를 감싸서 비용 큰 re-renders와 state 손실을 방지합니다. component tree를 unmount하지 않고 불필요한 재계산을 방지하며 internal state 관리를 보존합니다.
| title | impact | impactDescription | tags |
|---|---|---|---|
| SVG Element 대신 SVG Wrapper 애니메이션 | LOW | 하드웨어 가속 활성화 | rendering, svg, css, animation, performance |
SVG Element 대신 SVG Wrapper 애니메이션
많은 브라우저가 SVG elements에서 CSS3 animations에 대한 하드웨어 가속을 지원하지 않습니다. 이 성능 문제를 해결하려면 SVG graphics를 <div> container 안에 넣고 SVG 자체가 아닌 wrapper에 animations를 적용하세요.
Incorrect (SVG elements 직접 애니메이션):
function AnimatedIcon() {
return (
<svg className="animate-spin">
<circle cx="50" cy="50" r="40" />
</svg>
);
}Correct (wrapper div 애니메이션):
function AnimatedIcon() {
return (
<div className="animate-spin">
<svg>
<circle cx="50" cy="50" r="40" />
</svg>
</div>
);
}지원되는 Properties:
transformopacitytranslatescalerotate
wrapper div를 사용하면 브라우저가 animation 렌더링을 GPU로 오프로드하여 SVG elements를 직접 애니메이션하는 CPU 기반 렌더링에 비해 더 나은 성능과 부드러운 시각적 경험을 제공합니다.
| title | impact | impactDescription | tags |
|---|---|---|---|
| 명시적 조건부 렌더링 사용 | LOW | 0 또는 NaN 렌더링 방지 | rendering, conditional, jsx, falsy-values |
명시적 조건부 렌더링 사용
JSX에서 components를 조건부로 렌더링할 때, && operator는 예상치 못한 output을 생성할 수 있습니다. 조건이 0, NaN, 또는 렌더되는 다른 falsy 값일 수 있을 때는 && 대신 명시적 ternary operators (? :)를 사용하세요.
Incorrect (0이 화면에 렌더됨):
function NotificationBadge({ count }: { count: number }) {
return <div>{count && <span className="badge">{count}</span>}</div>;
}
// count가 0일 때, "0"이 DOM에 렌더됨Correct (null이 제대로 처리됨):
function NotificationBadge({ count }: { count: number }) {
return <div>{count > 0 ? <span className="badge">{count}</span> : null}</div>;
}
// count가 0일 때, 아무것도 렌더되지 않음문제는 &&가 만나는 첫 번째 falsy 값을 반환하기 때문에 발생합니다. count가 0일 때, 표현식 count &&는 0으로 평가되고, 이것이 렌더링을 방지하는 falsy 값으로 처리되지 않고 DOM에 text로 렌더됩니다.
| title | impact | impactDescription | tags |
|---|---|---|---|
| 긴 Lists에 CSS content-visibility 적용 | HIGH | 더 빠른 초기 렌더 | rendering, css, content-visibility, long-lists |
긴 Lists에 CSS content-visibility 적용
content-visibility: auto를 적용하여 off-screen 렌더링을 지연시키세요.
CSS:
.message-item {
content-visibility: auto;
contain-intrinsic-size: 0 80px;
}Example:
function MessageList({ messages }: { messages: Message[] }) {
return (
<div className="h-screen overflow-y-auto">
{messages.map(msg => (
<div key={msg.id} className="message-item">
<Avatar user={msg.author} />
<div>{msg.content}</div>
</div>
))}
</div>
);
}1000개 메시지의 경우, 브라우저가 ~990개 off-screen 항목의 layout/paint를 건너뜁니다 (10배 빠른 초기 렌더).
| title | impact | impactDescription | tags |
|---|---|---|---|
| Static JSX Elements 끌어올리기 | LOW | 불필요한 재생성 방지 | rendering, jsx, static, optimization |
Static JSX Elements 끌어올리기
렌더 중 불필요한 재생성을 방지하기 위해 static JSX를 components 외부로 추출하세요.
JSX elements가 component 함수 내부에 정의되면, 내용이 절대 변경되지 않더라도 component가 렌더될 때마다 재생성됩니다.
Incorrect (매 렌더마다 재생성):
function LoadingSkeleton() {
return <div className="h-20 animate-pulse bg-gray-200" />;
}
function Container() {
return <div>{loading && <LoadingSkeleton />}</div>;
}Correct (한 번 생성, 참조로 사용):
const loadingSkeleton = <div className="h-20 animate-pulse bg-gray-200" />;
function Container() {
return <div>{loading && loadingSkeleton}</div>;
}이 최적화는 재생성 중 상당한 계산 overhead를 수반하는 복잡하고 변경되지 않는 SVG 구조에 특히 유용합니다.
Modern Considerations: React Compiler가 활성화되면 자동으로 static JSX elements를 끌어올리고 component re-renders를 최적화하여, 이 기능을 사용하는 프로젝트에서는 수동 구현이 불필요해질 수 있습니다.
| title | impact | impactDescription | tags |
|---|---|---|---|
| Flickering 없이 Hydration Mismatch 방지 | MEDIUM | visual flicker와 hydration errors 방지 | rendering, ssr, hydration, localStorage, flicker |
Flickering 없이 Hydration Mismatch 방지
client-side storage 작업 시, 개발자들은 딜레마에 직면합니다: server-side에서 localStorage에 접근하면 errors가 발생하고, hydration 후 업데이트하면 visible flickering이 생깁니다. 해결책은 React hydration이 발생하기 전에 DOM을 수정하는 동기 script를 주입하는 것입니다.
Server-side 접근 실패: server rendering 중 localStorage를 읽으려고 하면 Node.js 환경에 API가 존재하지 않아 error가 발생합니다.
Post-hydration 업데이트는 flicker 발생: 저장된 값을 가져오기 위해 useState와 useEffect를 사용하면 component가 처음에 기본 내용으로 렌더되고, hydration 완료 후 업데이트되어 jarring한 visual flash가 발생합니다.
Correct (inline script로 동기 적용):
function ThemeProvider({ children }: { children: React.ReactNode }) {
return (
<div id="theme-container">
<script
dangerouslySetInnerHTML={{
__html: `
(function() {
try {
var theme = localStorage.getItem('theme') || 'light';
document.getElementById('theme-container').className = theme;
} catch (e) {}
})();
`,
}}
/>
{children}
</div>
);
}inline script는 element를 보여주기 전에 동기적으로 실행되어 DOM이 이미 올바른 값을 가지도록 보장합니다. Flickering도 없고, hydration mismatch도 없습니다.
사용 사례:
- Theme toggles
- User preferences
- Authentication states
- 즉각적인 렌더링이 필요한 모든 client-only 데이터
| title | impact | impactDescription | tags |
|---|---|---|---|
| 예상되는 Hydration Mismatches 억제 | LOW-MEDIUM | 알려진 차이에 대한 noisy hydration warnings 방지 | rendering, hydration, ssr, nextjs |
예상되는 Hydration Mismatches 억제
Next.js 같은 server-side rendering frameworks에서, random identifiers, timestamps, locale-specific formatting 같은 특정 값들은 server와 client renders 간에 의도적으로 다릅니다. 불필요한 console warnings를 제거하기 위해 이런 예상되는 mismatches를 suppressHydrationWarning으로 감싸는 것을 권장합니다. 단, 이 도구는 알려진 불일치에만 적용하고 진짜 버그를 감추는 수단으로 남용해서는 안 됩니다.
Incorrect (warnings 생성):
function Timestamp() {
return <span>{new Date().toLocaleString()}</span>;
}Correct (예상되는 warning 억제):
function Timestamp() {
return <span suppressHydrationWarning>{new Date().toLocaleString()}</span>;
}핵심 차이점은 suppressHydrationWarning이 dynamic content를 자체 element 내에 격리하여 framework가 해당 특정 mismatch에 대한 validation을 건너뛰면서 component tree의 다른 곳에서 의도하지 않은 차이에 대한 warnings는 보존하도록 한다는 것입니다.
| title | impact | impactDescription | tags |
|---|---|---|---|
| React DOM Resource Hints 사용 | HIGH | critical resources의 load time 감소 | rendering, preload, preconnect, prefetch, performance |
React DOM Resource Hints 사용
React DOM은 브라우저에 필요한 resources를 hint하는 APIs를 제공합니다. 이는 client가 HTML을 받기도 전에 resources를 loading 시작하는 server components에서 특히 유용합니다.
prefetchDNS(href): 연결 예정인 domain의 DNS 해석preconnect(href): server에 connection 수립 (DNS + TCP + TLS)preload(href, options): 곧 사용할 resource (stylesheet, font, script, image) fetchpreloadModule(href): 곧 사용할 ES module fetchpreinit(href, options): stylesheet나 script를 fetch하고 evaluatepreinitModule(href): ES module을 fetch하고 evaluate
Example (third-party APIs에 preconnect):
import { preconnect, prefetchDNS } from "react-dom";
export default function App() {
prefetchDNS("https://analytics.example.com");
preconnect("https://api.example.com");
return <main>{/* content */}</main>;
}Example (critical fonts와 styles preload):
import { preload, preinit } from "react-dom";
export default function RootLayout({ children }) {
// font file preload
preload("/fonts/inter.woff2", {
as: "font",
type: "font/woff2",
crossOrigin: "anonymous",
});
// critical stylesheet를 즉시 fetch하고 적용
preinit("/styles/critical.css", { as: "style" });
return (
<html>
<body>{children}</body>
</html>
);
}Example (code-split routes에 modules preload):
import { preloadModule, preinitModule } from "react-dom";
function Navigation() {
const preloadDashboard = () => {
preloadModule("/dashboard.js", { as: "script" });
};
return (
<nav>
<a href="/dashboard" onMouseEnter={preloadDashboard}>
Dashboard
</a>
</nav>
);
}각각 사용 시점:
| API | 사용 사례 |
|---|---|
prefetchDNS | 나중에 연결할 third-party domains |
preconnect | 즉시 fetch할 APIs나 CDNs |
preload | 현재 페이지에 필요한 critical resources |
preloadModule | 다음 navigation 가능성이 높은 JS modules |
preinit | 일찍 실행해야 하는 stylesheets/scripts |
preinitModule | 일찍 실행해야 하는 ES modules |
Reference: React DOM Resource Preloading APIs
| title | impact | impactDescription | tags |
|---|---|---|---|
| Script Tags에 defer 또는 async 사용 | HIGH | render-blocking 제거 | rendering, script, defer, async, performance |
Script Tags에 defer 또는 async 사용
defer나 async 없는 script tags는 script가 download되고 실행되는 동안 HTML parsing을 블로킹합니다. 이는 First Contentful Paint와 Time to Interactive를 지연시킵니다.
defer: 병렬로 download, HTML parsing 완료 후 실행, 실행 순서 유지async: 병렬로 download, 준비되면 즉시 실행, 순서 보장 없음
DOM이나 다른 scripts에 의존하는 scripts에는 defer를 사용하세요. analytics 같은 독립적인 scripts에는 async를 사용하세요.
Incorrect (렌더링 블로킹):
export default function Document() {
return (
<html>
<head>
<script src="https://example.com/analytics.js" />
<script src="/scripts/utils.js" />
</head>
<body>{/* content */}</body>
</html>
);
}Correct (non-blocking):
export default function Document() {
return (
<html>
<head>
{/* 독립적인 script - async 사용 */}
<script src="https://example.com/analytics.js" async />
{/* DOM 의존 script - defer 사용 */}
<script src="/scripts/utils.js" defer />
</head>
<body>{/* content */}</body>
</html>
);
}Note: Next.js에서는 raw script tags 대신 strategy prop과 함께 next/script component를 선호하세요:
import Script from "next/script";
export default function Page() {
return (
<>
<Script src="https://example.com/analytics.js" strategy="afterInteractive" />
<Script src="/scripts/utils.js" strategy="beforeInteractive" />
</>
);
}Reference: MDN - Script element
| title | impact | impactDescription | tags |
|---|---|---|---|
| SVG Precision 최적화 | LOW | 파일 크기 감소 | rendering, svg, optimization, svgo |
SVG Precision 최적화
파일 크기를 최소화하기 위해 SVG 좌표 precision을 줄이세요. 적절한 precision level은 viewBox dimensions에 따라 다르지만, precision을 낮추는 것이 일반적으로 권장됩니다.
Incorrect (과도한 decimal places):
<path d="M 10.293847 20.847362 L 30.938472 40.192837" />Correct (single decimal precision):
<path d="M 10.3 20.8 L 30.9 40.2" />SVGO로 자동화:
npx svgo --precision=1 --multipass icon.svg이 command는 더 나은 compression 결과를 위해 multipass process로 precision 감소를 적용합니다.
| title | impact | impactDescription | tags |
|---|---|---|---|
| 수동 Loading States 대신 useTransition 사용 | LOW | re-renders 감소와 코드 명확성 향상 | rendering, transitions, useTransition, loading, state |
수동 Loading States 대신 useTransition 사용
수동 useState loading state 관리를 자동으로 transition logic을 처리하는 built-in isPending state를 제공하는 React의 useTransition hook으로 교체하세요.
Incorrect (수동 loading state 관리):
function Search() {
const [isLoading, setIsLoading] = useState(false);
const [query, setQuery] = useState("");
const [results, setResults] = useState([]);
const handleSearch = async (value: string) => {
setIsLoading(true);
setQuery(value);
try {
const data = await fetchResults(value);
setResults(data);
} finally {
setIsLoading(false);
}
};
return (
<>
<input onChange={e => handleSearch(e.target.value)} />
{isLoading ? <Spinner /> : <Results data={results} />}
</>
);
}Correct (useTransition 사용):
import { useTransition, useState } from "react";
function Search() {
const [isPending, startTransition] = useTransition();
const [query, setQuery] = useState("");
const [results, setResults] = useState([]);
const handleSearch = async (value: string) => {
startTransition(async () => {
setQuery(value);
const data = await fetchResults(value);
setResults(data);
});
};
return (
<>
<input onChange={e => handleSearch(e.target.value)} />
{isPending ? <Spinner /> : <Results data={results} />}
</>
);
}주요 Benefits:
- 자동 pending state: 수동으로
setIsLoading(true/false)를 관리할 필요 없음 - Transitions에서 errors가 발생해도 resilience
- 업데이트 중 향상된 UI responsiveness
- 새 transitions가 자동으로 pending transitions를 취소
Reference: React useTransition