Skip to content

[fix] 초기 로드 렌더 차단 리소스 제거 - #1945

Open
seongwon030 wants to merge 2 commits into
develop-fefrom
fix/render-blocking-resources
Open

seongwon030 wants to merge 2 commits into
develop-fefrom
fix/render-blocking-resources

Conversation

@seongwon030

@seongwon030 seongwon030 commented Aug 23, 2026

Copy link
Copy Markdown
Member

#️⃣연관된 이슈

없음 (Lighthouse "렌더링 차단 요청" 지적 대응)

📝작업 내용

Lighthouse가 짚은 렌더 차단 요청 4건을 추적한 결과, 원인이 서로 다른 두 가지였습니다.

1. react-datepicker가 초기 청크에 딸려오던 문제 (vite.config.ts)

manualChunksreact-datepickerdate-fns를 둘 다 'dates' 청크로 묶고 있었습니다. date-fnsgetDeadLineText.ts, formatTimeAgo.ts메인 진입 경로에서 정적 import 되기 때문에, 관리자 모집 설정 탭에서만 쓰는 react-datepicker까지 초기 그래프로 끌려왔습니다.

react-datepicker'datepicker' 별도 청크로 분리했습니다.

before after
렌더 차단 CSS swiper 7.8KB + dates 22KB swiper 7.8KB
초기 modulepreload (dates) 157KB (gzip 38KB) 53KB (gzip 13.6KB)
datepicker 103.9KB (gzip 25KB) 전 페이지 초기 로드 AdminRoutes 지연 청크로 이동

2. 관리자 전용 폰트가 전체 렌더를 막던 문제 (index.html)

Krona One은 관리자 로그인 화면의 Log in 제목 한 곳(LoginTab.styles.ts:31)에서만 쓰는데, 동기 stylesheet라 관리자 페이지를 볼 일 없는 방문자 전원의 첫 렌더를 잡고 있었습니다. media="print" onload="this.media='all'"로 비동기 로드로 바꿨습니다.

검증 방법

  • tsc --noEmit 통과
  • jest 52 suites / 444 tests 전부 통과
  • vite builddist/index.html에 datepicker 관련 <link>가 없고, AdminRoutes-*.js__vitePreload 의존 목록에만 문자열로 남는 것 확인
  • 빌드 산출물에서 Krona One <link>media="print" onload 속성이 유지되는 것 확인

측정 결과 (로컬 Lighthouse 12, before/after 교차 측정)

두 빌드를 같은 로컬 서버(vite preview + dev API 프록시)에서 devtools 스로틀링 10Mbps/40ms·CPU 4x로 교차 측정했습니다. LCP 요소는 두 빌드 모두 앱 다운로드 팝업 이미지(app-download.webp)로 프로덕션과 동일합니다.

중앙값 (n=9) before after
FCP 2268ms 954ms
LCP 2287ms 1208ms
빠른 모드(FCP<1.5s) 비율 1/9 7/9

결과가 ~950ms / ~2270ms 이중 분포로 갈립니다. before는 9회 중 8회가 느린 모드, after는 9회 중 7회가 빠른 모드였습니다. 트레이스를 보면 느린 회차는 1315ms에 페이지 전체를 Paint까지 해놓고 compositor가 2263ms까지 한 프레임도 화면에 내보내지 않습니다(필름스트립에도 그 사이 프레임 없음). 차단 CSS는 모든 회차에서 300ms 안에 끝나므로, Lighthouse가 표시한 "160ms 절감" 모델과는 다른 경로입니다.

두 변경을 분리해 측정한 결과 Krona One 비차단 전환이 이 지연을 없앱니다. datepicker 분리만 적용한 빌드는 before와 같고(빠른 모드 1/4), Krona One만 비차단으로 바꾼 빌드는 after와 같습니다(3/4). datepicker 분리의 가치는 LCP가 아니라 초기 JS 103.9KB / CSS 22KB 제거입니다.

