20년치 프론트엔드 변화, 8개 레이어로 따라잡기

한동안 프론트엔드를 들여다보지 않다가 다시 열어보면 낯선 용어만 가득하다. Vite, SSR, hydration, RSC, edge, shadcn... 각각이 왜 필요한지 모르면 그냥 유행어 목록으로 보인다. 하지만 이 도구들은 임의로 생긴 게 아니라 전부 실제로 겪었던 문제("상처") 위에 덧댄 해결책이다. David Poblador i Garcia가 쓴 "What happened to the frontend"(GeekNews에서 소개됨)를 따라가며, 2008년부터 지금까지 프론트엔드가 어떤 문제를 풀다가 여기까지 왔는지 정리해본다.

jQuery와 "화면 직접 고치기"의 시대

2008년의 문제는 단순했다. 페이지 전체를 새로고침하지 않고 일부만 바꾸고 싶은데, XMLHttpRequest를 직접 다루면 브라우저마다 동작이 달랐다. jQuery는 이 차이를 감춰줬고, $.ajax로 비동기 요청을 하는 게 표준이 됐다.

문제는 그다음이었다. 서버에서 데이터가 오면 화면의 특정 <div>를 직접 찾아 텍스트를 바꾸고, 클래스를 토글하고, 이벤트를 다시 바인딩해야 했다. 데이터가 열 군데에서 쓰이면 열 군데를 손으로 동기화해야 한다는 뜻이다. 이 수작업 동기화가 다음 레이어의 원인이 됐다.

선언형 UI와 Virtual DOM

React(2013), Vue, Angular가 공통으로 들고나온 답은 "데이터가 바뀌면 화면이 알아서 반영된다"는 선언형 모델이다. 개발자는 state가 어떻게 바뀌는지만 신경 쓰고, DOM을 어떻게 바꿀지는 프레임워크가 계산한다. 컴포넌트라는 단위로 UI를 레고 블록처럼 조립할 수 있게 된 것도 이때부터다.

그런데 JavaScript에는 모듈 시스템이 없었다. import/export를 여러 파일에 걸쳐 쓰려면 트랜스파일과 번들링이 필요했고, 그게 Babel과 webpack을 낳았다. 파일을 브라우저에서 그냥 열어볼 수 없게 된 시점이 바로 여기다.

// webpack 이전: 스크립트 태그 순서가 곧 의존성 관리였다
<script src="jquery.js"></script>
<script src="app.js"></script>

// webpack 이후: 모듈 그래프를 번들러가 계산한다
import { fetchUser } from './api';
import { renderProfile } from './ui';

빌드가 느려지자 도구를 다시 짰다

번들러가 트랜스파일, 트리 쉐이킹, 청크 분할까지 떠맡으면서 빌드 시간이 90초를 넘기는 일이 흔해졌다. 해법은 JavaScript로 짠 도구를 Go나 Rust로 다시 쓰는 것이었다. esbuild와 SWC는 기존 도구보다 10~100배 빠르고, Vite는 이 위에서 개발 서버를 즉시 띄우고 HMR(파일 저장 즉시 반영)을 제공한다.

// vite.config.js 자체는 짧다 - 무거운 계산은 esbuild/rollup이 내부에서 처리
{
  "scripts": {
    "dev": "vite",
    "build": "vite build"
  }
}

서버로 돌아온 렌더링, 그리고 hydration 이라는 대가

순수 클라이언트 렌더링 SPA는 검색엔진이 빈 페이지만 보고, 사용자는 JS가 다 로드될 때까지 흰 화면을 본다는 문제가 있었다. 그래서 Next.js, Astro, Nuxt 같은 메타 프레임워크가 SSR/SSG로 서버에서 HTML을 미리 만들어 보내는 방식으로 돌아갔다.

문제는 서버가 보낸 HTML에는 클릭 이벤트가 붙어있지 않다는 점이다. 브라우저는 같은 컴포넌트 트리를 다시 실행해서 이벤트를 "붙이는" 작업, 즉 hydration을 해야 한다. 결과적으로 같은 렌더링을 서버와 클라이언트에서 두 번 하는 셈이 되고, 이 비용을 줄이려고 나온 게 Astro의 Islands(상호작용이 필요한 부분만 hydration), Qwik의 Resumability, React Server Components다.

보조 도구가 성숙해지고, 배포가 git push 하나로 줄었다

코드베이스가 커지자 타입 없이는 리팩터링이 무서워졌고, 그래서 TypeScript가 사실상 기본값이 됐다. 반복되는 스타일 작업은 Tailwind의 유틸리티 클래스가, 반복되는 위젯은 shadcn/ui처럼 "설치"가 아니라 "복사해서 내 코드로 만드는" 방식이 대신하기 시작했다.

배포도 달라졌다. FTP로 파일을 올리던 시절과 달리 지금은 Git 저장소를 Vercel, Netlify, Cloudflare 같은 서비스에 연결해두면 push할 때마다 자동으로 빌드되고, PR마다 미리보기 URL이 생긴다. 흥미로운 건 여기서 한 바퀴 돌아 다시 "서버가 렌더링한 HTML을 CDN에 올려둔다"는, 20년 전과 크게 다르지 않은 그림으로 돌아왔다는 점이다.

AI가 코드를 대신 쓰는 지금, 여전히 알아야 하는 것

v0, Bolt, Cursor 같은 도구는 자연어로 원하는 UI를 설명하면 코드를 생성해준다. 문제는 생성된 코드가 앞서 나온 8개 레이어(모듈 시스템, 번들러, SSR, hydration, 배포 파이프라인)를 전부 암묵적으로 전제하고 있다는 점이다. 어떤 코드가 왜 그렇게 생겼는지 모르면 AI가 만들어준 결과물이 왜 안 되는지도 디버깅할 수 없다.

원문 저자의 결론은 20%만 제대로 이해하면 나머지 80%는 상황에 따라 골라 쓰는 뷔페에 가깝다는 것이다. jQuery의 수동 동기화 문제, 번들링이 필요해진 이유, hydration의 비용, 이 세 가지 흐름만 잡아도 지금 나오는 새 도구 대부분을 "또 다른 상처 위의 흉터"로 이해할 수 있다.

원문: David Poblador i Garcia, What happened to the frontend (GeekNews 공유)

댓글

아직 댓글이 없습니다.

댓글 남기기