들어가며
프론트엔드 개발을 시작한 지 1년도 채 되지 않아 인턴으로 경험을 쌓을 수 있었던 건, 이정환 님의 한입 시리즈 강의 덕분이었습니다.
개발자로 진로를 정한 직후 동료 개발자의 추천으로 리액트, 타입스크립트, 넥스트 강의를 차례로 수강하며 혼자 공부하며 느꼈던 막막함을 친절한 설명과 실습 위주의 강의가 크게 덜어주었고 덕분에 빠르게 성장할 수 있었습니다.
감사한 마음을 보답하고 싶던 중, 서평단에 선정되는 기회를 얻게 되어 이렇게 리뷰를 남기게 되었습니다.
편의를 위해 평서체로 작성하겠습니다!
책 소개
책의 구성과 학습 흐름
책은 Next.js의 기초부터 실무적인 활용까지 자연스럽게 이어지도록 구성되어 있다.
특히 개념을 도식화한 그림과 다이어그램이 풍부해 이해하기 쉬웠고, “왜 Next.js를 사용하는가?”라는 질문에 스스로 답할 수 있을 만큼 핵심을 짚어준다.
각 장의 내용을 간략히 정리하면 다음과 같다.
1장 Next.js 소개와 실습 환경 구축
2장 페이지 라우터 버전 시작하기
3장 페이지 라우터 활용하기
4장 앱 라우터 버전 시작하기
5장 앱 라우터 데이터 패칭
6장 페이지 캐시
7장 스트리밍
8장 서버 액션
9장 고급 라우트 기법
10장 최적화 및 배포
총 10장으로 구성되어 있으며, 더 자세한 내용은 책을 직접 확인해 보길 추천한다..💓
이 책에 작성된 모든 실습 예제는 저자의 깃허브 저장소에 업로드되어 있으며, 인프런 강의를 통해 함께 실습할 수도 있다. 또한 학습 중 모르는 부분은 카카오톡 오픈채팅방 (비밀번호: wlreact), 네이버 카페 등을 통해 질문할 수 있도록 커뮤니티가 운영되고 있다.
책의 특징
실습 위주의 학습
책에서는 한입북스 프로젝트를 직접 만들어 보며 다양한 기능을 경험할 수 있도록 이끌어 준다. 한입북스는 총 3개의 페이지로 구성된 간단한 도서 조회 사이트이다.
- 인덱스 페이지: 검색 폼, 추천 도서, 전체 도서 리스트
- 검색 페이지: 검색 결과 렌더링
- 상세 페이지: 선택한 도서 아이템 렌더링
이 과정에서 네트워크 탭을 확인하며 데이터가 어떻게 불러와지는지, JS 번들이 어떤 방식으로 로딩되는지를 직접 눈으로 확인할 수 있었다. 예를 들어, 앱 라우터 버전에서는 모든 컴포넌트가 기본적으로 서버 컴포넌트로 설정된다는 개념을 설명하며, 이를 검증하기 위해 브라우저 개발자 도구에서 console.log('페이지 컴포넌트');를 작성해 보았다. 그러면 메시지 앞에 Server라는 태그가 붙어 표시되는데, 이는 해당 메시지가 서버에서 출력된 것임을 의미한다!
또한 독자가 더 깊이 공부할 수 있도록 React와 Next.js 공식 문서 링크를 곳곳에 첨부해 둔 것도 넘 센스있다..!
사전 렌더링 방식에 대한 명확한 이해
Next.js의 가장 큰 장점 중 하나는 사전 렌더링 기능이다. 사전 렌더링 방식은 3가지로 나뉜다.
-
SSR(Server Side Rendering) 서버 사이드 렌더링
: 브라우저의 접속 요청을 받을 때마다 매번 새롭게 사전 렌더링을 진행하는 방식이다. 서버에서 페이지를 미리 생성하는 방식으로 동작하기 때문에 백엔드 서버에서 데이터를 미리 불러올 수 있으므로 빠른 페이지 렌더링이 가능하다. 이는 언제나 최신 데이터가 포함된 HTML을 받으므로 최신 데이터의 반영이 중요한 페이지에 좋지만, 요청이 들어올 때마다 페이지를 새롭게 생성하는 과정이 오래 걸리면 서버에서 부하가 발생하거나 페이지의 응답속도가 늦어지는 단점이 존재한다.
⚠ 이 때, SSR 설정 시 주의사항이 있다.
getServerSideProps 는 Next.js 서버에서만 실행되므로 이 함수에는 브라우저 환경에서 동작하는 코드를 작성하면 안된다. 예를 들어, window 객체에 접근하려고 하면 오류가 발생한다! 인턴 때도 window.??.?? 로 접근하려다가 에러가 발생했던 기억이 있다.. 이 문제를 해결하려면? useEffect문 등을 사용해 브라우저에서만 window 객체에 접근하도록 코드를 작성해야 한다. -
SSG(Static Site Generation) 정적 사이트 생성
: 프로젝트의 빌드타임에 미리 사전렌더링을 진행하는 방식이다. 최신 데이터 반영에는 불리하지만, 미리 생성한 페이지를 바로 반환하므로 응답 속도가 매우 빠르다. 예를 들어, 쇼핑몰 관리자가 상품 정보 데이터를 수정하거나 사용자가 새 리뷰를 추가하더라도 SSG로 생성한 페이지는 이 데이터를 반영할 수 없다. -
ISR(Incremental Static Regeneration) 중분 정적 재생성
: SSG 방식으로 빌드 타임에 생성한 정적 페이지를 주기적으로 다시 생성하여 최신 데이터를 반영하는 방식이다. 빠른 페이지 응답과 동시에 최신 데이터를 반영할 수 있어 1번과 2번의 장점을 모두 취한다.
SSR, SSG, ISR의 차이를 표로 정리하면 다음과 같다.
방식 | 동작 방식 | 장점 | 단점 |
---|---|---|---|
SSR (서버 사이드 렌더링) | 요청 시마다 새 페이지 생성 | 최신 데이터 보장 | 서버 부하, 응답 지연 |
SSG (정적 사이트 생성) | 빌드 시 페이지 미리 생성 | 응답 속도 매우 빠름 | 최신 데이터 반영 불가 |
ISR (증분 정적 재생성) | 일정 주기로 정적 페이지 갱신 | 빠른 응답 + 최신 데이터 반영 | 데이터 반영이 약간 지연 |
사전 렌더링 방식과 각각의 특징
단순히 이론에 그치지 않고, 실제 프로젝트에서 어떤 방식을 선택해야 하는지 기준을 세우는 데 큰 도움이 된다.
또한, Next.js는 아래와 같은 장점을 가진다.
-
빠른 초기 로딩(FCP 개선)
React는 CSR(Client Side Rendering) 방식으로 동작하기 때문에 페이지 이동은 빠르지만, 초기 접속 속도(FCP: First Contentful Paint, 사용자가 처음으로 콘텐츠를 보게 되는 시점)가 느려지는 단점이 있다. 하지만 Next.js는 사전 렌더링을 통해 이 문제를 개선하여 더 빠른 초기 로딩을 제공한다. -
쾌적한 페이지 이동
클라이언트 측 라우팅을 활용해 페이지 전환 시마다 서버에 새롭게 데이터를 요청할 필요가 없어, 자연스럽고 부드러운 이동 경험을 제공한다. -
프리페칭 및 코드 스플리팅
코드 스플리팅을 적용하면 각 페이지별로 JS 번들이 분리되어, 현재 접근한 페이지에 필요한 번들만 받아오게 된다. 이를 통해 초기 로딩 시 가져와야 할 번들 크기를 줄일 수 있고, 결과적으로 TTI(Time To Interactive, 페이지가 실제로 상호작용 가능한 상태가 되기까지 걸리는 시간)를 단축할 수 있다. 다만 페이지 단위로 분리된 번들은 새로운 페이지로 이동할 때마다 추가 로드가 필요하므로, 이 과정에서 지연이 발생할 수 있다. 이를 보완하기 위해 Next.js는 사용자가 페이지를 이동하기 전에 필요한 리소스를 미리 가져오는 프리페칭(prefetching) 기능을 제공한다.
결국 사용자 경험, 데이터의 변동성, 성능 요구 사항 등을 종합적으로 고려해 상황에 맞는 방식을 선택하는 것이 중요하다!
개념 리마인드
- 그룹 라우팅: 경로에 영향을 미치지 않는 특정 폴더를 생성하여 경로가 다른 여러 페이지를 하나의 폴더로 묶어주는 기능이다. 실제로 내가 진행 중인 중고거래 프로젝트에서도 사용중이었지만, 정확한 개념을 모르고 단순히 "여기에 Navbar가 들어가나 보다" 정도로만 이해하고 있었는데, 이번에야 비로소 그 구조와 의미를 제대로 파악하게 되었다.
❗ 여기서 문제
이 책을 읽고나면 아래 질문에 자신 있게 답할 수 있을 것이다!!!
- 서버 컴포넌트는 서버에서만 실행된다? → Yes
- 클라이언트 컴포넌트는 브라우저에서만 실행된다? → No
서버 + 브라우저 모두에서 실행된다.
클라이언트 컴포넌트는 서버 컴포넌트가 아니라 일반적인 리액트 컴포넌트이므로 초기 접속 요청이 있으면 사전 렌더링 과정에서 서버에서 먼저 실행된 다음, 이후 자바스크립트 번들에 포함되어 브라우저에 전달된다. 그리고 하이드레이션 과정에서 이 컴포넌트들이 브라우저에서 다시 한 번 실행되므로 총 2번 실행된다. 즉 서버에서 1번, 브라우저에서 1번 총 2번 실행되므로 브라우저와 서버 모두에게서 한 번씩 실행된다. - 클라이언트 컴포넌트에서 서버 컴포넌트를 불러올 수 없다? → Yes
- 서버 컴포넌트는 사전 렌더링 과정에서 한 번만 실행된다? → Yes
앱 라우터와 페이지 라우터 버전의 중요한 차이
캐시 동작 방식
페이지 라우터는 SSG, SSR 방식처럼 오직 페이지 단위로만 캐시할 수 있었지만, 앱 라우터는 독립적으로 동작하는 캐시 기능을 조합해 페이지 내부의 특정 컴포넌트나 API 요청 결과까지도 개별적으로 유연하게 최적화할 수 있다.
Suspense와 로딩 처리
Suspense를 이용한 스트리밍 설정 챕터(p.365)를 읽다가 문득 궁금증이 생겼다.
“로딩 상태를 굳이 Suspense
로 구현해야 할까? 그냥 Loading.tsx
같은 로딩 컴포넌트를 만들어서 스피너를 보여주면 되지 않나?”
하지만 이 의문은 절반만 맞는 생각이었다. Suspense를 사용하더라도 Loading 컴포넌트는 여전히 필요하기 때문이다. 다만 차이가 있다면, 이를 컴포넌트 외부로 분리해 보다 선언적으로 다룰 수 있다는 점이다.
이 질문을 멘토 같은 동료 개발자에게 던졌더니, “아주 좋은 질문”이라는 칭찬과 함께 참고할 만한 영상을 하나 추천받았다. 바로 토스ㅣSLASH 21 - 프론트엔드 웹 서비스에서 우아하게 비동기 처리하기였다.
영상에서 말하는 좋은 코드란, 성공·실패의 경우를 분리해 처리할 수 있고 비즈니스 로직을 한눈에 파악할 수 있는 코드였다. 흔히 다음과 같이 상태를 직접 관리해서 로딩을 처리하곤 한다.
// RecoBooks.tsx 내부
const [loading, setLoading] = useState(false);
if (!data) {
return <Loading />;
}
이 방식도 동작은 하지만, 문제는 상태(loading, error, data)를 직접 관리해야 하고, 로딩/에러/성공 처리를 컴포넌트마다 중복해서 작성해야 한다는 점이다. 즉, 선언적이지 못할 뿐더러, 로딩 로직이 컴포넌트 내부에 포함되면서 “이 컴포넌트가 무엇을 보여줄 것인가”라는 본래 역할과는 관계없는 책임이 추가된다.
그렇다면 Suspense를 사용하면 어떨까?
return (
<Suspense fallback={<Loading />}>
<RecoBooks />
</Suspense>
);
Suspense를 활용하면 컴포넌트 단위로 로딩 UI를 선언적으로 지정할 수 있다. 또한 로딩/에러 처리를 외부로 분리할 수 있어서, 컴포넌트 내부에서는 오직 “데이터가 준비되었을 때 무엇을 보여줄지”에만 집중할 수 있다. 정리하자면, 명령형 분기 처리 vs 선언적 관리의 차이라고 할 수 있다.
마치며
이 책을 통해 Next.js의 전반적인 구조와 사전 렌더링 방식에 대한 이해를 넓힐 수 있었다. 프로젝트 구조를 설계하는 과정에서는 단순히 디렉토리 구성을 나누는 데 그치지 않고, 페이지 라우팅과 API 설계를 어떻게 유기적으로 연결할지 고민하게 되었다. 특히 각 페이지가 어떤 데이터를 필요로 하는지, 그리고 그 데이터를 언제 불러올지를 기준으로 설계를 진행하면서, 불필요한 중복 호출을 줄이고 사용자 경험을 해치지 않는 효율적인 데이터 흐름의 중요성을 실감할 수 있었다.
한 입 크기로 잘라먹는 Next.js는 Next.js를 처음 접하는 초보자부터 실무 적용을 앞둔 중급 개발자까지 모두에게 추천할 만한 책입니다. 저처럼 한입 시리즈 강의를 통해 큰 도움을 받았던 분들이라면, 이 책에서도 동일한 친절함과 체계적인 구성을 느낄 수 있을 것입니다.
개발 입문 때부터 함께했던 시리즈의 책 버전을 만나게 되어 즐거웠고, 소중한 기회를 주신 이정환 님께 감사드립니다 :)
이 글은 도서를 제공받아 작성한 솔직 리뷰입니다.