조건을 붙입니다. 이 지연은 headless Chrome(신·구 모드 모두)에서 재현한 것이고, 실사용자 Chrome에서 같은 정도로 나는지는 배포 후 Sentry Web Vitals p75 LCP로 확인해야 합니다. Google Fonts 차단 stylesheet가 Chrome 내부에서 왜 이 stall을 만드는지는 특정하지 못했습니다.

영향 범위

  • 학생용 화면: 초기 로드 JS·CSS 감소. 렌더 결과는 동일합니다.
  • 관리자용 화면: 모집 설정 탭 진입 시 datepicker 청크를 그때 받습니다(지연 청크로 이동). 로그인 화면의 Log in 제목은 Krona One이 늦게 적용되어 잠깐 폴백 폰트로 보일 수 있습니다.
  • CSP 설정이 없어 인라인 onload 핸들러는 차단되지 않는 것을 확인했습니다.

논의하고 싶은 부분

Pretendard는 판단이 갈려서 이번에 손대지 않았습니다.

  • 비차단 전환: CDN pretendard.css는 이미 9개 @font-face 전부 font-display: swap이라, 차단이 FOUT을 막아주고 있지 않습니다. 다만 빠른 회선에서 스왑이 매번 보이게 됩니다. FCP는 좋아지고 CLS는 나빠질 수 있습니다. 이번 측정에서도 빠른 모드 회차는 CLS 0.076, 느린 모드는 0이었습니다.
  • 셀프 호스팅: 차단은 유지하되 서드파티 연결 300ms가 사라집니다. 대신 폰트 파일을 배포에 포함해야 합니다.

🫡 참고사항

별건이지만 같이 확인하다 발견했습니다. 현재 쓰는 static/pretendard.cssweight당 woff2가 765~791KB입니다(Regular 765,892 / Bold 791,156 바이트). 이번 측정 트레이스에서도 Regular·Medium·SemiBold·Bold 4개가 각각 ~6초 동안 내려오는 것이 확인됐습니다(10Mbps 기준). font-display: swap이라 LCP는 막지 않지만 대역폭을 크게 잡습니다.

dist/web/static/pretendard-dynamic-subset.css로 URL 한 줄만 바꾸면 unicode-range 분할 서브셋이라 실제 쓰는 글자 범위만 받습니다. font-family와 weight가 동일한 드롭인이라 시각적 변화가 없습니다. 렌더 차단과는 다른 축이라 이 PR에는 넣지 않았습니다.

manualChunks가 react-datepicker와 date-fns를 같은 'dates' 청크로 묶고 있었다.
date-fns는 getDeadLineText 등 메인 진입 경로에서 정적 import되므로, 관리자
화면에서만 쓰는 react-datepicker까지 초기 그래프로 끌려와 JS 103.9KB가
모든 페이지에서 modulepreload되고 CSS 22KB가 렌더를 차단했다.
Krona One은 관리자 로그인 화면의 'Log in' 제목 한 곳에서만 쓰는데,
index.html의 동기 stylesheet라 전체 방문자의 첫 렌더를 막고 있었다.
@vercel

vercel Bot commented Aug 23, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
moadong Ready Ready Preview Aug 23, 2026 1:58pm

@coderabbitai

coderabbitai Bot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@seongwon030, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 57 minutes

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

Wait for the limit to reset, then comment @coderabbitai review or push new commits to the PR.

An organization admin can change what happens after included review limits in Billing.

How do review limits work?

CodeRabbit enforces per-developer PR review limits within each organization.

For paid Pro and Pro+ reviews, CodeRabbit uses a developer's included PR review attempts over the past 7 days to set the current hourly allowance. At typical activity levels, the full plan allowance applies. Higher sustained activity can lower the allowance until earlier attempts leave the 7-day window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: c67727e4-d10e-49dd-a196-6c5e5ecf7a11

📥 Commits

Reviewing files that changed from the base of the PR and between f5eef03 and 8369744.

📒 Files selected for processing (2)
  • frontend/config/vite.config.ts
  • frontend/index.html

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@seongwon030 seongwon030 added the 🛠Fix 기능이 의도한 대로 동작하지 않는 버그를 수정 label Sep 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

💻 FE Frontend 🛠Fix 기능이 의도한 대로 동작하지 않는 버그를 수정

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant