rm -rf ./frontend
나는 이 프로젝트를 2019년 정도에 장고 풀스택 기반으로 개발했다가, 2022년 넥스트로 마이그레이션 했었다. 그리고 지금 2025년 다시 장고 풀스택 기반으로 돌아왔다. 이 여정에 대한 짧은 기록을 남기려고 한다.
나를 구원했던 넥스트 ✨

웹 개발 지식이 거의 전무했던 나는 Jekyll의 템플릿 문법과 동일한 문법을 제공하는 장고를 이용해서 처음 이 프로젝트를 만들었다. 프론트엔드의 거의 대부분은 템플릿 파일로 작성했다가, AJAX에 대해서 알게 되면서 많은 자바스크립트 코드를 작성하게 되었다.
jQuery
로 대충 돔을 조작하다가, 타입스크립트 기반의 바닐라로 전환했다. 하지만, 코드는 걷잡을 수 없이 복잡해져 관리하기가 힘들었다. 결국엔 리액트와 같은 도구를 이용해야 겠다고 생각했지만 SSR이 발목을 잡았기 때문에 고민했다. 그러던 와중에 Next.js를 알게 되었다.
그 아이는 약간의 규칙만 지키면 우아하게 SSR을 해주면서도 리액트로 개발할 수 있는 마법의 도구였다. 무엇보다 사이트가 SPA처럼 동작하는 게 그때는 왜 그렇게 힙해 보였던 건지… 나는 당장 모든 템플릿을 넥스트로 마이그레이션 했다. 장고는 API 서버 역할만 하고, 프론트엔드는 온전히 넥스트가 책임지는 구조. 그때는 그것이 최상의 선택으로 보였다.
내게 깨달음을 준 RSC
리액트와 넥스트가 발전하면서 서버 컴포넌트라는 개념이 등장했다. 세간은 떠들석(?) 했다. 근데 이상하게 내 눈엔 그게 마음에 들지 않아 보였다. 마치 코드 마지막에 세미콜론을 남기지 않은 것 처럼 그 코드가 묘하게 이질적으로 보였다.
import db from './database';
async function Note({id}) {
const note = await db.notes.get(id);
return (
<div>
<Author id={note.authorId} />
<p>{note}</p>
</div>
);
}
나는 이 코드가 PHP 처럼 보였다. PHP가 오히려 나은 점은 그 아이는 태생부터 서버에 쉽게 접근 할 목적으로 등장한 도구였다는 점이다. 리액트는 클라이언트 개발을 목적으로 나왔다가 서버 사이드 도구가 되는 기이한 회귀를 했다. SPA의 등장으로 클라이언트와 서버의 구분이 명확해진 상태에서, 더 나은 최적화를 위해 제안된 RSC가 나에게 던진 메세지는 많은 생각을 하게 만들었다.
최적화를 하고 싶니? 그럼…
결국 어플리케이션을 최적화 하기 위해서는 사이즈를 덜어내고 네트워크를 최적화 해야한다. 넥스트를 사용하는 대부분의 프로젝트들은 프록시 역할을 자처하면서 서버 사이드 렌더링이라는 목표를 달성했다. 하지만 이 얼마나 비효율적인 구조인가?세상에서 가장 느린 스펙은 네트워크인데 말이다.넥스트 서버가 클라이언트 요청 받아서, 다시 서버 API를 요청해서, 그걸 받아서 렌더링하는 최고로 느린 스펙이다.
심지어 넥스트는 리액트를 포함하기 때문에 150kb가 훌쩍 넘는 사이즈를 기본 탑재하고 페이지가 커질수록 스크립트 크기는 점차 비대해지며 서버에서 렌더링한 컴포넌트와 클라이언트 컴포넌트를 하이드레이션하는 과정은 상당한 오류를 유발하며 거대한 비용을 지불하게 한다.
서버 컴포넌트는 이런 일부 문제를 해결하기 위해 나왔다. 하지만 이걸 100% 활용하려면 서버 로직을 전부 Node로 변경하여 서버 사이드로 활용해야 가치가 있다고 생각하며, 그게 아니라면 큰 이점은 없다고 생각했다. (아주 약간 번들 사이즈가 줄어들 수 있는거 정도…?)
그래서, 다시 돌아왔다
이 모든 고민의 끝에 내가 내린 결론은 "다시 클래식한 구조"로 돌아가는 것이었다. 하지만 2019년으로 돌아간 건 절대 아니다. 내 개발 환경은 완전히 달라졌다.
우선 사랑하는Vite를 앞세워, 스타일링은Tailwind CSS를 활용해 템플릿에서 사용한 클래스만 빌드하여 최적화 한다. 템플릿을 개발하고 있을 때도 스크립트나 스타일을 Hot Reload해서 빠른 피드백을 받을 수 있게 한다. 그리고 빌드된 자바스크립트는 섬 아키텍처를 응용한 방식으로 내가 원하는 컴포넌트를 필요할 때 지연 로드하여 호출한다. 이게 웹의 근본이자 자바스크립트의 원래 자리다.
사실 이거, 예전에 한창 마이크로 프론트엔드가 부각되면서 유행하던 구조였다. 모듈 페더레이션이니, 매니페스트 파일이니 하면서 등장한 개념들은 백엔드에 의존적인 프론트엔드를 독립적인 구조로 점진적 마이그레이션 하는 것이 주된 목표였다. 섬 아키텍처나 RSC는 이보다 훨씬 진보된 개념으로 제로 번들 목표를 달성하면서도 모던 프론트엔드 프레임워크가 뷰의 주도권을 가지게 하는 방식이다.

