프로그레시브 웹 UI
지각된 성능(Perceived performance)은 UI가 실제로 빠른 것뿐만 아니라 느껴지게 빠르게 만드는 것입니다. 2초에 로딩되지만 스켈레톤을 즉시 보여주는 페이지는 1.5초에 로딩되지만 내내 빈 화면을 보여주는 페이지보다 더 빠르게 느껴집니다.
사용자가 코드를 공유하면:
- 사용자가 로딩 및 대기 상태에서 현재 무엇을 보고 있는지 이해하기 위해 코드를 읽습니다.
- 아래 의사 결정 가이드를 사용하여 적용 가능한 기술(들)을 식별합니다.
- 무엇을 변경해야 하는지, 왜 그것이 지각된 속도를 향상시키는지, 그리고 어떻게 구체적인 코드로 구현하는지 설명합니다.
핵심 원칙
1. "첫 번째 뷰"와 나머지를 분리하십시오
항상 다음을 구분하십시오:
- 첫 번째 페인트(First Paint)에 필요한 것
- 셸(Shell)이 표시된 후에 로드할 수 있는 것
- 사용자 상호작용까지 기다릴 수 있는 것
모든 데이터가 동일하게 긴급하다고 취급하지 마십시오.
2. 데이터보다 구조를 먼저 제공하십시오
페이지 셸(레이아웃, 탐색 슬롯, 헤더 프레임)은 데이터 가져오기가 완료되기 전에 브라우저로 전송되어야 합니다. 사용자는 처음 100-200ms 내에 속도 인상을 형성합니다. 구조를 즉시 표시하면 실제 로드 시간이 동일하더라도 지각된 대기 시간을 줄일 수 있습니다.
3. 클릭을 무시당한 채로 두지 마십시오
모든 상호작용(클릭, 탭, 탐색)은 첫 번째 프레임에서 눈에 보이는 응답을 생성해야 합니다. 실제 작업에 시간이 걸리는 경우 즉시 중간 상태를 표시하십시오.
4. 로딩 UI만 고치지 말고 구조를 고치십시오
loading.tsx와 스켈레톤은 외관을 개선하지만 과도한 데이터 가져오기나 순차적 데이터 종속성 문제를 해결하지는 않습니다. 항상 근본 원인을 해결하십시오:
- 순차적인
await체인 →Promise.all로 병렬화 - 단일 대용량 가져오기 → 첫 번째 뷰와 지연된 것으로 분할
- 탐색에서 전체 프로필 가져오기 → 경량 탐색 아이덴티티 헬퍼 사용
5. 탐색도 빠르게 느껴져야 합니다
지각된 속도는 초기 로딩에만 해당되지 않습니다. 페이지 전환, 재방문, 상호작용도 동등하게 빠르게 느껴져야 합니다:
- 사용자가 클릭하기 전에 경로를 프리페치(Prefetch)하십시오.
- 탐색 시작 시 즉시
loading.tsx를 표시하십시오. - SWR/캐시를 사용하여 즉시 재방문하십시오.
의사 결정 가이드
| 증상 | 기술 |
|---|---|
| 데이터 로딩 중 빈 화면 | 스켈레톤 스크린 / Suspense 스트리밍 |
| 버튼 클릭에 즉각적인 응답 없음 | 낙관적 UI / 즉각적인 피드백 |
| 페이지 전환이 클릭 후 시각적으로 시작되지 않음 | loading.tsx + 프리페치 |
| 무거운 컴포넌트가 초기 렌더링 속도 저하 | 동적 임포트 |
| 동일한 데이터에 방문할 때마다 스피너 표시 | SWR stale-while-revalidate |
| 하나의 느린 API가 전체 페이지를 차단 | Suspense 스트리밍(병렬 비동기 서버 컴포넌트) |
| 인증/프로필 가져오기가 탐색 또는 페이지 셸 지연 | 인증 레이아웃 분리(references/auth-performance.md 참조) |
| 이미지로 인한 레이아웃 이동 또는 느린 느낌 | next/image + priority / blur 플레이스홀더 |
| 글꼴 스왑으로 인한 스타일 없는 텍스트 깜빡임 | next/font |
| 양식 제출에 시각적 피드백 없음 | useFormStatus / useActionState |
| 긴 목록으로 인한 스크롤 버벅임 또는 느린 렌더링 | 가상 스크롤링(TanStack Virtual / react-window) |
| 긴급하지 않은 상태 업데이트가 사용자 입력 차단 | 지연 렌더링을 위한 startTransition |
분석 워크플로우
1단계: 사용자가 먼저 보는 것을 식별하십시오
0-200ms 내에 무엇이 렌더링됩니까? 빈 화면이나 스피너라면 스타일링 수정 전에 구조적 변경이 필요합니다.
2단계: 데이터를 긴급도별로 분류하십시오
- 첫 번째 뷰 — 페이지가 의미를 가지기 위해 반드시 있어야 함
- 지연됨 — 셸이 표시된 후 스트리밍 가능
- 요청 시 — 사용자 상호작용 후에만 로드
3단계: 데이터 종속성을 수정하십시오
- 순차적인
await→Promise.all또는 별도의 비동기 서버 컴포넌트 - 초기 로드 시 과도한 데이터 가져오기 → API 분할 또는 페이지네이션
- 탐색에서 전체 프로필 → 별도의 탐색 아이덴티티 헬퍼
4단계: 점진적 렌더링을 적용하십시오
필요한 것에 따라 선택하십시오:
loading.tsx— 즉각적인 탐색 스켈레톤 (전체 페이지)<Suspense>— 세분화된 스트리밍 (섹션별)useOptimistic— 즉각적인 돌연변이 피드백dynamic()— 무거운 컴포넌트 번들 지연- SWR — 캐시에서 즉시 재방문
5단계: 지각된 성능을 검증하십시오
- 의미 있는 내용이 즉시 나타납니까 (< 200ms)?
- 모든 로딩 상태에서 사용자에게 정보가 제공됩니까?
- 전체 데이터가 도착하기 전에 UI가 반응적입니까?
- 상호작용이 첫 번째 프레임에서 피드백을 생성합니까?
출력 형식
이 스킬을 적용할 때는 다음 구조로 응답하십시오:
## 현재 상황
- [사용자가 현재 경험하는 것을 설명]
## 근본 원인
- [코드에서 느린 느낌을 유발하는 것]
## 변경 사항 및 이유
- [변경 1]: [이것이 지각된 속도를 향상시키는 이유]
- [변경 2]: ...
## 구현
[코드]
## 참고 사항
- [주의 사항, 예외 사항 또는 후속 개선 사항]
참고 자료
전체 코드 예제가 포함된 구현 세부 사항은 다음을 참조하십시오:
references/nextjs-patterns.md— 스켈레톤 스크린, 낙관적 UI, Suspense 스트리밍, 프리페치, 동적 임포트, SWR, 이미지, 글꼴, 상호작용 피드백,useFormStatus, 가상 스크롤링,startTransitionreferences/auth-performance.md— 인증 레이아웃 분리, 탐색 아이덴티티 헬퍼, 요청/프로세스 워밍업, 폴백 상태 설계
특정 기술을 구현할 때는 관련 참조 문서를 읽으십시오.
수정해야 할 안티 패턴
항상 다음을 감지하고 해결하십시오:
- 최상위 레이아웃에서 전체 프로필/인증을 await하는 경우 → 모든 페이지 지연
- 독립적인 데이터에 대한 순차적인
await체인 → 불필요한 직렬화 - 기본적인 과도한 데이터 가져오기를 수정하지 않고
loading.tsx만 추가 - 초기 로드 시 전체 데이터셋 렌더링 → 페이지네이션 또는 가상화
- 작업이 완료될 때까지 클릭 시 시각적 피드백 없음 → 즉시 인지
- 탐색과 페이지에서 서로 다른 인증 소스 사용 → 정식 사용자 이름 불일치
- 서버가 응답한 후에만 양식 제출 상태 관리 →
useFormStatus를 사용하여 즉시 대기 상태 표시 - 마운트 시 긴 목록의 모든 항목 렌더링 → 가상화(보이는 행만 렌더링)
- 값비싼 상태 업데이트(필터/정렬/검색)가 사용자 입력 차단 →
startTransition으로 감싸기