"그냥 use server 붙이면 되잖아요" — 그게 함정입니다
Next.js App Router의 Server Action은 처음 보면 마법 같습니다. "use server" 한 줄이면 서버 함수가 브라우저에서 호출 가능한 API로 변환되고, FormData 파싱도, API 라우트 선언도 필요 없습니다. velog 기술 블로그에서 정리한 것처럼, 컴파일 결과 자동으로 해시값을 가진 엔드포인트가 생성되고 form의 action 속성에 함수를 그냥 꽂으면 됩니다. 분명 편합니다.
근데 저는 이걸 쓸수록 의심이 생깁니다. "이 추상화, 어디까지 믿을 수 있는 거야?"
실제 구현 레벨로 내려가면 이야기가 달라집니다. revalidatePath와 revalidateTag의 캐시 무효화 전략을 잘못 선택하는 순간, 페이지 전체가 날아갈 수도 있습니다. 특정 경로만 재검증하고 싶었는데 레이아웃 단위로 지정하면 그 레이아웃 하위 모든 페이지의 풀라우트 캐시가 PURGE됩니다. revalidatePath("/", "layout")은 사실상 전체 캐시 초기화입니다. Figma에서 디자인 볼 때는 몰랐는데 실제 트래픽 들어오면 체감되는 그 느낌이요.
useActionState — 편의성인가, 또 다른 레이어인가
클라이언트 컴포넌트에서 Server Action을 쓰려면 useActionState 훅이 필요합니다. [state, formAction, isPending] 이 세 개를 받아서 중복 제출 방지, 에러 핸들링, 로딩 UI를 처리하는 구조입니다. 이 정도면 괜찮습니다. 실제로 isPending으로 버튼 disabled 처리하고, useEffect로 에러 상태 감지하는 패턴은 깔끔합니다.
문제는 Server Action 함수 시그니처가 useActionState에 맞게 수정되어야 한다는 점입니다. 첫 번째 인수가 이전 state가 되고, 반환값도 state 객체 형태로 바뀝니다. "잠깐, 이 함수 지금 두 군데서 쓰이는 거 아닌가요?" 하고 물으면 — 맞습니다. 서버 사이드와 클라이언트 사이드의 호출 컨벤션이 달라서, 재사용성이 묘하게 흔들립니다. 추상화가 깊어질수록 디버깅할 때 '이 함수가 어느 컨텍스트에서 실행되는 건지' 추적하는 비용이 올라갑니다.
Parallel Routes × Intercepting Routes = 폴더 구조 지옥
App Router의 고급 라우팅 패턴은 더합니다. Parallel Routes(@slot 폴더)와 Intercepting Routes((.) 접두어 폴더)를 조합하면 인스타그램 스타일의 모달 UX — 피드 클릭 시 모달, URL 직접 접근 시 풀페이지 — 를 구현할 수 있습니다. 이건 진짜 강력합니다. URL 상태와 UI 상태가 동기화되고, 새로고침해도 맥락이 유지되는 것은 사용자 입장에서 명확한 가치입니다.
하지만 폴더 구조를 실제로 만들어보면 이야기가 다릅니다. @modal, (.)feed, default.tsx, page.tsx가 중첩되면서 파일 트리가 의미를 잃기 시작합니다. "이 default.tsx가 없으면 새로고침 시 에러 난다"는 사실을 팀원이 모르는 순간, 프로덕션 버그가 됩니다. 컴포넌트로 처리할 수 있는 걸 굳이 라우팅 레벨로 끌어올린 건지, 아니면 URL 동기화가 진짜 필요한 케이스인지 — 기획자가 이걸 의도한 건지 매번 다시 확인하게 됩니다.
Cloudflare가 1주일 만에 "더 가벼운 Next.js"를 만든 이유
이 맥락에서 Cloudflare의 vinext 프로젝트는 단순한 실험이 아닙니다. Cloudflare 엔지니어가 Claude를 활용해 일주일 만에 Next.js의 핵심 API — Routing, RSC, SSR, Server Actions, next/link, next/navigation — 를 Vite 플러그인 형태로 재구현했습니다. 결과는 충격적입니다: 빌드 속도 최대 4배, 클라이언트 번들 크기 57% 감소.
동기가 명확합니다. Next.js는 자체 빌드 도구인 Turbopack에 강하게 묶여 있어서, Cloudflare Workers나 AWS Lambda 같은 서버리스 환경에 배포하려면 OpenNext 같은 복잡한 어댑터를 거쳐야 합니다. 프레임워크가 배포 환경을 제한하는 역설적인 구조입니다. 저 번들 57% 축소 수치 — Lighthouse 돌릴 때마다 번들 사이즈 보면서 속 쓰리던 분들은 이게 얼마나 큰 숫자인지 바로 옵니다.
기술적으로는 Next.js 결과물을 수정하는 방식이 아닌, 처음부터 Vite 위에 Next.js API surface를 다시 구현했습니다. 포팅된 Vitest 1,700개 이상, Playwright E2E 380개 테스트로 검증하고 Next.js 16 API 호환성 94%를 달성했습니다. AI 토큰 비용 약 $1,100로 일주일 만에. 이게 무서운 건 '대단한 결과물'이 아니라, "거대한 프레임워크도 테스트와 AI 조합으로 단기간에 경량화 대체 가능하다"는 증명이기 때문입니다.
그래서, 지금 App Router를 어떻게 봐야 하나
Next.js App Router는 분명히 강력합니다. Server Action의 추상화, RSC의 렌더링 전략, 고급 라우팅 패턴 — 잘 쓰면 UX 품질이 올라갑니다. 하지만 "잘 쓰려면" 알아야 할 것들이 기하급수적으로 늘어납니다. 캐시 레이어의 동작 방식, revalidate 전략, 폴더 구조 컨벤션, 클라이언트/서버 컴포넌트 경계 — 이게 전부 암묵지로 팀에 쌓여야 굴러갑니다.
vinext가 던지는 질문은 결국 이겁니다. "이 복잡도, 프레임워크가 가져가야 하는 건가, 아니면 개발자가 통제권을 가져가야 하는 건가?" 번들 57% 축소는 숫자가 아니라 답변입니다. 당장 vinext로 마이그레이션하자는 게 아닙니다. 하지만 App Router를 도입할 때 "이걸 왜 이렇게 설계했을까?"를 팀 전체가 이해하지 못한 채 쓰고 있다면, 지금이 점검 타이밍입니다. 로딩 스켈레톤 하나 넣는 것보다, 라우팅 전략 한 번 다시 검토하는 게 더 급할 수 있습니다.