Next.js는 풀스택인가?
Next.js를 처음 접한 사람들은 막연하게 Next.js를 "풀스택 프레임워크"로 알고 있을 것입니다.
공식 홈페이지에서도 분명히 "프론트엔드 풀스택 프레임워크"라고 설명하고 있으니까요.
하지만 예전 Next.js에 처음 입문했을 당시의 저를 포함해 많은 개발자들이 이 "풀스택" 프레임워크라는 표현을 정확하지 이해하지 못하거나 혼란스러워하는 것처럼 보였습니다. 기존의 리액트와 달리 서버 사이드 렌더링(SSR)이라는 것이 가능하다는 것은 알지만 그것이 왜 "풀스택"으로 연결되는지에 대해서는 많은 초보 개발자들은 명확하게 이해하기 쉽지 않을 것 같습니다. 심지어 구글링을 하다 어떤 개발 관련 포스팅에서 "왜 Next.js을 풀스택이라고 하는지 이해할 수 없다"는 반응을 목격하기도 했습니다.
Next.js는 서버에서도 클라이언트에서도 실행된다
클라이언트 컴포넌트에서 console.log를 찍으면 해당 웹사이트 브라우저 개발자 도구의 console에 그 내용이 출력됩니다. 하지만 서버 컴포넌트에서 console.log를 찍으면 vscode 내의 콘솔에 그 내용이 출력됩니다. 이는 서버 컴포넌트의 코드는 클라이언트(웹 브라우저)가 아닌 서버(여기서는 vscode 개발 환경)에서 동작한다는 것을 명백하게 확인할 수 있는 방법 중 하나입니다. 여기서의 "서버"는 vscode의 개발 환경이지만(정확히 말하면 vscode 자체가 아닌 개발 환경에서 실행되는 node 런타임 환경을 의미합니다) 프로덕션 배포 후의 "서버"는 AWS나 Vercel를 통해 그 프로젝트가 배포된 "서버" 환경을 의미합니다.
Next.js 코드가 실행되는 방식
백엔드 프로젝트 코드와 그와 연결되는 프론트엔드 프로젝트 코드를 배포했다고 가정해 보겠습니다.
(Next.js 프로젝트의 코드와 백엔드 프로젝트 코드는 같은 서버에 배포된 것일 수도 있고 각각 다른 서버에 배포한 것일 수도 있습니다. 그러므로 아래 [서버 사이드]에 있다는 것은 "같은 서버"에 둘이 항상 공존한다는 것을 의미하지는 않습니다.)
1. 사용자가 웹사이트에 접속하기 전
사용자가 웹사이트에 접속하기 전까지 모든 코드와 데이터(그게 스프링 부트 백엔드든 Next.js 프론트엔드든) 전부 배포된 서버(AWS, Vercel 등)에 있습니다.
[서버 사이드]
Next.js의 서버 컴포넌트 코드
Next.js의 클라이언트 컴포넌트 코드
백엔드 프로젝트 코드
[클라이언트 사이드]
현재 아무것도 없음
2. 사용자가 웹사이트에 접속한 순간
이때 사용자가 접속(요청)을 하면(프로토콜을 보내면) Next.js 프론트엔드 프로젝트에서 "서버 컴포넌트"에 해당하는 코드는 이 "서버"에서 먼저 실행됩니다. (하이드레이션을 위한 클라이언트 컴포넌트 코드의 서버사이드 렌더링도 같이 발생합니다.)
[서버 사이드]
Next.js의 서버 컴포넌트 코드 - 자바스크립트 코드 실행 및 HTML/CSS 렌더링
백엔드 프로젝트 - 서버 컴포넌트에서 데이터 fetch 등 API 요청 코드가 있다면 해당하는 데이터 반환
(다시) Next.js의 서버 컴포넌트 코드 - 자바스크립트 실행 결과를 포함한(백엔드로의 data fetching이 있다면 그 반환된 데이터를 포함한) HTML/CSS 렌더링
Next.js의 클라이언트 컴포넌트 코드 - HTML/CSS 렌더링 발생(정적인 요소만)
[클라이언트 사이드]
현재도 여전히 아무것도 없음
3. 서버에서 렌더링 결과물을 클라이언트에 전송
이러한 실행의 결과(서버 컴포넌트의 자바스크립트 코드가 실행되고 모든 것이 렌더링을 완료한 결과와 클라이언트 컴포넌트의 정적인 HTML 요소만 모두 렌더링 된 결과와 자바스크립트 코드)는 RSC payload의 형태로 클라이언트에 전송됩니다. 클라이언트로 전송되기 전에 이미 서버 컴포넌트의 자바스크립트는 실행되었기에 별도의 백엔드 서버에 data fetching과 같은 API 요청을 보낸다면 그 요청으로 받은 결과까지 이미 포함하고 있는 상태입니다.
[서버 사이드]
서버 컴포넌트의 모든 과정이 완료된 결과물과 클라이언트 컴포넌트의 렌더링 된 정적 HTML/CSS 파일과 자바스크립트 번들을 RSC payload라는 특수한 형태로 변환해서 클라이언트 쪽으로 전송
4. 서버에서 전송한 내용이 클라이언트에 도착했을 때
[클라이언트 사이드]
서버에서 실행 및 렌더링이 완료된 서버 컴포넌트 결과물 도착(서버 컴포넌트 자바스크립트 제외)
서버에서 정적인 HTML/CSS 렌더링만 완료된 클라이언트 컴포넌트 결과물 도착(클라이언트 컴포넌트 자바스크립트 포함) -> 정적 HTML결과물은 이미 서버에서 렌더링이 완료되었기에 곧바로 보임
그러므로 실행과 렌더링이 완료된 결과물만 클라이언트로 전송할 뿐, 서버 컴포넌트의 자바스크립트 코드는 클라이언트로 전송되지 않습니다. 클라이언트 컴포넌트는 정적 HTML 요소만 서버에서의 렌더링을 완료한 후 자바스크립트 코드를 같이 클라이언트로 전송합니다.
+) 5. 클라이언트에 도착한 클라이언트 컴포넌트의 코드 중 데이터 요청을 보내는 코드가 있다면
클라이언트 컴포넌트에 별도의 백엔드 서버로 data fetching과 같은 API 요청을 보내는 코드가 있다면 이는 모든 결과물이 클라이언트에 도착한 후 도착한 자바스크립트가 코드가 실행되면서 동작합니다.
[클라이언트 사이드]
클라이언트에 도착한 클라이언트 컴포넌트의 자바스크립트 실행(하이드레이션, 이때 useEffect와 같이 컴포넌트 마운팅시 data fetching과 API 요청을 보내는 코드가 있다면 이 단계에서 백엔드에 요청을 보냄)
[서버 사이드]
백엔드 프로젝트 코드 - 클라이언트 컴포넌트의 요청을 받아 적절한 데이터 반환
[클라이언트 사이드]
백엔드 반환 데이터 받아서 다시 렌더링
이러한 과정이 Next.js의 코드가 실행되는 방식입니다. (전체적인 흐름을 적으려 했기에, 위 과정에는 세세한 부분은 생략되었을 수 있습니다.)
사람들은 왜 혼란스러워하는가
표면적으로만 생각한다면 백엔드 = 서버, 프론트엔드 = 클라이언트라고 느껴질 수 있습니다. 예를 들자면 스프링 부트로 백엔드를 구축하고 순수 리액트(리액트 서버컴포넌트를 포함하지 않은)로 프론트엔드를 구축한다는 전제하에 이는 정확합니다. 스프링 부트 백엔드 프로젝트는 서버에서만 실행되고 리액트 프로젝트는 클라이언트에서만 실행되기 때문입니다. 하지만 RSC(리액트 서버 컴포넌트)와 Next.js라는 리액트 프레임워크가 출시되면서 그러한 패러다임은 변화하게 되었습니다.
"서버"라는 단어의 맥락
앞부분에서는 저런 배포 환경(코드가 동작하는 런타임 환경(AWS의 EC2 인스턴스, Vercel의 서버리스 함수, vscode에서 실행되는 노드 런타임 등))이 "서버"라고 칭했지만 이 단어를 사용하는 맥락은 대화마다 다를 수 있습니다.
예를 들어 AWS EC2에 인스턴스를 대여해 가상 서버를 구축하고 자바 스프링 부트 기반의 백엔드 프로젝트와 Next.js의 프론트엔드 프로젝트를 배포했다고 가정해 보겠습니다. (같은 인스턴스에든 다른 인스턴스에든) 이때 프론트에서 서버로 요청을 보낸다 할 때 서버는 "EC2 인스턴스 위의 가상 환경 서버"가 아닌 그 가상 환경에 배포되어 있는 "스프링 부트 프로젝트"를 의미합니다. 이 상황은 "서버로 요청을 보낸다"도 맞는 말이지만 이걸 좀 더 정확히 하면 "서버에 있는 백엔드 프로젝트 코드에 요청을 보낸다"는 의미인 것입니다. 이렇게 백엔드=서버라고 치환해서 사용하는 경우는 개발 문화에서 일반적입니다.
하지만 이전에도 JSP(Java Server Pages)가 있었다
그런데 오히려 백엔드 = 서버, 프론트엔드 = 클라이언트라고 여겨졌던 것은 비교적 최근의 인식입니다. 이전에도 저 등호를 충족하지 않는 경우는 이미 존재했습니다. 예를 들면 스프링 부트 프로젝트에서 JSP를 사용하는 방식이 있습니다. JSP는 사용자에게 보이는 View를 생성하면서(프론트엔드를 담당), 스프링 부트 프로젝트라는 백엔드 프로젝트 내부의 코드입니다. (이에 더해 JSP에는 백엔드 로직까지 들어갈 수 있지만 현대 아키텍처에서는 사용하지 않는 방식입니다.) JSP는 Next.js의 서버컴포넌트처럼 "서버"에서 "실행"되고 이 코드는 "클라이언트"로 전송되지 않습니다. 다만 JSP는 스프링 부트라는 통상적인 "백엔드" 프로젝트 내부에 있고 Next.js의 서버 컴포넌트는 Next.js라는 통상적인 "프론트엔드" 프로젝트 내부에 있을 뿐입니다.
그러니까 JSP는 서버 코드(서버에서 실행되고 클라이언트로 전송되거나 클라이언트에서 실행되지 않음)이면서, 프론트엔드 코드(View를 담당하는)이고, 백엔드 프로젝트의 코드(자바 스프링 부트 프로젝트 내부의 코드)이지만, 백엔드를 담당하는 코드(로직의 관점에서)가 될 수도 있고 아닐 수도 있는 것입니다.
"풀스택"이라는 단어의 전제조건
"풀스택 개발자"는 일반적으로 한 명의 개발자가 하나의 서비스를 처음부터 끝까지 혼자서 만들어낼 수 있는 능력이 있는 경우를 의미합니다.
만약 "풀스택"이라는 단어가 "백엔드 프로젝트"와 "프론트엔드 프로젝트"를 명백하게 분리해야 한다(백엔드를 담당하는 또 다른 서버가 필요하다)는 전제를 충족해야만 한다면 Next.js는 이러한 통상적인 "풀스택"이라는 단어에는 부합하지 않습니다. Next.js는 자신만으로 서버 로직과 클라이언트 렌더링을 실행할 수 있지만, 이는 백엔드 프로젝트와 프론트엔드 프로젝트를 각각 다른 프로젝트로 분리한 것(스프링 부트(백)-리액트(프론트)/node.js(백)-vue(프론트)처럼)은 아니기 때문입니다.
하지만 한 명의 개발자가 하나의 서비스를 처음부터 끝까지 혼자서 만들어낼 수 있는 능력이 있다는 첫 번째 전제만 충족한다면 풀스택이라고 할 수 있다!라고 한다면 Next.js는 풀스택 프레임워크가 맞다고 할 수 있습니다.
애초에 "풀스택"이라는 단어에 절대적인 기준이 존재하지 않습니다. 그리고 이것이 초기 혼란의 원인이었던 것입니다.
우리나라에서의 "풀스택"이라는 단어의 전제조건
이는 메인 주제를 살짝 비껴가는 느낌이 있어서 접은 글로 분리합니다.
(express.js 내부 db + express.js + 리액트 + PaaS)로 프로젝트를 구축하고 배포하든 (postgre sql + spring boot + next.js + IaaS)로 프로젝트를 구축하고 배포하든 어쨌든 하나의 서비스를 한 명의 개발자가 혼자서 만들고 배포까지 했으니 앞서 말한 전제하에서는 둘 다 풀스택 개발자라고 부를 수 있을 것입니다.
그러나 현시대의 우리나라에서는 "풀스택"이라는 단어는 저러한 의미보다는 각각의 분야에 대한 (중첩되지 않는) 서로 다른 지식과 역량이 있을 때를 의미하는 것 같습니다. 예를 들면 혼자서 SQL로 데이터베이스도 구축하고, 스프링 부트 등으로 백엔드 REST API도 만들고, 리액트 기반으로 프론트엔드 프로젝트를 만들어서 이에 연결하고 AWS를 통한 배포까지 완료가 가능하면 그때서야 "풀스택 개발자"라고 인정해 주는 느낌입니다. (물론 이는 일반화의 여지가 있기에 모든 개발자들이 이러한 가치관을 가지고 있다는 것은 아닙니다. 다만 이러한 경향성이 있다고 느꼈습니다.)
때문에 현 시각 "대한민국"의 기준에서 앞서 전자의 케이스(express.js 내부 db + express.js + 리액트 + PaaS)는 풀스택 개발자라고 인정하지 않는 사람들이 적지 않을 것입니다. (분명 백엔드와 프론트엔드를 분리해야 한다는 전제도 충족했는데도 말이죠!) 전자는 백엔드-프론트엔드 전부 자바스크립트라는 단일 언어 기반이고, 프로젝트 내부 db 구축은 sql 관련 학습이 없어도 가능하고(아마 json 파일의 형태로 관리), PaaS 배포는 별도의 인프라 지식을 필요로 하지 않기 때문입니다. 후자는 postre sql이라는 데이터베이스 관리 프로그램을 사용하기 위한 별도의 SQL 공부가 필요하고, spring boot을 사용하기 위해 자바라는 언어를 학습해야 하며, next.js를 위해 자바스크립트와 리액트에 대한 지식이 있어야 하고, 배포를 위한 별도의 인프라 지식이 필요합니다. 우리나라에서 통용되는 "풀스택 개발자"라는 칭호에는 좀 더 넓고(자바스크립트 단일이 아닌 다른 언어나 영역까지), 좀 더 깊은 지식(백엔드 프로젝트 내부에 db를 담당하는 파일을 만드는 것보다 데이터베이스를 sql로 따로 구축하는 것은 db에 대한 상대적으로 더 깊은 지식이 필요합니다.)을 갖고 있어야 한다는 높은 요구사항을 충족하는가가 기준이 됩니다.
"백엔드"라는 단어의 의미
"프론트엔드"라는 단어는 "사용자가 접하는 맨 앞(front) 단", "사용자에게 보이는 view"를 담당한다는 의미를 내포하니까 Next.js가 "프론트엔드"인 것은 당연히 맞습니다. 그런데 Next.js를 "백엔드"라고도 이야기한다면 이는 적절한 표현일까요?
이때의 "백엔드"라는 단어의 의미는 무엇입니까? 어떤 전제를 충족해야 "백엔드"라고 할 수 있습니까? 서버에서만 실행되는 로직? 사용자에게 보이지 않는 로직? 보이지 않는다는 것은 로직에 관련된 코드가 클라이언트에 보이지 않는다(노출되지 않는다)는 의미입니까? 아니면 사용자에게 보이는 View와는 관련 없는 코드를 의미합니까? 무엇이라고 정의하시겠습니까?
Next.js는 "백엔드"의 역할도 합니까?
리액트 프로젝트에서 이미지를 업로드하는 기능이 구현되었다면, 사용자가 올린 이미지를 특정 형식(blob)의 객체로 변환하는 과정을 담은 코드가 있을 것입니다. 그리고 이 코드는 클라이언트에서 실행될 것입니다. 하지만 이는 사용자에게 보여지게 되는 "View"를 담당하거나 그와 관련이 있는 코드는 아닙니다. 그러니까 이 "코드 자체"는 클라이언트에 노출되지만 사용자에게 보이는 "View"와는 별개의 코드인 것입니다. 프론트엔드가 "View"에 관한 것만을 의미하고 백엔드는 "View"와는 관련 없는 부분을 가리키는 것이라면 이는 프론트엔드가 아니라 백엔드에 가깝다고 할 수 있겠으나 통상적으로 이를 "백엔드 코드"라고 여기지는 않습니다. "서버"에서 실행되지 않기 때문입니다.
별도로 분리된 백엔드 프로젝트가 존재하지 않을 뿐 Next.js의 서버 컴포넌트의 자바스크립트 코드들은 전부 서버에서 실행됩니다. 이 코드들은 클라이언트에서 실행되지도, 클라이언트로 전송되지도 않습니다. 그리고 이러한 특징들은 기존의 백엔드 프로젝트의 코드가 갖는 특성과 일치합니다.
그러면 사용자에게 보이는 "View"와도 직접적인 관련이 없고, "서버"에서만 실행된다면 이는 "백엔드"라고 할 수 있는 것일까요? 그러면 Next.js의 "사용자에게 보이는 View와는 관련 없는 로직"을 담은 "서버"에서만 실행되는 서버 컴포넌트 코드는 "백엔드 코드"라고 부를 수 있는 것일까요?
결국 "백엔드"라는 단어의 전제를 "서버에서 실행되는 서버사이드 로직이 있고 그 로직 관련한 코드가 클라이언트에 전송되지 않는 경우"로 라고 정의한다면 Next.js는 "백엔드"의 기능도 한다고 말할 수 있는 것입니다. (만약 여기에 API 엔드포인트로 들어온 요청을 받을 수 있어야 한다는 전제가 더해진다 해도, 이는 Next가 제공하는 API Routes 기능을 통해서 가능하기에 여전히 위 문장은 유효합니다.)
Next.js의 이러한 "백엔드 기능"을 "보안" 측면에서 바라보면
보통 프론트엔드는 백엔드 서버에 API 요청을 보내고, 백엔드 서버는 그에 해당하는 결과를 데이터베이스에서 찾아서 다시 프론트엔드로 전송합니다. 이때 백엔드 서버가 존재해야 하는 것은, 분업의 목적도 있지만 데이터베이스에 접근하는 코드를 프론트엔드 프로젝트(RSC가 없고 Next.js가 아닌 순수한 리액트)에서 작성하면 클라이언트에 노출되기 때문입니다. 물론 .env파일로 중요한 환경변수는 따로 관리하겠지만, 어쨌든 그 로직 자체는 노출됩니다. 이때 "서버"에서만 실행되기에 "클라이언트"로는 전송되지 않는 로직이 있는 별도의 백엔드 서버를 만들어서 이러한 보안적인 취약성을 보완하는 것입니다. (이는 프론트엔드 관점에서의 서술입니다.)
그런데 Next.js의 서버 컴포넌트에서는 별도의 백엔드 서버를 구축하지 않고도 이러한 것들이 가능해졌습니다. 서버 컴포넌트를 통해 클라이언트에 노출되지 않는 API 요청 코드를 작성할 수 있고, Server Actions을 통해서 클라이언트에 노출되지 않는 데이터베이스에 접근하는 로직을 작성할 수 있게 된 것입니다.
Next.js는 기존의 백엔드 프로젝트를 대신할 수 있습니까?
물론 이러한 부분이 가능해졌다고 해서 기존의 방식과 같이 백엔드-프론트엔드를 분리하는 형식이 더 이상 의미 없어진 것은 절대 아닙니다. 현실적으로도 몇몇 특수한 경우가 아니라면 백-프론트 프로젝트를 분리하여 구축하는 것이 일반적이며 더 적절합니다. 스프링 부트와 같은 별도의 백엔드 프로젝트를 구축하는 것은 Next.js에서 제공하는 "백엔드 기능" 이상의 것들을 훨씬 더 많이 그리고 비교할 수 없을 정도로 강력하게 제공합니다. (이에 더해 근간이 되는 언어 자체의 차이도 있습니다!) Next.js의 이러한 특성은 프론트엔드 개발에 있어서 추가적인 편의성을 제공해 주는 것이지 기존의 백-프론트 분리 아키텍처를 대신할 수 있다고는 말할 수 없습니다.
결론
지금까지 했던 이야기들이 바로 Next.js가 풀스택 프레임워크라고 불리는 이유입니다. 개발 문화에서도 대화를 나눌 때 실제 개발에서처럼 어떤 단어나 용어에 대한 "추상화"를 많이 사용하기 때문에 생긴 혼란이라고 생각합니다. 생각해 보니 추상화보다는 용어에 대한 명확한 정의(1:1로 대응되는) 내지는 기준의 부재 때문이라는 표현이 더 적절하겠네요! 이 글은 Next.js App Router 디렉토리 기준으로 작성되었습니다. 혹시라도 잘못된 정보가 있다면 언제라도 피드백을 부탁드립니다!
'FE > Next.js' 카테고리의 다른 글
[Next.js] AWS EC2에 Docker와 Node없이 Next.js 정적 배포(Static Exports)하기 (0) | 2025.05.18 |
---|---|
[Next.js] AWS EC2로 Next.js를 배포할 때 Node.js를 설치해야 하나요? (0) | 2025.05.16 |
[Next.js] 포트폴리오 성능 점수 100점 도전하기 (feat. 라이트하우스, 폰트 최적화) (0) | 2025.05.01 |
[Next.js] Yarn berry로 Next.js 프로젝트 시작하기 (0) | 2025.03.13 |
[Next.js] App Router에서의 CSS-in-JS 사용과 한계 (0) | 2025.03.11 |