
초기 진입 시 모든 이미지가 불필요하게 로딩되어, 우선순위가 높은 비디오가 늦게 로딩된다.
스크롤로 이미지가 뷰포트에 들어오는 시점에 이미지를 로드 → 비디오 우선 로딩, UX 개선
useEffect(() => {
const options = {
root: null,
threshold: 1,
rootMargin: 0,
};
const callback = (entries, observer) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.src = entry.target.dataset.src;
observer.unobserve(entry.target);
}
});
};
const observer = new IntersectionObserver(callback, options);
observer.observe(imgRef.current);
return () => observer.disconnect();
}, []);
<img data-src="image.jpg" ref={imgRef} />
<img src="image.jpg" alt="설명" loading="lazy" />
loading 속성:
auto: 브라우저가 판단lazy: 늦게 로딩eager: 즉시 로딩picture 태그를 사용해 호환성 확보<picture>
<source data-srcset="image.webp" type="image/webp" />
<img data-src="image.jpg" ref={imgRef} />
</picture>
<picture>
<source class="webp-source" data-srcset="main.webp" type="image/webp" />
<source class="png-source" data-srcset="main.png" type="image/png" />
<img data-src="main.jpg" ref={imgEl3} />
</picture>
const webpSourceEl = entry.target.parentElement.querySelector('.webp-source');
const pngSourceEl = entry.target.parentElement.querySelector('.png-source');
<source><video autoPlay loop muted>
<source src="video.webm" type="video/webm" />
<source src="video.mp4" type="video/mp4" />
</video>
blur 필터나 패턴 오버레이로 저화질 눈속임 처리.pattern-overlay {
background-image: url('pattern.png');
opacity: 0.5;
position: absolute;
top: 0; left: 0;
width: 100%; height: 100%;
}
| 방식 | 특징 |
|---|---|
| FOUT | 글자 먼저, 폰트 나중 (UX 빠름) |
| FOIT | 폰트 로드 후 표시 (시각적으로 깔끔) |
fontfaceobserverimport FontFaceObserver from "fontfaceobserver";
const font = new FontFaceObserver("BMYEONSUNG");
useEffect(() => {
font.load(null, 20000).then(() => setIsFontLoaded(true));
}, []);
| 포맷 | 특징 |
|---|---|
| TTF/OTF | 데스크톱용, 용량 큼 |
| WOFF | 웹 최적화 압축 |
| WOFF2 | WOFF보다 더 효율적 (30%↓) |
@font-face {
font-family: BMYEONSUNG;
src: url("subset.woff2") format("woff2"),
url("subset.woff") format("woff"),
url("subset.ttf") format("truetype");
font-display: block;
}
@font-face {
font-family: BMYEONSUNG;
src: url("data:font/woff2;charset=utf-8;base64,...") format("woff2");
}
const header = {
setHeaders: (res, path) => {
if (path.endsWith(".html")) {
res.setHeader("Cache-Control", "no-cache");
} else if (path.endsWith(".js") || path.endsWith(".css") || path.endsWith(".webp")) {
res.setHeader("Cache-Control", "public, max-age=31536000");
} else {
res.setHeader("Cache-Control", "no-store");
}
},
};
| 리소스 유형 | 캐시 전략 |
|---|---|
| HTML | no-cache (서버 확인 필요) |
| JS/CSS/이미지 | public, max-age=31536000 |
| 민감 데이터 | no-store |
배포 환경에서 사용하지 않는 CSS가 포함되어 Lighthouse 경고 발생
npm install --save-dev purgecss
purgecss --css ./build/static/css/*.css \
--output ./build/static/css/ \
--content ./build/index.html ./build/static/js/*.js \
--config ./purgecss.config.js
module.exports = {
defaultExtractor: (content) => content.match(/[\w\:\-]+/g) || [],
};