그래서 내가 채택한 방식은, 리액트의 생태계가 거대한걸 부정할 순 없다. SSR이 필요없는 핵심 기능은 리액트를 활용 하면서도 SSR이 중요한 요소 + 상호 작용이 필요한 애들은 서버 사이드 랜더링을 하고 Alpine과 같은 가벼운 스크립트를 붙히는 방식을 택했다. 그게 RSC로 부터 얻은 교훈이었다. 오히려 좀 더 가벼워진?
처음 프론트엔드를 배울때는 모듈화하고 컴포넌트를 재사용해서 앱을 빠르게 빌딩하는데 초점을 맞췄는데 지금은 뭐랄까… 쉽게 버리고 다시 빨리 만들 수 있는 구조를 빌딩하는데 초점을 맞추고 있는 것 같다. 요즘엔 특히 AI가 있으니 더 쉽게 버릴 수 있으면서도 AI가 쉽게 이해하는 형태로 구성하는데 집중하게 된다.
<div x-data="{ toggled: false }">
<button
@click="toggled = !toggled"
class="bg-blue-500 text-white font-bold py-2 px-4 rounded shadow-md hover:scale-105 transition-transform"
>
Toggle
</button>
<p x-show="toggled" class="mt-2">Hello AI!</p>
</div>
스타일(테일윈드), 상태(x-data
), 동작(@click
, x-show
)이 전부 하나의 HTML 덩어리 안에 있다. AI가 가장 이해하기 쉽고, 가장 실수 없이 생성할 수 있는 완벽한 형태다. 리액트도 같은 패턴으로 만들 수 있겠지만, 뭐랄까… 이건 좀 더 근본적이다. 번들링이나 트랜스파일링이 필요하지 않은 HTML, 무거운 스크립트 없이 필요한 만큼의 스크립트로 동작하게 된다.
물론 난 여전히 리액트를 좋아하고 다양한 프로젝트에서 리액트를 쓰고 있다. 하지만 어느샌가 리액트라는 도구에 집착하고 있었던 것 같다. 일단 프론트엔드는 리액트로 만들어야 한다는 강박. 빠른 생산성을 위해 가렸던 눈을 서버 컴포넌트가 다시 뜨게 해준 것 같다.
자바스크립트는 조력자가 되어야 한다. 무언가를 최적화 하려면 근본적인 관점에서 다시 들여다 봐야 한다.