본문 바로가기
개발자/기술스택 도구

Next.js 실무 도입 후 1년, 솔직하게 달라진 점들을 써봅니다

by 나무011 2026. 6. 12.

솔직히 말하면, Next.js를 처음 실무에 도입하자는 이야기가 나왔을 때 나는 그렇게 적극적이지 않았다. 당시 팀에서 쓰던 Create React App 기반 구조가 나름 익숙했고, "잘 돌아가는 걸 왜 바꾸지?" 하는 생각이 있었다. 그런데 SEO 이슈가 계속 발목을 잡았고, 결국 팀 전체가 Next.js로 전환하기로 결정했다.

그게 2025년 초의 일이다. 지금은 1년 넘게 실무에서 써봤고, 기대했던 것도 있었고 예상 못 했던 것도 있었다. 좋은 점만 골라 쓰는 홍보글 말고, 진짜로 달라진 것들을 기록해두고 싶어서 이 글을 쓴다.

 

Next.js 실무 도입 후 1년
Next.js 실무 도입 후 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에서 에러를 내는 경우가 있다.

🔄 생각보다 달랐던 것

설계 대화가 늘어남

컴포넌트 책임 분리를 팀 전체가 고민하게 됐다. 좋은 변화였다.

💡 Next.js 실무 도입 시 팀에 공유했던 팁들
  • 'use client'는 최대한 말단 리프 컴포넌트에 붙이는 게 원칙 — 위에 붙이면 서버 이점을 날린다
  • 서드파티 라이브러리를 쓰기 전에 App Router 호환 여부를 먼저 확인하는 습관을 들일 것
  • 데이터 패칭은 가능하면 Server Component에서 처리 — 클라이언트 번들 크기가 줄어든다
  • next/image는 귀찮더라도 width, height 명시하는 게 CLS 방지에 좋다
  • 로컬 개발 중 .next 캐시 문제가 자주 난다면 rm -rf .next 후 재시작이 가장 빠른 해결책

지금 돌이켜보면 전환 결정은 잘한 선택이었다. 처음 몇 달의 적응 비용이 있었지만, 그 이후로는 생산성과 코드 품질 양쪽에서 긍정적인 변화가 계속되고 있다. 무엇보다 팀이 같은 고민을 공유하면서 코드베이스를 바라보는 눈이 달라진 게 가장 큰 수확이라고 생각한다.

아직 마이그레이션을 망설이고 있다면, 처음부터 전체를 바꾸려 하지 말고 새 페이지나 신규 기능부터 Next.js로 시작해보는 방식을 추천한다. 직접 써봐야 감이 온다.


소개 및 문의 · 개인정보처리방침 · 면책조항

© 2026 나무핀