Next.js 최적화 완전 정복: 이미지부터 캐싱까지
📺 원본 영상: 바이브 코더가 알아듣기 쉽게 최대한 얕게 떠봤습니다. 20분 컷 NextJS 최적화 강의.
🎙️ 채널: 코드팩토리
📅 정리 일자: 2026-02-12
⚠️ Note: 본 리포트는 영상의 음성을 텍스트로 변환(STT)하여 정리한 것입니다. 화면에 표시된 자료(차트, 코드, 슬라이드 등)는 포함되지 않을 수 있습니다.
개요
Next.js를 사용하면서 성능 최적화를 고민하는 개발자들을 위한 실전 가이드다. 강연자는 라이브 방송에서 다룬 내용을 재정리하여, 이미지 최적화, 렌더링 전략, 코드 분할, 스크립트 로딩, 데이터 캐싱이라는 다섯 가지 핵심 최적화 영역을 다룬다. 특히 “바이브 코더”(초보 개발자)도 이해할 수 있도록 깊이보다는 실용성에 초점을 맞추었으며, CRCD(Create, Read, Update, Delete)와 같은 기본적인 내용은 제외하고 최적화의 핵심만 빠르게 전달한다.
시작 전 필수 체크: App Router를 사용하라
Next.js 버전 16부터는 App Router가 기본 설정이다. 강연자는 특별한 이유가 없다면 Page Router로 전환하지 말고 App Router를 그대로 사용할 것을 강력히 권장한다. App Router는 Next.js의 최신 기능들을 최대한 활용할 수 있는 구조이며, 이후 설명할 최적화 기법들도 대부분 App Router 환경을 전제로 한다.
1. 이미지 최적화: 네 가지 문제를 한 번에 해결하는 방법
일반 <img> 태그의 문제점
전통적인 HTML의 <img> 태그를 사용하면 네 가지 주요 문제가 발생한다. 첫째, 사용자가 업로드한 원본 이미지(예: 4000픽셀, 10000픽셀)를 그대로 서빙하게 되어 데이터 낭비와 속도 저하가 발생한다. 둘째, WebP 같은 최신 포맷을 자동으로 적용할 수 없다. 셋째, 화면에 보이지 않는 이미지도 페이지 접속 즉시 다운로드를 시작하여 성능이 저하된다. 넷째, 이미지가 로드되면서 텍스트가 아래로 밀리는 CLS(Cumulative Layout Shift) 현상이 발생한다. 이는 특히 오래된 웹사이트에서 자주 경험할 수 있는 문제로, 먼저 글자가 표시되다가 갑자기 이미지가 생기면서 레이아웃이 틀어지는 현상이다.
Next.js Image 컴포넌트의 해법
Next.js에서 제공하는 Image 컴포넌트를 사용하면 이 모든 문제를 자동으로 해결할 수 있다. next/image에서 Image 컴포넌트를 불러와 사용하면, 모바일 접속 시 640픽셀짜리 이미지가 필요할 때 원본이 5000픽셀이더라도 자동으로 640픽셀로 축소하여 제공한다. 이는 실제 파일 크기와 용량을 모두 줄여준다. WebP와 AVIF 포맷을 지원하는 브라우저에서는 자동으로 해당 포맷으로 변환하여 제공하며, 스크롤이 이미지까지 도달했을 때 비로소 레이지 로딩(Lazy Loading)을 수행하여 초기 성능을 향상시킨다. 또한 width와 height 정보를 기반으로 플레이스홀더를 미리 확보해두기 때문에 이미지 로딩 전에도 레이아웃이 틀어지지 않는다.
💡 [영상 참조] 강연자는 이 시점에서
<img>태그와 Next.js Image 컴포넌트의 비교 테이블을 화면에 표시하며 설명합니다. 상세 내용은 원본 영상을 확인하세요.
Next.config.js 설정의 중요성
Image 컴포넌트를 제대로 활용하려면 next.config.js에서 외부 이미지 소스를 등록해야 한다. Next.js는 기본적으로 외부 소스의 이미지를 차단하도록 설계되어 있는데, 이는 보안을 위한 조치다. 예를 들어 Unsplash 같은 외부 서비스에서 이미지를 불러오려면 해당 도메인을 명시적으로 허용해주어야 한다. 또한 포맷과 사이즈를 직접 최적화할 수 있는 옵션도 제공된다.
비용 구조와 캐싱 전략
Image 컴포넌트는 Vercel의 기능을 사용하므로, 이미지 호스팅을 Supabase나 S3에서 하더라도 Vercel의 비용이 별도로 발생한다. 아키텍처를 살펴보면, 처음 요청 시 Supabase에서 이미지를 가져오는 비용이 발생하고, Vercel에서 최적화하는 비용이 추가로 발생한다. 그러나 최적화된 이미지가 캐싱되면 이후에는 캐시를 읽어오는 비용만 발생하며, 이 경우 Supabase 쪽 비용은 발생하지 않는다. Vercel의 무료 한도는 월 5000건의 트랜스포메이션(변환), 30만 건의 캐시 읽기, 10만 건의 캐시 쓰기다. 강연자는 비용 절감을 위해 캐시 시간을 매우 길게 설정할 것을 권장한다. UUID를 사용하여 이미지를 유니크하게 관리한다면 같은 이미지를 반복해서 생성할 필요가 없으므로, 캐시 타임을 늘리는 것이 비용 절감에 큰 도움이 된다.
이미지 최적화 아키텍처의 역사적 의미
Vercel이 이러한 이미지 최적화 아키텍처를 제공하기 전에도, 개발자들은 S3와 Lambda를 사용하여 유사한 구조를 직접 설계했다. 사용자가 mycat.jpg?format=webp&width=200과 같은 쿼리 파라미터를 포함한 요청을 보내면, 먼저 CDN 캐시를 확인하고, 캐시에 없으면 S3 버킷을 확인한다. 해당 포맷과 크기의 이미지가 버킷에 없으면 Lambda 함수가 원본 이미지를 기반으로 새로운 이미지를 생성하여 버킷에 저장하고, 이를 반환한다. 한 번 생성된 이미지는 계속 캐시 히트되므로 반복 생성이 불필요해진다. Vercel의 Image 컴포넌트는 이러한 아키텍처가 이미 적용된 것이라 볼 수 있다.
점검 프롬프트
강연자는 AI 도구를 활용하여 이미지 최적화 상태를 점검할 수 있는 프롬프트를 제안한다. 중요한 패턴은 next.config.js를 확인하고, 이미지 포맷을 확인하며, Image 컴포넌트를 사용했는지 확인하는 것이다.
2. 렌더링 전략: Next.js 최적화의 핵심
렌더링 전략은 Next.js를 효율적으로 만들 수 있는 가장 중요한 요소다. 강연자는 이를 “가장 적은 노력으로 가장 많은 최적화를 할 수 있는 영역”이라고 강조한다.
CSR vs SSR: 큰 프레임의 이해
전통적인 React를 포함한 SPA(Single Page Application)는 보통 CSR(Client-Side Rendering) 방식을 사용한다. 이는 빈 HTML을 먼저 보내고 브라우저에서 JavaScript를 통해 동적으로 화면을 만들어내는 방식이다. 이 방식은 SEO에 불리하고 초기 로딩이 느리다는 단점이 있다. 반면 SSR(Server-Side Rendering)은 서버에서 HTML을 생성하여 보내는 방식으로, 요청마다 매번 새로 렌더링한다. 즉, 천만 번의 요청이 들어오면 천만 번 동일한 페이지를 새로 생성하는 것이 SSR의 기본 작동 방식이다. SSR은 초기 로딩 속도가 빠르고 SEO에 유리하지만, 서버 부하가 높다는 단점이 있다.
SSG: 미리 만들어두고 재사용하라
SSG(Static Site Generation)는 HTML 파일을 미리 만들어두고 캐싱하여, 매번 동일한 파일을 제공하는 방식이다. 빌드 시 서버에서 한 번 생성(SSR)하고, 그 이후에는 생성된 콘텐츠를 재사용한다. 캐싱 기간은 초 단위로 설정할 수 있으며, 1초로 설정하면 1초 동안의 모든 요청에 대해 한 번만 생성하고 나머지는 캐시된 페이지를 제공한다. SEO는 당연히 우수하고, 서버 부하는 빌드 시에만 발생하므로 거의 없다고 볼 수 있다. 이 방식은 약관, 문서 등 잘 변하지 않는 콘텐츠에 적합하며, 업데이트가 필요하면 빌드를 다시 실행하면 된다.
ISR: SSG의 진화형
ISR(Incremental Static Regeneration)은 SSG의 단점을 보완한 방식이다. 미리 만들어둔 HTML 파일을 주기적으로 재생성하여, 오래된 데이터 문제를 해결한다. 빌드 시가 아니라 서버 실행 중에도 지속적으로 페이지를 재생성하므로, 초기 로딩 속도는 빠르면서도 데이터 신선도를 유지할 수 있다. 블로그, 뉴스 기사, 상품 상세 페이지 등에 적합하며, 한 시간마다 업데이트하는 등의 설정이 가능하다. 서버 부하는 SSG보다 약간 높지만, SSR에 비하면 훨씬 낮다.
PPR: 정적과 동적의 결합 (실험 단계)
PPR(Partial Pre-Rendering)은 정적 콘텐츠와 동적 콘텐츠를 혼합하는 방식이다. 정적인 껍데기를 먼저 보내고, 그 안에서 Suspense를 활용하여 서버로부터 지속적으로 데이터를 스트리밍 받아 업데이트하는 형태다. 하지만 강연자는 바이브 코더에게는 PPR을 아직 사용하지 않을 것을 권장한다. 현재 Experimental(실험) 기능이며 완성도가 높지 않기 때문이다.
렌더링 전략 비교 요약
| 전략 | 렌더링 시점 | 초기 로딩 속도 | SEO | 서버 부하 | 적합한 사례 |
|---|---|---|---|---|---|
| CSR | 브라우저 | 느림 | 불리 | 낮음 | 관리자 페이지, SEO 불필요 |
| SSR | 서버 (요청마다) | 빠름 | 우수 | 높음 | 실시간 데이터 (주식 차트), 매우 동적인 페이지 |
| SSG | 빌드 시 (한 번) | 매우 빠름 | 최상 | 거의 없음 | 약관, 문서, 정적 콘텐츠 |
| ISR | 서버 (주기적) | 빠름 | 최상 | 낮음 | 블로그, 뉴스, 상품 상세 페이지 |
| PPR | 하이브리드 | 빠름 | 우수 | 중간 | 정적+동적 혼합 (실험 단계) |
CSR이 관리자 페이지에 적합한 이유는 SEO가 필요 없기 때문이다. 이미 확보한 사용자들이며, 페이지가 조금 느리더라도 이탈할 사용자들이 아니므로 굳이 서버 부하를 높일 필요가 없다. 반면 SSR은 주식 차트처럼 완전히 실시간이어야 하는 경우에 적합하다.
서버 컴포넌트와 클라이언트 컴포넌트
Next.js의 App Router에서는 RSC(React Server Component) 개념을 이해해야 한다. 서버 컴포넌트는 코드가 서버에서만 실행되며, 첫 콘텐츠를 빠르게 그릴 수 있고 클라이언트로 보낼 JavaScript 양을 줄일 수 있다. App Router에서는 서버 컴포넌트가 디폴트다. 클라이언트 컴포넌트는 파일 상단에 "use client" 디렉티브가 있으며, 브라우저에서 실행된다. 중요한 점은, "use client"를 단순히 지운다고 해서 서버 컴포넌트가 되는 것이 아니라는 것이다. 클라이언트 사이드에서만 사용할 수 있는 훅(hook)들이 있기 때문에, 함부로 디렉티브를 제거하면 오류가 발생한다.
코드 예시를 통한 이해
💡 [영상 참조] 강연자는 이 부분에서 화면에 코드를 표시하며 설명합니다. 상세 코드는 원본 영상을 확인하세요.
App Router에서 generateStaticParams 함수를 사용하면 어떤 페이지들을 미리 생성할지 결정할 수 있다. 이 함수가 반환하는 파라미터 값들을 기반으로 빌드 시 해당 페이지들이 미리 만들어지며, 사용자 요청 시 만들어진 페이지를 직접 서빙한다.
ISR을 적용하려면 페이지 컴포넌트에 revalidate 옵션을 추가하면 된다. 예를 들어 export const revalidate = 60을 설정하면 60초마다 페이지가 자동으로 재생성된다. 대부분의 실무 사례에서는 순수 SSG보다 ISR을 더 많이 사용한다.
점검 프롬프트
AI 도구에 프롬프트를 작성할 때는 SSG, ISR, RSC(React Server Component), PPR 등의 키워드를 명확히 넣어주는 것이 중요하다. 또한 “PPR은 웬만하면 적용하지 않는 방향으로”라는 조건을 명시해야 한다. 리포트를 요청할 때는 “어떤 파일에 어떤 방식이 적용되어 있고, 어떤 방식으로 마이그레이션하면 좋을지, 그리고 왜 그렇게 바꿔야 하는지”를 자연어로 설명해달라고 요청하면, 코딩을 잘 모르더라도 쉽게 이해할 수 있다.
3. 코드 스플리팅: 필요한 것만 로드하라
코드 스플리팅은 동적 임포트(Dynamic Import)를 활용하여 필요한 라이브러리만 로드하는 기법이다. 예를 들어 Lodash 라이브러리를 전통적인 방식으로 불러오면, 단 하나의 함수만 사용하더라도 라이브러리 전체를 불러오게 된다. 반면 동적 임포트를 사용하면 실제로 사용하는 부분만 선택적으로 로드할 수 있어 훨씬 효율적이다.
실전 적용 사례
강연자가 제시한 가장 좋은 예시는 무거운 텍스트 에디터를 사용할 때다. Lexical, TipTap 같은 에디터는 패키지 크기가 크기 때문에, 한 번에 모두 불러오면 첫 렌더링이 매우 느려진다. 이런 경우 동적 임포트로 에디터를 로드하면 사용자에게 “조금 더 빠른 것 같은 착각”을 줄 수 있다. 실제로 체감 성능이 개선되는 것이다. 유사한 사례로는 매우 무거운 차트 라이브러리나 대형 에디터 컴포넌트를 들 수 있다.
과최적화의 경계
다만 강연자는 이 기법이 “과최적화의 영역”에 들어갈 수 있다고 경고한다. 바이브 코더나 작은 프로젝트에서는 모든 임포트를 동적으로 바꿀 필요는 없으며, 정말 무거운 라이브러리에만 선택적으로 적용하는 것이 현명하다.
점검 프롬프트
AI 도구에 “동적 임포트”와 “Tree-shaking”이라는 키워드를 넣고, 어떤 파일에 적용하면 좋을지 테이블 형식으로 리포트를 요청하면 된다.
4. 번들 애널라이저: 시각화로 최적화 지점 찾기
Bundle Analyzer는 번들의 크기를 시각적으로 보여주는 도구다. 강연자는 자신의 프로젝트에서 실제로 실행한 예시를 공유했는데, 모듈별로 차지하는 공간이 크기에 비례하여 표시되므로, 어떤 모듈이 번들의 많은 부분을 차지하고 있는지 한눈에 파악할 수 있다. 이를 통해 불필요하게 큰 의존성을 찾아내거나, 코드 스플리팅이 필요한 지점을 발견할 수 있다.
설치와 실행은 매우 간단하며, AI 도구에 “번들 애널라이저 설치해서 실행해 달라”고 요청하면 몇 줄의 코드로 금방 처리된다.
5. 스크립트 로딩 전략: 중요한 것부터 먼저
외부 스크립트(Google Analytics, Google Tag Manager 등)를 로드할 때도 최적화가 가능하다. 일반 <script> 태그 대신 Next.js의 Script 컴포넌트를 사용하면 strategy 옵션을 통해 로딩 우선순위를 조절할 수 있다.
예를 들어 strategy="afterInteractive"로 설정하면, HTML이 모두 표시되고 Hydration(JavaScript 기본 값 주입)이 완료된 직후에 스크립트를 로드한다. Google Analytics 같은 경우 조금 늦게 로딩되어도 문제가 없으므로, 이런 식으로 로딩 순서를 조정하여 첫 화면 렌더링 속도를 높일 수 있다.
강연자는 이 부분을 “Google Analytics 외에는 바이브 코더가 쓸 일이 거의 없을 것”이라고 언급하며, 실용성 측면에서 선택적 적용을 권장한다.
6. 데이터 캐싱: 과최적화의 영역
데이터 캐싱은 “과최적화의 영역”에 속한다고 강연자는 강조한다. 대부분의 캐싱은 앞서 설명한 SSG나 ISR 같은 페이지 단위 캐싱으로 충분하다. 하지만 여러 페이지에 걸쳐 동일한 데이터를 요청하거나, 한 페이지 내에서 여러 쿼리를 최적화하고 싶을 때는 unstable_cache나 TanStack Query 같은 라이브러리를 활용할 수 있다.
서버 사이드: unstable_cache
예를 들어 getUser()라는 함수를 layout.tsx와 page.tsx에서 각각 호출하면, 동일한 데이터를 가져오는데도 두 번 요청이 발생한다. 이는 비효율적이다. unstable_cache로 함수를 래핑하면 첫 번째 실행 시 데이터베이스를 조회하고, 두 번째 실행 시에는 메모리 캐시에서 데이터를 반환하여 중복 요청을 방지한다.
unstable_cache는 세 가지 파라미터를 받는다. 첫째는 실행할 비동기 함수, 둘째는 캐시 키(이 조합으로 캐시를 식별), 셋째는 재검증 시간과 태그 설정이다. 강연자는 이 기능이 버전 17~18에서 Deprecated될 것으로 예상하지만, 대체 기능도 개념은 동일하므로 알아두면 유용하다고 말한다.
대표적인 사용 사례는 OG(Open Graph) 데이터 생성이다. 페이지를 만들 때와 OG 데이터를 만들 때 동일한 데이터베이스 접근이 발생한다면, unstable_cache로 래핑하여 한 번만 요청하도록 최적화할 수 있다.
클라이언트 사이드: TanStack Query
프론트엔드 관점에서는 TanStack Query(구 React Query)나 TRPC 같은 라이브러리를 사용하여 동일한 개념을 적용할 수 있다. 키 값을 기준으로 캐싱하고, Revalidate 시간을 설정하는 방식은 서버 사이드와 유사하다.
캐싱 사용 시 주의사항
강연자는 캐싱 적용 시 반드시 “어디에 어떤 캐시를 적용했는지” 명확히 메모해둘 것을 당부한다. 캐싱은 특정 시점의 스냅샷을 저장하는 개념이므로, 라이브 데이터가 아니다. 따라서 실제로는 버그가 아닌데 버그라고 착각하는 상황이 생길 수 있다. 본인뿐만 아니라 AI 도구의 컨텍스트에도 이 정보가 명확히 들어가도록 정리해야, 나중에 특정 현상이 왜 발생하는지 이해할 수 있다.
점검 프롬프트
“Memoization”, “Request Memoization”, “Data Cache”, “Cache 무효화”라는 세 가지 키워드를 3위 일체로 묶어서 프롬프트에 포함하고, 테이블 형식으로 어떤 마이그레이션이 필요하고 어떤 장점이 있는지 정리해달라고 요청하면 된다.
마무리: 최적화의 핵심은 캐싱 관리다
오늘 다룬 다섯 가지 최적화 기법(이미지 최적화, 렌더링 전략, 코드 스플리팅, 스크립트 로딩, 데이터 캐싱) 외에도 Next.js에서 할 수 있는 최적화는 훨씬 더 많다. 하지만 과최적화로 가면 오히려 복잡도만 증가하고 유지보수가 어려워진다. 강연자는 특히 캐싱을 적용할 때 “어디에 어떤 부분이 적용되었는지” 정확히 메모를 남겨야 한다고 강조한다. 본인도 알고, AI도 이해할 수 있도록 문서화하는 것이 필수다. 캐싱은 라이브 데이터가 아니므로, 관리가 제대로 되지 않으면 예상치 못한 문제가 발생할 수 있다.
이번 강연에서 다룬 개념들은 Next.js를 예시로 했지만, 대부분의 프레임워크에서 유사한 기능을 제공한다. 만약 지원하지 않더라도, 앞서 설명한 이미지 최적화 아키텍처처럼 직접 설계할 수 있는 표준적인 패턴들이다. 이러한 개념을 잘 이해하고 있으면 AI와 함께 코딩할 때 훨씬 더 효과적으로 협업할 수 있다.
📚 출처 및 참고
| 구분 | 내용 |
|---|---|
| 원본 영상 | 바이브 코더가 알아듣기 쉽게 최대한 얕게 떠봤습니다. 20분 컷 NextJS 최적화 강의. |
| 채널 | 코드팩토리 |
| 관련 라이브 | 2024년 12월 11일 라이브 방송 풀 버전 |
| 리포트 작성 | 2026-02-12 |
| 작성 방식 | YouTube 영상 STT 변환 후 에디팅 |
💬 본 리포트는 영상 내용을 바탕으로 재구성되었습니다. 정확한 내용은 원본 영상을 참조해 주세요.