
Next.js의 단점과 한계점
Next.js를 실제 프로덕션에서 사용하면서 겪은 단점과 한계점들을 정리합니다.
시작하며
Next.js는 현재 가장 인기 있는 React 기반 프레임워크 중 하나입니다. 실제로 State of JS 2023 조사에서는 meta-framework 범주에서 가장 많이 사용되는 프레임워크로 평가받았으며, React 기반 웹 애플리케이션을 위한 SSR/SSG을 지원하는 대표적인 솔루션입니다. 기업과 개인 프로젝트 모두에서 사실상 표준처럼 자리 잡은게 아닌가 생각합니다.
React 기반의 많은 라이브러리들과 호환되며, Next.js로 프로젝트를 시작하면 백엔드, 프론트엔드를 한번에 처리하며 SSR, SSG 등 다양한 기능을 쉽게 개발할 수 있다는 큰 장점이 있습니다. 하지만 실제로 사용하면서 느낀 명확한 단점 두 가지가 있어, 이에 대한 개인적인 의견을 공유하고자 합니다.
1. 문서 품질의 한계
빈약한 문서
백엔드 개발, 크롤러 개발 등을 하면서 Spring Framework, Apache Kafka 등의 기술을 경험해왔는데, 이들과 비교했을 때 Next.js 문서의 수준은 상당히 아쉽습니다.
Next.js 공식 문서는 기본 개념과 간단한 API 예시에 집중되어 있습니다. 반면 다른 프레임워크와 비교해보면 차이가 크게 드러납니다.
- Spring Framework: Core 문서만 1000페이지 이상, 각 어노테이션과 설정에 대한 상세한 설명과 내부 동작 원리까지 포함
- Apache Kafka: 개념부터 아키텍처, 구현 세부사항까지 체계적으로 정리된 수백 페이지 분량
- Next.js: 주요 기능별로 간단한 예제 중심, 내부 동작이나 엣지 케이스에 대한 설명 부족
잘못된 가이드
더 심각한 문제는 잘못된 정보나 모호한 표현들입니다. 실제 사례로, redirect 함수 사용에 대한 잘못된 가이드가 있었습니다.
공식 문서는 redirect should be called after the try/catch block고 명시했었는데, 그 이유가 "redirect가 내부적으로 에러를 throw하기 때문"이라고 했습니다.
하지만 논리적으로 생각해보면,
- try 블록 내에서 redirect를 호출하면 catch에 잡혀 문제가 될 수 있음
- 하지만 catch 블록 내에서는 이미 에러 처리 중이므로 문제가 없음
- 오히려 catch 블록에서 에러를 처리하고 적절히 redirect하는 것이 좋은 패턴
let isError = false; // let 사용
try {
// 데이터베이스 접근 등의 비즈니스 로직
const data = await fetchData();
} catch (error) {
isError = true
}
// 에러 발생 지점과 처리 지점이 떨어져 가독성이 떨어짐.
// catch 블록에서 직접 처리하는 방식이 더 명확함.
if(isError) redirect()
try {
const data = await fetchData();
} catch (error) {
// 에러 처리 후 적절한 페이지로 리다이렉트
if (error.response?.status === 404) {
notFound()
} else {
redirect('/error/unknown');
}
// 혹은 await + catch 조합
// API 호출 시점에 에러 처리
const data = await queryClient
.fetchQuery(...)
.catch((error) => {
if (error.response?.status === 404) {
notFound()
} else {
redirect('/error/unknown');
}
});
// data 사용
...
}
이 문제를 Next.js discussion에서 제기했고, 2025년 7월 1일 이후 공식 문서가 수정되었습니다.
Next.js DiscussionIn Server Actions and Route Handlers, redirect should be called outside the
try
block when usingtry/catch
statements.
안타깝게도 이미 많은 Next.js 개발자들이 잘못된 정보를 그대로 전파하며 사용 중인 상태입니다.
또한 제 개인의 이해도 문제일수도 있지만 캐싱, 렌더링 순서 등 중요한 동작 방식이 문서에 충분히 표현되지 않아 기대와 다르게 동작하는 경우도 자주 발생합니다.
결과적으로 문서만 믿고 개발하면 예기치 못한 시행착오를 겪을 수 있습니다.
2. 서버 관련 기능 부족
다른 서버 프레임워크 사례들
Next.js가 풀스택 프레임워크를 표방하지만, 실제로는 API Routes에 일부 기능만 추가한 수준입니다.
다른 프레임워크들은 이를 더 체계적으로 해결할 수 있습니다.
- Spring Framework AOP: 특정 비즈니스 로직 실행 전후에 공통 검증이나 로깅을 삽입할 수 있습니다.
- Express.js Middleware: 모든 요청을 라우트 처리 전에 intercept하여 검증이나 공통 처리를 수행할 수 있습니다.
즉, 공통 레이어를 통해 중복을 제거하고 일관된 제어를 가능하게 합니다.
Next.js 의 서버 기능 부족 예시
Next.js에도 middleware 기능이 있지만 구조적으로 한계가 존재합니다. 현재 동작 방식은 다음과 같습니다.
middleware → layout
→ generateMetadata
→ page
<-- 병렬적으로 실행 -->
middleware와 렌더링 단계가 분리되어 있으며, 캐시를 공유하지 않고 layout, generateMetadata, page는 병렬적으로 처리됩니다.
middleware에서 검증 API를 호출하고 결과를 캐싱하기 위해선 불필요한 작업이 필요합니다.
layout, generateMetadata, page에서 처리할 경우는 각각 redirect 처리를 해야 합니다.
결과적으로 최적화가 어려운 구조입니다.
이상적인 구조 2가지
Next.js의 현재 구조는 middleware와 rendering layer가 분리되어 있고 캐시를 공유하지 않기 때문에 불필요한 API 호출과 중복 검증 로직이 발생합니다. 이상적으로는 두 가지 방향의 개선이 가능합니다.
middleware → rendering layer (layout, metadata, page)
<------------------ cache shared ------------------>
이 경우, middleware와 rendering 과정 간의 캐시가 공유되어야 합니다.
이를 통해 에러 상황에서는 middleware에서 적절히 처리하고, 정상적인 경우에는 middleware에서 사용된 데이터를 rendering 단계에서도 재활용할 수 있습니다.
middleware → pre-rendering layer → rendering layer (layout, metadata, page)
<------------------------- cache shared ------------------------->
또는 pre-rendering 단계를 추가하여 API 결과와 검증 로직을 한 번만 실행하고, 그 결과를 렌더링 단계 전체에서 공유할 수 있어야 합니다.
이 과정에서 redirect나 notFound와 같은 처리를 미리 수행하면, 불필요한 rendering 자체를 막을 수 있습니다.
그러나 현재 Next.js에는 이러한 개념이 부재하며, 개발자가 layout, metadata, page 각각에서 동일한 API 호출과 redirect 처리를 반복해야 하는 구조적 한계가 존재합니다.
마무리
Next.js는 여전히 가장 강력하고 생산성이 높은 React 기반 프레임워크 중 하나입니다.
그러나 문서의 부족함과 잘못된 정보, 그리고 서버 기능의 한계로 인해 불필요한 중복 코드가 발생하는 문제가 존재합니다.
이러한 단점은 소규모 프로젝트에서는 크게 드러나지 않지만, 규모가 커질수록 유지보수성과 개발 효율성을 저해하는 주요 요인이 됩니다.
앞으로 Next.js가 진정한 풀스택 프레임워크로 발전하기 위해서는, 문서 품질 개선과 서버 기능 강화가 반드시 수반되어야 한다고 봅니다.