무엇을 만들 수 있는지를 화면 자체로

2025.12 ~ 2026.04 (운영 중)

셰이더 히어로와 3개 국어 이력서 생성을 갖춘 원페이지 포트폴리오입니다.

과제

  • 만들 수 있는 것을 문장 대신 화면 자체로 보여줘야 함
  • 국문·영문·일문 이력서를 요청 즉시 건넬 수 있어야 함

담당 범위

  • 원페이지 구성: Next.js 16 App Router (1인)
  • GLSL 셰이더 히어로(@react-three/fiber), rAF 궤도·3D 캐러셀
  • 브라우저 이력서 PDF 생성, API 기반 MDX 렌더링

설계 판단

  • 이력서 PDF를 브라우저에서 생성 → 화면과 같은 데이터 소스라 내용 불일치 없음
  • 히어로를 GLSL 셰이더로, 궤도·캐러셀을 rAF 루프로 구현 → 해상도 독립, 매 프레임 계산
  • 작업물 본문도 API + MDX로 받고 웹훅으로 갱신 → 블로그와 렌더러·캐시 규칙 통일

결과 지표

  • 이력서 3개 언어(ko / en / jp), 브라우저에서 즉시 생성
  • TypeScript/TSX 59개 파일, 3,665줄
  • 포트폴리오 앱에 닿은 커밋 30건
원문 자세히 보기

과제

프리랜서로 직접 수주하는 입장에서 포트폴리오는 '무엇을 만들 수 있는지'를 문장이 아니라 화면 자체로 보여줘야 했고, 동시에 상대가 요구하는 형식(국문·영문·일문 이력서 PDF)으로도 즉시 건네줄 수 있어야 했다. 인터랙션을 넣되 그 비용이 초기 로딩과 저사양 기기에서 감당 가능한 선에 머무르게 하는 것이 과제였다.

담당 범위

원페이지 · 소스 3,665줄 · 3개 국어 이력서 생성

1인 개발. (1) Next.js 16 App Router 기반 단일 페이지 구성, (2) @react-three/fiber + drei로 GLSL 셰이더 히어로 구현, (3) requestAnimationFrame 기반 스킬 궤도·3D 캐러셀 인터랙션, (4) @react-pdf/renderer로 ko/en/jp 이력서를 브라우저에서 즉시 생성·내려받게 구현, (5) Framer Motion 기반 스크롤 인터랙션, (6) 작업물 콘텐츠를 백엔드 API에서 받아 MDX(next-mdx-remote + Shiki)로 렌더링, (7) /api/revalidate Route Handler로 어드민 저장 시 캐시 태그·경로 무효화.

보기

선택안 · 대안 · 근거 · 비용

이력서 PDF를 서버에서 만들지 않고 @react-pdf/renderer로 브라우저에서 ko/en/jp 3개 언어를 즉시 생성해 내려받게 했다

대안언어별 PDF 파일을 만들어 정적 자산으로 올려두거나, 서버에서 Puppeteer로 화면을 인쇄해 생성하는 안근거정적 파일은 이력 내용이 바뀔 때마다 3개 파일을 다시 만들어 올려야 하고, 그 순간 화면에 보이는 내용과 PDF가 어긋나기 시작한다. 서버 렌더는 이 앱만을 위해 헤드리스 브라우저를 띄우는 운영 부담이 붙는다. 브라우저 생성은 화면과 같은 데이터 소스를 쓰므로 둘이 어긋날 여지가 구조적으로 없다.비용PDF 레이아웃을 React 컴포넌트로 다시 짜야 해서 화면 컴포넌트를 재사용할 수 없고, 폰트(특히 한글·일문)를 직접 등록해야 하며 그 폰트가 번들 용량으로 들어온다. 생성이 사용자 기기에서 일어나므로 저사양 기기에서는 버튼을 누른 뒤 잠깐 멈춘 것처럼 보인다.

히어로 배경을 이미지나 동영상이 아니라 Three.js GLSL 셰이더로 그리고, 스킬 궤도·캐러셀 애니메이션은 CSS 트랜지션이 아니라 requestAnimationFrame 루프로 직접 돌렸다

대안미리 렌더한 배경 영상이나 Lottie 애니메이션을 얹거나, 전부 CSS 애니메이션·트랜지션으로 처리하는 안근거배경 영상은 용량이 크고 화면 비율마다 다시 뽑아야 한다. 셰이더는 해상도에 독립적이고 파일 크기가 사실상 코드 길이다. 궤도·캐러셀은 요소가 서로의 위치를 참조하며 매 프레임 갱신돼야 해서, 선언형 CSS 트랜지션으로는 중간 상태를 계산할 자리가 없었다.비용Three.js와 셰이더 코드가 번들에 들어와 이 앱만 초기 로딩 비용이 눈에 띄게 커졌고, GPU가 약한 기기에서는 프레임이 떨어진다. 2026-09-04 실측에서 이 대가가 그대로 나왔다 — 배포본 Lighthouse Performance 38(TBT 1,170ms)로, 같은 조건의 블로그(67, TBT 0ms)와 갈렸다. 접근성 96·Best Practices 100·SEO 100은 유지되므로 '전반적으로 나쁜 페이지'가 아니라 '메인 스레드를 오래 잡는 페이지'라는 성격이 분명하다. rAF 루프는 직접 정리(cleanup)를 놓치면 그대로 누수라, CSS라면 브라우저가 대신 해줬을 관리 책임을 코드가 떠안았다.

작업물 본문도 블로그와 같은 방식으로 API에서 받아 MDX를 런타임 렌더링하고, 갱신은 어드민 저장 웹훅 → /api/revalidate(태그·경로 선택 무효화)로 통일했다

대안포트폴리오는 변경이 잦지 않으니 저장소 안 MDX 파일로 두고 빌드 타임에 정적 생성하는 안근거블로그와 포트폴리오가 서로 다른 콘텐츠 파이프라인을 쓰면 MDX 렌더러·캐시 태그 규칙·프리뷰 처리가 두 벌이 된다. 어차피 packages/mdx와 packages/shared를 공유하는 구조라, 소스만 통일하면 두 앱이 같은 규칙 위에서 돈다.비용변경이 드문 콘텐츠까지 백엔드 가용성에 묶였다. 블로그와 마찬가지로 apiFetch 실패가 예외가 아니라 빈 화면으로 degrade되므로, 포트폴리오가 비어 보이는 상태와 백엔드 장애가 화면상 구별되지 않는다.

사용 기술

TypeScriptNext.jsReactThree.jsFramer Motion@react-pdf/renderer