Section
3D 이력서 사이트 성능 최적화
2026.09·개인 프로젝트 · AI 도구와 함께 분석·개선
Next.jsReactThree.js성능 측정
프로젝트 핵심 요약
문제에서 결과까지- 홈 리소스 전송 본문
- 95.7% ↓
- 도시 유휴 draw call
- 0회
- 기본 개발 의존성
- 약 144MiB ↓
- 문제
- 3D 이력서가 첫 진입에 전국 지도·건물·HDR을 모두 받고, 상세 페이지를 읽는 동안에도 Canvas를 유지해 네트워크와 GPU 비용이 컸습니다.
- 판단
- 포트폴리오의 3D 탐색을 유지하면서, 방문한 화면에 필요한 리소스만 받도록 바꾸고 지도 삼각분할을 사전 생성 단계로 옮겼습니다.
- 내 기여
- AI 코딩 도구와 함께 병목을 점검하고, 지도 분할·사전 생성, Scene 지연 로딩, 도시 demand 렌더링, 오류 복구와 경량 배포 구성을 적용했습니다.
- 결과
- 최적화 당시 홈 리소스 전송 본문을 약 10.74MiB에서 469.3KiB로 줄였습니다. 도시 유휴 draw call은 0회였고, 모델 변환기를 분리해 기본 개발 의존성도 약 144MiB 줄였습니다.
범위와 측정 기준 · 2026-09-06 최적화 직후의 기록: Windows, Node.js 22.14.0, Chrome 148, 로컬 프로덕션 서버, 캐시 비활성화, 1440×1000. Resource Timing encodedBodySize 합계로 HTML·HTTP 헤더는 제외했습니다. 이번 콘텐츠 보강·프로젝트 목록 표시 시간 조정 이전 수치이며 FPS·로딩 시간 개선율로 환산하지 않았습니다.
Overview
이 사이트는 한국 지도에서 서울·부산의 건물을 선택해 경력을 탐색하는 포트폴리오입니다. 첫 화면에서 방문하지 않은 지역·건물까지 불러오는 문제를 찾아, 콘텐츠 접근성을 유지하면서 전송량과 렌더링 비용을 줄였습니다.
Background
- ▸초기 화면에서 약 25.7MB의 전국 GeoJSON과 모든 도시 건물, HDR 조명을 함께 요청했습니다.
- ▸경력 상세 화면에서도 3D Canvas를 유지하고, 정지된 도시 지도도 매 프레임 다시 그렸습니다.
- ▸지도나 Scene 청크가 실패하면 본문 접근이 막히고, 재시도하기 어려웠습니다.
- ▸일회성 OBJ 변환기의 큰 하위 의존성이 평소 개발 설치에도 포함되어 있었습니다.
Architecture
현재 사이트 코드의 콘텐츠·3D 로딩 경계와 지도 사전 처리·배포 구조입니다.
검증 근거 · 현재 resume 저장소의 컴포넌트, 지도 로더, 빌드·패키징 스크립트 및 프로덕션 빌드로 대조했습니다.
→ 요청 · 전달⇢ 코드 의존성 (점선)→ 빌드 · 배포
콘텐츠 우선 표시와 선택적 3D 로딩
서버 HTML을 먼저 표시하고, 홈에서만 지도 모듈을 준비합니다.
노드 선택 · Ctrl/⌘ + 휠로 확대 · 드래그로 이동
100%
- • 목록의 최소 표시 시간과 지도 준비는 병렬입니다. 상세 URL은 3D를 요청하지 않고, 지도 실패·동작 줄이기·데이터 절약에서는 목록 경로를 제공합니다.
- • 도시는 demand 렌더링을 사용합니다. 한국 지도는 페이지가 보이고 동작 줄이기가 꺼져 있을 때 마커 애니메이션을 계속 렌더링합니다.
연결 관계를 텍스트로 보기
- 정적 HTML → SceneRoot · hydrate
- SceneRoot → Scene / Canvas · 홈 · 지도 모드
- SceneRoot → 프로젝트 목록 · 목록 유지
- Scene / Canvas → 지도 JSON · loadMap
- Scene / Canvas → RegionMap · 도시 선택
- RegionMap → 도시 건물 GLB · useGLTF
지도 사전 처리와 배포 패키징
지도 갱신 작업과 일반 사이트 빌드를 분리해 운영 시 원본 지리 데이터를 처리하지 않습니다.
노드 선택 · Ctrl/⌘ + 휠로 확대 · 드래그로 이동
100%
- • maps:build는 지도 갱신 때만 실행합니다. 일반 서버용 빌드는 standalone을 패키징하고, VERCEL=1에서는 standalone을 끄고 Vercel의 플랫폼 어댑터가 산출물을 처리합니다.
- • 해시 지도에는 immutable 캐시를 적용합니다. 고정 경로의 건물 모델은 1시간 후 재검증하며, 배포에는 사용 중인 에셋만 포함합니다.
연결 관계를 텍스트로 보기
- 원본 지도 데이터 → maps:build · 갱신 입력 (빌드·배포)
- maps:build → 해시 지도 파일 · 파일 생성 (빌드·배포)
- 사이트 소스 → next build · 빌드 입력 (빌드·배포)
- next build → 배포 패키징 · 산출물 (빌드·배포)
- 해시 지도 파일 → 배포 패키징 · 필요 파일 (빌드·배포)
- 배포 패키징 → Node 서버 · 배포 · 실행 (빌드·배포)
- next build → Vercel · VERCEL=1 (빌드·배포)
Key Decisions
| 결정 | 이유 | 결과 |
|---|---|---|
| 지역별 지도 분할·메시 사전 생성 | 방문자마다 전국 데이터를 받고 삼각분할할 필요가 없음 | 첫 지도 전송 본문 8.32MiB → 83.6KiB |
| 홈에서만 Scene 로드·상세에서 Canvas 해제 | 본문을 읽는 동안 3D 초기화와 렌더링이 필요하지 않음 | 상세 직접 진입의 지도·GLB 요청 0개 |
| 도시 demand 렌더링·복제 자원 dispose | 화면 변화가 없는 프레임과 전환 후 잔여 자원 감소 | 도시 유휴 draw 0회, 20회 전환 시 버퍼 계수 일정 |
| HDR·실시간 그림자 제거, DPR 상한 1.25 | 정교한 조명보다 읽기와 안정적인 탐색을 우선 | 조명 리소스·추가 패스 감소, 선명도와의 절충 |
| 모델 변환기 분리·standalone 배포 | 가끔 쓰는 개발 도구와 사용하지 않는 에셋의 기본 포함 방지 | 기본 개발 의존성 약 144MiB 감소 |
What I Did
- ▸AI 코딩 도구와 함께 프로덕션 요청·번들·GPU 동작을 점검하고, 가장 큰 지도 데이터 병목부터 개선했습니다.
- ▸고정 원본에서 지역별 메시를 생성하고, 공유 경계·해시·영역 수·삼각형 인덱스를 검사했습니다.
- ▸상세 경로의 3D 로딩을 분리하고, 지도 실패 재시도·건물 대체 표시·목록 탐색을 추가했습니다.
- ▸Puppeteer로 모바일 뷰포트, 실제 건물 클릭, 오류 복구, 도시 반복 전환과 캐시 헤더를 검증했습니다.
Before / After
| 항목 | Before | After |
|---|---|---|
| 홈 리소스 전송 본문 | 11,261,990 B | 480,601 B (약 -95.7%) |
| 첫 지도 전송 본문 | 8,721,526 B | 85,582 B (약 -99.0%) |
| 홈 GLB / HDR 요청 | 4개 / 1개 | 0개 / 0개 |
| 도시 유휴 렌더링 | 매 프레임 갱신 | 관찰 구간 draw call 0회 |
| 기본 개발 의존성 | 769,771,310 B | 618,636,866 B |
Impact
- ▸최적화 직후 지도 회귀 테스트 7개·브라우저 검사 40개를 통과했습니다. 당시 버전의 검사 결과입니다.
- ▸서울↔부산 20회 전환에서 live WebGL buffer 계수가 30개로 일정했습니다. 전체 메모리 누수 부재를 뜻하는 수치는 아닙니다.
- ▸JavaScript·WebGL을 사용할 수 없거나 지도 요청이 실패해도, 경력과 프로젝트 링크에 접근하도록 개선했습니다.
- ▸한국 마커 애니메이션은 유지하고, 모바일의 세밀한 지도 라벨은 확대해서 보도록 해 표현과 비용의 절충을 남겼습니다.
Tech Stack
Frontend
Next.js 16React 19TypeScriptTailwind CSS
3D / Data
Three.jsReact Three FiberTopoJSONBufferGeometry
Verification
PuppeteerResource TimingNode.js testESLint