코드 리뷰 및 최적화
북스토어 코드를 책임·가독성·안전성 기준으로 리뷰하고 이미지·폰트·데이터·번들 성능을 측정해 개선합니다.
실전 프로젝트를 마무리할 때 코드 리뷰와 최적화는 반드시 거쳐야 하는 단계입니다.
기능이 동작하는 수준을 넘어 코드를 더 효율적이고 유지보수하기 쉽게 만들고, 성능까지 끌어올리는 데 직접 기여하기 때문입니다.
이 절에서는 코드 리뷰의 핵심 관점과 일반적인 최적화 기법, 성능 개선 도구 활용법을 다룹니다.
코드 리뷰의 중요성 및 방법
코드 리뷰는 변경된 코드의 정확성, 유지보수성, 성능 영향, 테스트 범위를 검토하는 과정입니다.
리뷰에서는 단순한 스타일보다 요구사항 충족 여부와 회귀 위험을 먼저 확인합니다.
코드 리뷰의 이점
- 버그 조기 발견: 개발 초기 단계에서 논리적 오류나 잠재적 버그를 발견하여 수정 비용을 절감합니다.
- 코드 품질 향상: 가독성, 유지보수성, 재사용성 측면에서 더 나은 코드를 작성하도록 유도합니다.
- 지식 공유 및 팀 역량 강화: 팀원 간에 기술과 모범 사례를 공유하고, 서로의 강점과 약점을 파악하여 학습 기회를 제공합니다.
- 일관성 유지: 코딩 스타일, 아키텍처 패턴 등 프로젝트 전반의 일관성을 유지하는 데 도움이 됩니다.
- 보안 취약점 발견: 잠재적인 보안 취약점을 식별하고 개선합니다.
효과적인 코드 리뷰 방법
- 명확한 목표 설정: 무엇을 중점적으로 볼 것인지(예: 기능 정확성, 성능, 보안, 가독성) 미리 정합니다.
- 작은 단위로 리뷰: 한 번에 너무 많은 코드를 리뷰하지 않도록 합니다. PR(Pull Request) 크기를 작게 유지하는 것이 좋습니다.
- 긍정적인 분위기 조성: 비판보다는 건설적인 피드백에 집중하고, 존중하는 태도로 의견을 교환합니다.
- 체크리스트 활용: 일반적인 코딩 표준, 보안 고려사항, 성능 가이드라인 등을 포함하는 체크리스트를 활용하여 누락 없이 검토합니다.
- 자동화 도구 활용: ESLint, Prettier와 같은 린터(Linter)와 포맷터(Formatter)를 사용하여 기본적인 코드 스타일과 잠재적 오류를 자동으로 검사합니다. 이는 수동 리뷰의 부담을 줄여줍니다.
- 테스트 코드 확인: 관련 테스트 코드가 충분한지, 그리고 테스트가 통과하는지 확인합니다.
Next.js 성능 최적화 기법
Next.js는 기본적으로 빠른 성능을 제공하지만, 복잡한 애플리케이션에서는 추가적인 최적화가 필요합니다.
이미지 최적화 (next/image)
next/image는 이미지 최적화와 크기별 제공, 기본 지연 로딩을 지원합니다. 효과는 이미지·설정·캐시·네트워크에 따라 측정합니다. 아래 preload는 첫 화면의 주요 이미지에 한정한 선택이며 모든 도서 카드에 붙이지 않습니다.
- 적용 방법: 일반
<img>태그 대신next/image를 사용하고src와 의미에 맞는alt를 지정합니다. 이미지 공간은 정적 import가 추론한 크기,width와height, 또는fill중 하나로 확보합니다. -
예시
import Image from 'next/image'; function BookImage({ src, alt }: { src: string; alt: string }) { return ( <Image src={src} alt={alt} width={500} height={600} sizes="(max-width: 768px) 100vw, 500px" style={{ width: '100%', height: 'auto' }} placeholder="blur" blurDataURL="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==" preload /> ); }
폰트 최적화 (next/font)
next/font는 글꼴 파일을 애플리케이션에서 제공하고 폰트 메트릭 조정으로 글꼴 교체 시 레이아웃 이동을 줄일 수 있습니다. next/font/google은 빌드 때 파일을 가져오므로 브라우저가 Google에 직접 요청하지 않지만, 자체 호스팅된 폰트 다운로드는 여전히 발생합니다. 모든 원인의 CLS가 없어지는 것은 아닙니다.
- 적용 방법:
next/font/google또는next/font/local에서 폰트를 임포트하여 사용합니다. -
예시
app/layout.tsx import { Inter } from 'next/font/google'; import type { ReactNode } from 'react'; const inter = Inter({ subsets: ['latin'], display: 'swap' }); export default function RootLayout({ children }: { children: ReactNode }) { return ( <html lang="ko" className={inter.className}> <body>{children}</body> </html> ); }
스크립트 최적화 (next/script)
서드파티 스크립트(Google Analytics, 광고 스크립트 등)는 페이지 로딩 성능에 큰 영향을 미칠 수 있습니다.
next/script 컴포넌트를 사용하면 스크립트 로딩 전략을 제어할 수 있습니다.
-
전략
beforeInteractive: hydration 전에 필요한 중요 스크립트로, Root Layout에 배치afterInteractive(기본값): 페이지의 일부 hydration이 진행된 뒤 로드lazyOnload: 브라우저가 유휴 상태일 때 낮은 우선순위로 로드worker: 웹 워커에서 로드하는 실험적 전략이며 App Router에서는 지원하지 않음
-
예시
import Script from 'next/script'; function MyAnalytics() { return ( <> <Script src="https://www.googletagmanager.com/gtag/js?id=GA_MEASUREMENT_ID" strategy="afterInteractive" /> <Script id="google-analytics" strategy="afterInteractive"> {` window.dataLayer = window.dataLayer || []; function gtag(){dataLayer.push(arguments);} gtag('js', new Date()); gtag('config', 'GA_MEASUREMENT_ID'); `} </Script> </> ); }
데이터 페칭 최적화 (캐싱 및 재검증)
Next.js의 캐싱 메커니즘을 이해하고 효율적으로 활용합니다.
- Data Cache: Next.js 15 이후
fetch응답은 기본적으로 캐시되지 않습니다. 반복해서 재사용할 데이터는cache: 'force-cache'로 명시해 캐싱합니다. Next.js 16에서cacheComponents: true를 활성화한 프로젝트라면use cache지시어도 사용할 수 있습니다. - Full Route Cache: 정적으로 렌더링한 라우트의 HTML과 React Server Component payload를 서버에 저장합니다. 개발 모드에서는 페이지를 요청 시점에 렌더링하므로 운영 캐시 동작과 구분해야 합니다.
- Incremental Static Regeneration (ISR): 정적 생성된 페이지는 유효기간이 지난 뒤 다음 요청을 계기로 재검증하거나, 명시적인 on-demand 재검증으로 업데이트할 수 있습니다. 이는 빌드 시간을 단축하면서도 최신 콘텐츠를 제공하는 데 유용합니다.
- App Router에서 ISR:
fetch의revalidate옵션을 사용합니다. 필요할 때 경로는revalidatePath, 태그의 SWR 갱신은revalidateTag(tag, 'max'), Server Action의 즉시 만료는updateTag로 처리합니다. - 예시: 아래 세그먼트 설정은
cacheComponents를 사용하지 않는 이전 캐시 모델용입니다.[id]경로를 ISR로 처리하려면generateStaticParams로 정적 경로를 준비하거나 빈 배열을 반환하는 등 정적 생성 조건도 함께 갖춰야 합니다. 17장 2절의 주석 처리된generateStaticParams를 그대로 둔 채 이 한 줄만 추가해 모든 요청이 캐시된다고 보장하지 않습니다.app/books/[id]/page.tsx export const revalidate = 60; // 60초가 지난 뒤 다음 요청을 계기로 재검증
- App Router에서 ISR:
- Incremental Static Regeneration (ISR): 정적 생성된 페이지는 유효기간이 지난 뒤 다음 요청을 계기로 재검증하거나, 명시적인 on-demand 재검증으로 업데이트할 수 있습니다. 이는 빌드 시간을 단축하면서도 최신 콘텐츠를 제공하는 데 유용합니다.
- Static Assets Cache:
/_next/static아래의 해시 기반 빌드 자산은 장기 캐시에 적합합니다.public파일은 같은 이름으로 교체될 수 있으므로 필요한 캐시 헤더를 배포 환경에서 별도로 정합니다.
페이지 단위 분할과 지연 로딩 설계
Next.js는 페이지 단위 코드 스플리팅을 자동으로 처리합니다.
공유 레이아웃·공통 모듈과 사전 가져오기 경로도 함께 고려해야 하므로, 네트워크 요청이 현재 페이지의 고유 코드에만 한정된다는 뜻은 아닙니다.
추가적으로, 특정 컴포넌트나 라이브러리를 필요할 때만 로드하도록 동적 임포트를 사용할 수 있습니다.
next/dynamic: 클라이언트 컴포넌트나 무거운 라이브러리를 레이지 로드하는 데 사용합니다.-
예시
src/components/DashboardChart.tsx (클라이언트 컴포넌트) "use client"; // 무거운 차트 라이브러리를 동적으로 로드 import dynamic from 'next/dynamic'; import Loading from './Loading'; const DynamicChart = dynamic(() => import('../components/Chart'), { loading: () => <Loading />, // 로딩 중 표시할 컴포넌트 ssr: false, // 이 컴포넌트는 클라이언트에서만 렌더링 (SSR 제외) }); export default function MyDashboard() { return ( <div> <DynamicChart /> </div> ); }
성능 측정 및 디버깅 도구
성능 질문에 맞는 측정 근거
| 대상 | 먼저 볼 근거 | 이것만으로 알 수 없는 것 |
|---|---|---|
| 첫 화면 지연 | Lighthouse의 실험실 LCP·CLS·TBT | 실제 사용자의 모든 상호작용 지연 |
| 실행 원인 | DevTools Performance의 작업·렌더링 기록 | 전체 사용자 집단의 분포 |
| 실사용 환경 | Speed Insights 등의 실제 사용자 지표 | 느린 함수의 구체적인 실행 원인 |
| 번들 크기 | 사용한 번들러의 모듈별 분석 | 모듈 크기만으로 실제 실행 시간 |
- 대상: 첫 화면 지연
- 먼저 볼 근거: Lighthouse의 실험실 LCP·CLS·TBT
- 이것만으로 알 수 없는 것: 실제 사용자의 모든 상호작용 지연
- 대상: 실행 원인
- 먼저 볼 근거: DevTools Performance의 작업·렌더링 기록
- 이것만으로 알 수 없는 것: 전체 사용자 집단의 분포
- 대상: 실사용 환경
- 먼저 볼 근거: Speed Insights 등의 실제 사용자 지표
- 이것만으로 알 수 없는 것: 느린 함수의 구체적인 실행 원인
- 대상: 번들 크기
- 먼저 볼 근거: 사용한 번들러의 모듈별 분석
- 이것만으로 알 수 없는 것: 모듈 크기만으로 실제 실행 시간
같은 페이지·기기·네트워크 조건에서 변경 전후를 비교합니다. 이 표는 도구의 범위를 설명하며 북스토어의 측정 결과를 담지 않습니다.
Lighthouse 기준선 진단
- 용도: 성능, 접근성, 권장 사항, SEO를 진단합니다. PWA 카테고리는 Lighthouse 12에서 제거되었으므로 설치·서비스 워커 동작은 별도로 확인합니다.
- 사용법: Chrome 개발자 도구에서 Lighthouse 탭을 클릭하고 Analyze page load를 실행합니다.
- 활용: Lighthouse 로딩 검사에서 LCP·CLS·TBT 등을 확인합니다. 사용자 입력이 없는 한 번의 로딩 검사로 INP를 측정하지는 못하므로 상호작용 기록이나 실제 사용자 지표를 함께 사용합니다.
Chrome DevTools (성능 탭, 네트워크 탭)
- Performance 탭: 런타임 성능을 기록하고 분석하여 JavaScript 실행, 렌더링, 레이아웃 계산 등에서 발생하는 병목 현상을 시각적으로 파악합니다.
- Network 탭: 페이지 로딩 시 발생하는 모든 네트워크 요청(JS, CSS, 이미지, API 호출)을 분석하고, 각 리소스의 크기, 로딩 시간, 캐싱 여부 등을 확인합니다. 이를 통해 불필요한 요청이나 큰 리소스를 식별할 수 있습니다.
Vercel Analytics / Speed Insights
Vercel은 배포된 Next.js 애플리케이션에 대한 내장 분석 및 성능 지표를 제공합니다.
- Vercel Analytics: 방문자 수, 페이지 뷰, 트래픽 소스 등 일반적인 웹사이트 분석 데이터를 제공합니다.
- Speed Insights: 실제 사용자 데이터(RUM - Real User Monitoring)를 기반으로 Core Web Vitals를 포함한 페이지 성능 지표를 모니터링합니다. Vercel 대시보드에서 기능을 활성화하고
@vercel/speed-insights를 설치한 뒤 Root Layout에<SpeedInsights />를 렌더링해야 데이터를 수집합니다.
번들 분석기 (Bundle Analyzer)
@next/bundle-analyzer는 Webpack 빌드용 도구입니다. 아래 예제는 Next.js 16에서도 --webpack으로 빌드할 때 적용합니다. Turbopack을 분석할 때는 설치된 Next.js가 제공하는 별도 분석 기능을 사용하며 이 플러그인의 보고서와 섞지 않습니다.
어떤 모듈이 가장 많은 공간을 차지하는지 파악하여 불필요한 코드나 큰 라이브러리를 식별하고 제거할 수 있습니다.
- 설치:
npm install --save-dev @next/bundle-analyzer next.config.js설정: CommonJS 형식의 예시입니다. 기존next.config.ts등과 두 설정 파일을 함께 두지 말고, 선택한 형식의 한 파일에 기존 설정을 보존해 결합합니다.next.config.js const withBundleAnalyzer = require('@next/bundle-analyzer')({ enabled: process.env.ANALYZE === 'true', }); module.exports = withBundleAnalyzer({ // Next.js config options });- macOS·Linux에서 실행:
ANALYZE=true npm run build -- --webpack - PowerShell에서 실행:
$env:ANALYZE='true'; npm run build -- --webpack
빌드가 끝나면 번들 분석 보고서가 웹 브라우저에 열립니다.
변경 범위와 해결하려는 병목을 연결하고, 수정 뒤에는 해당 기능과 같은 조건의 측정값을 함께 확인합니다.