솔직히 말하면, Next.js를 처음 실무에 도입하자는 이야기가 나왔을 때 나는 그렇게 적극적이지 않았다. 당시 팀에서 쓰던 Create React App 기반 구조가 나름 익숙했고, "잘 돌아가는 걸 왜 바꾸지?" 하는 생각이 있었다. 그런데 SEO 이슈가 계속 발목을 잡았고, 결국 팀 전체가 Next.js로 전환하기로 결정했다.
그게 2025년 초의 일이다. 지금은 1년 넘게 실무에서 써봤고, 기대했던 것도 있었고 예상 못 했던 것도 있었다. 좋은 점만 골라 쓰는 홍보글 말고, 진짜로 달라진 것들을 기록해두고 싶어서 이 글을 쓴다.

도입 전 우리 팀 상황
팀은 프론트 3명 규모였고, 스택은 React + Vite였다. 백엔드 API는 따로 있었고, 프론트는 완전한 CSR(Client-Side Rendering) 방식으로 돌아갔다. 크게 문제 없이 돌아가고 있다고 생각했는데, 사실 몇 가지 불편한 지점이 쌓여가고 있었다.
① SEO가 필요한 페이지에서 메타태그 관리가 복잡함 ② 초기 로딩이 느려 LCP 지표가 좋지 않음 ③ 이미지 최적화를 수동으로 해야 했음 ④ 라우팅 설정이 길어질수록 관리 포인트가 늘어남
달라진 점 ① — 라우팅이 폴더 구조로 해결된다
App Router를 처음 써봤을 때 가장 먼저 든 생각은 "이게 왜 이렇게 편하지?"였다. 기존엔 react-router-dom으로 라우트를 일일이 정의해야 했는데, 이제는 폴더 하나 만들고 page.tsx 파일 넣으면 끝이다.
// 기존 React Router 방식
const router = createBrowserRouter([
{ path: '/', element: <HomePage /> },
{ path: '/blog', element: <BlogPage /> },
{ path: '/blog/:id', element: <BlogDetailPage /> },
]);
// Next.js App Router — 폴더 구조만으로 끝
app/
page.tsx → /
blog/
page.tsx → /blog
[id]/
page.tsx → /blog/:id
처음엔 "뭐 이게 그렇게 큰 차이야?" 했는데, 페이지가 20개, 30개로 늘어날수록 차이가 확 느껴졌다. 라우트 정의 파일이 따로 없으니 구조 파악이 훨씬 빠르고, 새 팀원이 합류했을 때도 폴더만 열어보면 사이트 구조가 보인다.
달라진 점 ② — Server Component가 생각보다 큰 변화였다
솔직히 처음에 Server Component 개념이 잘 와닿지 않았다. "그냥 서버에서 렌더링하는 거 아닌가?" 싶었는데, 실제로 써보니까 생각보다 패러다임이 많이 달랐다.
가장 크게 달라진 건 데이터 패칭 방식이다. 기존엔 컴포넌트 안에서 useEffect + useState 조합으로 API를 호출했다. 이러면 화면이 먼저 뜨고 데이터가 나중에 채워지는 레이아웃 시프트가 발생한다. 이제 Server Component에서 async/await로 데이터를 받아오면 이런 문제가 사라진다.
// 기존 방식 — 클라이언트에서 데이터 패칭
function BlogList() {
const [posts, setPosts] = useState([]);
useEffect(() => {
fetch('/api/posts').then(r => r.json()).then(setPosts);
}, []);
return <ul>{posts.map(p => <li>{p.title}</li>)}</ul>;
}
// Next.js Server Component 방식
async function BlogList() {
const posts = await fetch('/api/posts').then(r => r.json());
return <ul>{posts.map(p => <li>{p.title}</li>)}</ul>;
}
코드도 훨씬 단순해지고, 클라이언트에 불필요한 JavaScript가 내려가지 않으니 번들 사이즈도 줄었다. 다만 이 부분은 뒤에 얘기할 "힘들었던 점"과도 연결된다.
달라진 점 ③ — SEO와 성능 지표가 눈에 띄게 좋아졌다
전환 후 약 2개월이 지나 Google Search Console과 Lighthouse 수치를 비교해봤을 때, 확실히 차이가 있었다. LCP(Largest Contentful Paint)가 줄어들었고, 검색 노출이 잡히지 않던 페이지들이 색인되기 시작했다. 특히 next/image로 이미지 최적화가 자동으로 처리되는 점이 체감상 크게 다가왔다.
| 항목 | 전환 전 (CRA + CSR) | 전환 후 (Next.js) | 체감 |
|---|---|---|---|
| LCP | 3.8초 | 1.4초 | 크게 개선 |
| SEO 색인 속도 | 느림 (CSR 한계) | 빠름 | 크게 개선 |
| 이미지 최적화 | 수동 처리 | 자동 (WebP 변환) | 자동화 |
| 번들 사이즈 | 단일 번들 | Route별 분리 | 개선 |
| 개발 초기 러닝커브 | 낮음 | 중간 | 주의 필요 |
| 로컬 빌드 시간 | 빠름 | 초기엔 느렸음 | 단점 |
힘들었던 점도 솔직하게 — "use client" 경계 잡기
좋은 점만 있었으면 이 글이 홍보글이겠지. 적응 과정에서 가장 헷갈렸던 건 Server Component와 Client Component의 경계였다.
useState, useEffect, 이벤트 핸들러를 쓰는 컴포넌트는 반드시 'use client' 선언이 필요하다. 그런데 처음엔 어디까지를 Server로 두고 어디부터 Client로 분리해야 하는지 감이 잘 안 잡혔다. 너무 위쪽에서 'use client'를 선언해버리면 Server Component의 이점을 다 날려버리는 꼴이 된다.
흔한 실수 패턴: 레이아웃 컴포넌트에 'use client'를 붙이면 그 하위 트리 전체가 클라이언트 컴포넌트가 된다. 인터랙션이 필요한 부분만 작게 분리해서 클라이언트로 내리는 게 포인트다.
이 부분은 팀 내에서도 코드 리뷰 때 자주 지적이 오갔고, 결국 우리 팀만의 간단한 컴포넌트 분리 가이드라인을 문서로 만들어 공유하게 됐다. 오히려 이게 팀 전체의 설계 고민을 끌어올리는 계기가 됐다.
예상 못 했던 긍정적 변화 — 팀 내 설계 대화가 늘었다
이 데이터 패칭을 Server에서 할지 Client에서 할지 결정하려면, 이 컴포넌트가 어떤 역할인지를 먼저 명확히 해야 하더라고요.
기존에는 컴포넌트를 만들 때 "어디에 어떻게 두면 돌아가지?"가 먼저였다면, Next.js를 쓰면서 "이 컴포넌트의 책임이 뭔지"를 먼저 생각하게 됐다. 서버에서 처리할 로직인지, 클라이언트 인터랙션이 필요한지를 구분하다 보니 자연스럽게 컴포넌트 설계가 더 단단해졌다.
팀 전체가 같은 고민을 공유하면서 리뷰 시간이 오히려 더 알차졌다. 단순히 "돌아가냐 안 돌아가냐"가 아니라, "왜 이렇게 나눴냐"는 대화가 늘어난 건 예상 밖의 수확이었다.
1년 사용 후 총평
SEO / 성능 지표
도입 이유 자체였던 부분. 수치로 확인할 수 있을 만큼 개선됐다.
파일 기반 라우팅
복잡한 라우트 정의 없이 폴더 구조로 해결. 신규 팀원 온보딩이 빨라졌다.
이미지 / 폰트 자동 최적화
next/image만 써도 WebP 변환, 지연 로딩이 자동으로 처리된다.
Server / Client 경계
개념을 잡는 데 시간이 걸렸다. 팀 전체가 학습 비용을 치렀다.
서드파티 라이브러리 호환
CSR 전제로 만들어진 라이브러리가 Server Component에서 에러를 내는 경우가 있다.
설계 대화가 늘어남
컴포넌트 책임 분리를 팀 전체가 고민하게 됐다. 좋은 변화였다.
'use client'는 최대한 말단 리프 컴포넌트에 붙이는 게 원칙 — 위에 붙이면 서버 이점을 날린다- 서드파티 라이브러리를 쓰기 전에 App Router 호환 여부를 먼저 확인하는 습관을 들일 것
- 데이터 패칭은 가능하면 Server Component에서 처리 — 클라이언트 번들 크기가 줄어든다
next/image는 귀찮더라도width,height명시하는 게 CLS 방지에 좋다- 로컬 개발 중
.next캐시 문제가 자주 난다면rm -rf .next후 재시작이 가장 빠른 해결책
지금 돌이켜보면 전환 결정은 잘한 선택이었다. 처음 몇 달의 적응 비용이 있었지만, 그 이후로는 생산성과 코드 품질 양쪽에서 긍정적인 변화가 계속되고 있다. 무엇보다 팀이 같은 고민을 공유하면서 코드베이스를 바라보는 눈이 달라진 게 가장 큰 수확이라고 생각한다.
아직 마이그레이션을 망설이고 있다면, 처음부터 전체를 바꾸려 하지 말고 새 페이지나 신규 기능부터 Next.js로 시작해보는 방식을 추천한다. 직접 써봐야 감이 온다.
여러분 팀은 Next.js로 전환하셨나요, 아직 고민 중이신가요?
실무에서 겪으신 경험이나 궁금한 점이 있으시면
댓글로 남겨주시면 같이 얘기해보겠습니다 🙌