매우매우 주관적인 글입니다. 저는 정답이 아닙니다. 제가 느낀 거를 작성한 일기라 생각하고 봐주세용
안녕하세요. 저는 next 13.0.0에서부터 production 환경에서 app router를 운영했던, app router를 가장 오랜 기간 사용한 사람 중 한 명입니다. 소박한 Next.js Contributor이기도 하죠.
현재의 app router에서 사용하는 메모리보다 2배 이상 소모될 때부터, 서버 컴포넌트에서 컴포넌트별 ISR이 되지 않았을 때부터 사용하고 있었죠. 물론 현재는 이와 같은 문제가 많이 해결된 상태입니다. 그래서 13.0.0이 나온 지 어느덧 2년이 넘었고, 이제는 사이드 프로젝트에서도, 기업에서도 app router를 많이 채택하고 있습니다.
그렇다면 이 app router, 이제는 쓰면 될까요?
뭐 제가 쓰란다고 쓰고, 제가 쓰지 말라고 안 쓰겠습니까.. 그냥 개인적으로 제가 느끼는 것을 얘기해보려고 합니다. 저는 app router를 사용하는 데에는 많은 트레이드오프를 고려해야 된다고 생각합니다.
먼저 app router를 사용했을 때 얻게 되는 이점은 어떤 게 있을까요? 가장 큰 장점은 컴포넌트별 렌더링 전략을 선택할 수 있다는 것이죠. 기존에는 page 단위로 통째로 SSR, ISR, SSG를 선택해야 했던 것을 컴포넌트별로 선택할 수 있다는 점이 매우 인상적입니다.
또 Parallel Routes, Intercepting Routes, Server Action, fetch 병렬 처리 같은 app router에서만 되는 특수 기능들도 제공하고 있습니다.
그리고 서버 컴포넌트를 이용했을 때 번들 사이즈가 전혀 없다라는 점도 있겠군요.
그렇다면 app router를 썼을 때 단점은 뭐가 있을까요? 사실 저는 이것을 말하려고 이 글을 작성하고 있습니다 :)
먼저 위에 작성한 저 이점들을 크게 이용하고 있지 않습니다.
한 페이지에 ISR, SSR, SSG를 선택적으로 여러 개 이용하는 경우는 크게 없기 때문이죠.
Parallel Routes, Intercepting Routes도 유사합니다. 사용할 일도 그렇게 크게 많지 않으며, 충분히 이러한 UI 형태를 구현하기 위해 app router의 이 기능들을 사용하지 않아도 됩니다. Server action은 Next.js 하나로만 프론트, 백엔드를 모두 처리하는 상황이 아니면 크게 사용할 일은 없다고 생각됩니다.
또한 서버 컴포넌트에 우리가 실제로 넣을 수 있는 DOM은 그렇게 많지 않으며, 이로 얻을 수 있는 zero-bundle의 이점은 거의 없다고 생각됩니다.
또한 단점은 계속 쏟아집니다.
app router의 사용을 위해 아예 스타일 라이브러리를 변경하는 경우도 있습니다.
기존에 scss, module css 같이 사용을 해왔다면 page router → app router의 변경에도 그렇게 critical하지는 않을 것입니다.
하지만 styled-components, emotion과 같은 runtime css-in-js 들을 사용했을 때는 이를 서버 컴포넌트에서 사용할 수 없습니다. 그렇기에 서버 컴포넌트는 오직 데이터를 fetch하고 이를 client component에 넘겨주는 것밖에 할 수 없게 되죠.
그렇다고 이 app router 사용을 위해 기존에 사용하던 스타일 라이브러리를 scss, module css 혹은 tailwind css, panda css, vanilla-extract로 바꾸는 건 옳을까요? 물론 app router를 그 정도로 이용해야 될 이유가 있다면 괜찮다고 생각하지만, 기존에 쓰던 스타일 라이브러리를 모두 마이그레이션하면서까지 app router의 서버 컴포넌트에 스타일을 입혀야 할까요??
또한 공통 라이브러리에서 app router를 한 번 더 고려해야 된다는 것도 생각보다 꽤 큰 문제입니다.
대부분의 회사가 app router를 쓰더라도 page router + app router라고 생각됩니다. 모든 페이지가 app router로 마이그레이션된 곳은 크게 없다고 생각합니다. 이렇게 page router + app router로 되는 순간부터 공통적으로 사용하고 있는 라이브러리에서는 고려해야 될 부분이 2배가 됩니다.
일단 page router, app router 모두 Next.js라는 하나의 프레임워크에 의존성을 가지고 있지만, 단지 어떤 router를 사용하느냐에 따라 내부에서 참조하는 메서드들은 달라지기 때문입니다.
예를 들어 page router에서는 next/router
를 이용해 router 처리를 하는데, app router에서는 next/navigation
을 이용해 router 처리를 하죠. 그리고 next/navigation
의 router에서는 router.on
과 같은 router에 이벤트를 걸 수 없어 page router에서 처리하던 방식을 그대로 사용할 수 없죠.
또한 page router에서는 req를 통해 안에서 header 정보들을 가져와 처리하는데, app router에서는 headers()
, cookies()
라는 메서드를 제공하여 처리를 하죠.
그리고 compound component pattern과 같이 <Select.Option>
처럼 사용하는 dot notation이 app router에서는 오류를 내뱉는 문제도 존재합니다. 이러한 이슈 때문에 이미 다른 radix ui와 같은 UI 라이브러리에서는 실제로 app router 대응을 위해서 dot notation을 떼내는 처리도 진행하였습니다. 그렇기에 사내 공통 라이브러리에 compound component pattern으로 dot notation을 사용했다면 제거해야 되는 문제도 존재합니다.
이는 생각보다 공통 라이브러리를 개발할 때 많이 피로를 가져오게 됩니다. Next.js에 의존이 없는 게 최고의 라이브러리겠지만, 어쩔 수 없이 의존이 생기는 경우에는 app router, page router 모두 고려하여 2벌을 만들어야 되기 때문이죠.
무엇보다 그냥 개발이 피로합니다.
프레임워크를 사용한다.
이는 정말 개발의 용이성을 위해서 사용한다고 생각합니다. 실제로 Next.js 같은 경우 내부적으로 제공하고 있는 최적화 옵션이나 next.config.js
로 모든 걸 제어하는 편의성은 개발을 많이 편하게 하죠.
하지만 app router는 기본적으로 server component로 사용되며, client component 이용을 위해서는 use client
를 붙여야 하는 번거로움이 존재하며, app router를 사용하는 가장 큰 이유인 server component
를 사용한 순간부터 개발의 난이도가 확 올라가게 됩니다.
먼저 최상위 page.tsx
, layout.tsx
는 반드시 서버 컴포넌트로 유지되어 있어야 할 것, 서버 컴포넌트를 이용했다면 클라이언트 컴포넌트에서는 서버 컴포넌트를 import할 수 없기에 반드시 children으로 넣어야 할 것, 이로 인해 하나의 컴포넌트를 만들 때 정말 데이터를 뿌려주는 server component wrapper가 존재하고, 클라이언트 컴포넌트는 그 데이터를 props에서 받아서 그리기만을 진행하죠. 즉, 컴포넌트 하나를 구현하기 위해 쓸데없이 2개의 다른 컴포넌트가 생겨나게 됩니다.
이로 인해서 처음 app router를 개발하는 사람 입장에서는 실수를 많이 진행하고는 합니다.
예를 들어 page.tsx
, layout.tsx
를 실수로 클라이언트 컴포넌트로 두고 시작하곤 합니다. 이런 경우에는 최상위 컴포넌트가 클라이언트 컴포넌트가 됐기에 절대로 이 route에서는 서버 컴포넌트를 이용할 수 없습니다. (클라이언트 컴포넌트에서는 서버 컴포넌트 import 불가)
그리고 서버 컴포넌트는 클라이언트 컴포넌트에서 반드시 children으로 사용해야 된다는 룰이 있는데, 이는 사실 안 지킨다고 해서 런타임에서 아무런 에러를 내지 않습니다. 그냥 아무도 모르게 동작이 이상하게 됩니다. 서버 컴포넌트인 줄 알고 썼는데 클라이언트 컴포넌트처럼 동작하고는 하죠. Next.js는 이를 에러로 잡아주지 않습니다.
자 여기서 의견을 정리해보겠습니다.
보통 회사에서 app router를 사용하게 되면 page router와 함께 공존하는 경우가 대부분입니다.
새로운 기능 추가 시 *folder라도 생길지 모름...
아무튼 전 다음과 같은 이유로 app router 사용을 하고 싶지 않습니다. 제가 아직까지 느꼈던 경험으로는 app router를 쓸 이유가 쓰고 싶지 않은 이유보다 크지 않아 보이기 때문입니다.
하지만 app router를 안 쓴다? 그거는 어려울지도 모릅니다.
Next.js 팀은 이제 page router는 단순한 유지보수, 그리고 앞으로 새로운 기능 추가, 개선 같은 것들은 app router 중심적으로 될 예정이기 때문이죠. 하지만 라이브러리가 더 이상 개선되지 않는다… 그건 죽은 라이브러리와 같죠. 결국 Next.js는 점진적인 app router 전환으로 모두를 이끌 것입니다. 아예 React 팀과 협업을 하여 server component를 이끌어가기 때문에 이 app router를 영원히 배척하겠다는 것도 참 어려워 보입니다.
마치 yarn v1, 그리고 yarn berry를 보는 것 같습니다. 대부분의 사람들이 아직 많이 이용하지만 업데이트는 유기된 yarn v1, 그리고 앞으로 모든 업데이트는 zero-install을 내세운 pnp 전략을 지닌 yarn berry가 받게 되죠… 이와 다를 게 없어 보입니다.
개발자들은 yarn berry의 pnp 기능의 사용하기 어려움과 .yarn/cache
의 git 저장소에서의 관리, 가끔 충돌하는 의존성 문제로 인해 꺼려하곤 합니다. 그렇기에 그냥 yarn v1도 yarn berry도 아닌 pnpm
을 많이 이용하곤 합니다.
이와 같이 Next.js도 사용하기에는 불편하게 하면서 기존 page router 대신 app router를 계속 발전시킨다면 pnpm
마냥 Next.js 대신에 다른 것을 이용하는 게 좋을지도 모릅니다.
앞에서 얘기했듯 사실 app router에서 더 나아가 Next.js를 써야 할까… 라는 의문이 들곤 합니다.
뭐 그거 외에도 단점이라면 얼마든지 있습니다..
서버를 사용한다.
이 하나만으로 프론트에서 신경 써야 할 것이 매우 많아진다. 백엔드 서버 운영함과 유사하게 프론트엔드의 서버도 지속적인 모니터링이 필요하고, 이에 대응하는 인프라 설정 역시 요구된다. 물론 서버에 대한 추가적인 비용은 당연히 보너스next/image
와 같이 성능 최적화하는 것을 제공하면서 그 최적화를 SSR 서버와 동일한 서버가 한다는 거를 잊어서는 안 된다. 최적화를 하는 거는 공짜가 아니다. 그것도 Next의 서버가 하는 것이다. SSR과 이미지 최적화가 하나의 서버에서 같이 처리되고 있다면 메모리 이슈는 물론, 사용자가 많아질 경우 둘 중 하나는 하자가 생길 수 있다.뭐 이렇게는 작성했지만 SEO가 정말 정말로 중요한 프로젝트 같은 경우에는 Next.js를 사용할 듯합니다. SEO가 별로 필요 없다면 무조건 React를 사용할 듯하고요. 물론 여유가 있어, 회사에서 내부 SSR 서버를 직접 node로 구축할 수 있다면 Next.js를 버리고 React + SSR 서버 스택으로 갈 듯합니다.
여러분들도 일단 Next.js를 사용하기보다는, 굳이 SEO가 필요하지 않아도 된다면 React를 사용해보면 어떨까요?
한번 더 생각하게 되네요 ..