
제어의 역전(IoC)이란?
우리가 순수 React를 가지고 애플리케이션을 개발했던 때를 생각해보면 마치 빈 땅을 산 건축가와 같습니다.
React는 화면을 그리는 UI라는 벽돌만 제공해주고 집을 짓기 위한 모든 도구를 개발자가 직접 조립해야 했습니다.

이것은 우리에게 무한한 자유를 주었지만, 동시에 설정이라는 늪에 빠지는 특징 중 하나였습니다.
실제로 이러한 라이브러리(Library) 방식의 접근이 코드로 구현되었을 때
페이지 이동 하나를 위해 얼마나 많은 설정을 해주어야 했는지 생각해보면, 프레임워크로서의 Next.js의 필요성이 이해가 됩니다.
즉 라이브러리 방식은 개발자인 내가 코드를 직접 호출하고 제어하는 주도권을 쥐고 있는 형태입니다.
이제 Next.js의 생태계를 살펴보면, 프레임워크는 빈 땅이 아니라 이미 골조와 배관이 다 갖춰진 모델하우스와 같습니다.

Next.js는 바로 제어의 역전이라는 원칙을 따르기 때문인데 쉽게 말해 주도권이 바뀌었다는 뜻입니다.
구조를 살펴보면, 중심에는 프레임워크가 있고, 내 코드는 부품처럼 끼워져 호출 당하는 구조입니다.
우리는 그저 Next.js가 정해진 규칙과 약속에 맞춰 파일을 만들기만 하면 됩니다.
예를 들어, Next.js의 라우팅 구조는 React와 달리 폴더를 만들고 파일을 넣는 행위가 라우팅 설정이 됩니다.
우리는 이로써 어떻게 라우팅을 구현하는지 같은 기술적 고민에서 해방되고
보여줄 화면만 고민하는 비즈니스 로직에만 신경씁니다.
우리가 만들고자 하는 것이 SEO가 주요하고 수십 수만개의 페이지가 필요하다면
Next.js라는 강력한 장비 위에 올라타는 것이 현명할 것입니다.
파일 시스템 라우팅 자동화
우리가 React에서 순수 구현했던 라우팅 시스템에서 복잡했던 것은 수동 지도의 늪이었습니다.
마치 지도에서 경로를 일일히 수첩에 적어놓은 것과 같습니다.

새로운 페이지가 생길 때마다 경로를 다시 그려야 했고, 만약에 오타라도 생기면 404 에러를 마주해야 했습니다.
프로젝트가 커질수록 이 라우트 설정은 거대한 괴물이 되어 개발자의 시야를 가리는 주범이 되었습니다.
모든 페이지 컴포넌트를 직접 불러와야 하고, URL 경로와 1:1로 맵핑해주어야 했습니다.
이 중 오타가 난다면 그것을 디버깅하기도 힘들고, 없는 페이지 처리를 위한 예외 경로까지 직접 관리해야 합니다.
이 중 하나라도 빼먹으면 화면이 그려지지 않습니다.

이제 Next.js의 파일 시스템 라우팅은 마치 모든 지도에 GPS와 자율 주행 센서를 처리한 것과 같습니다.
그 위에 폴더와 파일을 만들기만 하면 자동으로 주소가 부여됩니다.
물리적 위치가 곧 논리적 주소가 되는 직관적인 시스템입니다.
예를 들어, [ id ] 폴더 구조를 통해 동적으로 바뀌는 페이지임을 즉각적으로 이해할 수 있습니다.
즉, Next.js의 라우팅 방식을 선택하는 것은 지루한 설정에서 벗어나, 오직 페이지에 가치와 UX에만 집중하겠다는 것입니다.
랜더링 패턴 동작원리

1. CSR (Client Side Rendering)
사용자가 사이트에 접속하면, 서버는 텅빈 html 파일과 거대한 자바스크립트 파일만 덩그러니 던져줍니다.
브라우저는 설명서(Javascript Bundles)를 다운로드하여 읽고, 맞는지 실행하며 확인한 뒤
필요한 데이터를 찾아오기 위해 API 요청을 보내고, 하나하나 조립하는 랜더링을 시작합니다.
그 과정에서 로딩 시간이 느려 사용자 경험(UX)이 저하되었고
Google Engine은 내용 없음으로 인식하여 SEO 실패의 결과를 받아들여야 했습니다.
예를 들어 초기 상태는 다음과 같이 비어있거나, 로딩 중입니다.
const [data, setData] = useState(null)
const [isLoading, setIsLoading] = useState(true)이후 Javascript 다운로드 -> 컴포넌트 랜더링 -> useEffect 실행 -> API 요청 및 리 랜더링의 폭포수 현상이 발생합니다.
useEffect(() => {
fetch("/api/products").then(res => res.json())
.then(data => {
setData(data);
setIsLoading(false);
})
}, [])사용자는 이 긴 과정을 모두 기다려야 비로소 실행 화면을 맞이합니다.
이후 이러한 문제를 해결하기 위해 SSR이 등장합니다.
2. SSR (Server Side Rendering)
SSR은 서버에서 미리 완성된 HTML을 조립하여 브라우저로 전송하는 방식입니다.
이 때, 조립된 HTML은 화면에 랜더링되어 있으나, 상호작용(Interaction)은 할 수 없습니다.

결국, 기능을 활성화하기 위해 거대한 Javascript Bundlers를 전송해야 하는 무거움은 여전합니다.
3. RSC (React Server Components)
Next.js App Router의 RSC는 서버 코드를 아예 브라우저로 보내지 말자는 아이디어입니다.
RSC는 Node.js 서버 환경에서 실행되므로, 데이터베이스에 직접 접근할 수 있고
아무리 무거운 라이브러리를 설치해도 브라우저로 전송되는 번들 사이즈는 0입니다.
RSC(React Server Component) 방식의 구현 예시를 살펴봅시다.
import db from '@/lib/db';
export default async function ProductPage() {
const data = await db.product.findMany()
return (
<div>
{data.map(item => <div key={item.id}>{item.name}</div>)}
</div>
)
}use client가 없으므로, 오직 서버에서만 실행되는 컴포넌트입니다.
따라서 DB에 직접 연결해도 보안 문제가 없으며, async-await를 컴포넌트 레벨에서 직접 사용해
복잡한 state, effect 훅이 필요없습니다.
서부 내부 통신이므로, 네트워크 지연이 거의 없고, DB 관련 코드 및 로직은 브라우저로 전송되지 않습니다.
브라우저는 오직 결과물인 HTML 구조와 결과물만 받습니다.
참고로, use client는 코드를 브라우저로 보내라는 발송 레벨입니다.
이 RSC에서의 동작 원리는 5단계로 나뉩니다.
- 사용자가 페이지 접속하여 요청(Request)가 시작됩니다.
- 서버 내부에서 컴포넌트가 실행되어 데이터를 즉시 가져옵니다.
- 서버는 컴포넌트의 결과물을 RSC payload라는 JSON 문자열로 변환합니다. (직렬화)
- 이 가벼운 JSON 파일만 네트워크를 타고 브라우저로 보내집니다. (전송 및 반영)
- 브라우저는 이 JSON 파일을 해석해 즉시 화면을 그립니다.
그리하여, 거대한 라이브러리 다운로드 과정이 필요없이 데이터가 즉시 랜더링됩니다.
즉, 개발자는 API 구성 없이 컴포넌트 안에서, 데이터를 직접 조회하는 엄청난 생산성을 얻게되고
민감한 정보가 서버 밖으로 새어나가지 않아 보안성 측면에서도 이득을 얻습니다.
하지만, Next.js는 RSC와 CSR의 하이브리드 엔진이어서 기본적으로 모든 것은 RSC로 간주하지만
상호작용이 필요한 컴포넌트의 경우 use client를 사용해 브라우저로 보내져야 합니다.
하이드레이션(Hydration)
서버에서 막 도착한 직후의 HTML은 사실 죽어있는 상태와 다름 없습니다.
버튼을 눌러도 반응이 없는 상호작용이 불가능한 화면인 것입니다.
여기에 생명을 불어넣는 즉 상호작용할 수 있게 해주는 것이 수화(Hydration) 과정입니다.
바짝마른 정적인 HTML에 물에 비유한 JS를 붓는 과정으로, 이로 인해 애플리케이션이 상호작용이 가능해집니다.

그 과정을 살펴봅시다.
먼저 브라우저에 HTML 뼈대가 도착하면, 사용자는 그 화면을 즉시 볼 수 있는 상태입니다.
그후, 브라우저가 JS 번들 백그라운드를 다운로드합니다.
이후, React가 그려진 HTML 트리를 순회하면서 알맞은 장소에 이벤트 리스너를 끼워 맞춥니다.
이제 모든 결합이 완료되면 비로소 활성화된 애플리케이션이 완성됩니다.
여기서, Next.js는 선택적 Hydration 기술을 사용하여
오직 use client만 붙은 Client Component만 선택하여 Hydration을 수행합니다.
이러한 작업에는 서버에서 내려준 HTML의 모양과 Hydration 후 예상한 HTML의 모양이 일치해야 합니다.
이 때 여기서 mismatch가 발생하면 에러를 내는데, 이것이 Hydration Mismatch Error입니다.
이것을 해결하기 위해 클라이언트 컴포넌트에서 Two-Pass Rendering 전략을 사용합니다.
State로 초기값을 null를 사용해, 서버에 있는 초기값 null과 완전히 일치시키는 것입니다.
그 후 Hydration이 끝난 뒤에 실행되는 useEffect 안에서 원래 데이터를 설정하면 에러를 교묘히 피할 수 있습니다.
두번째로, suppressHydrationWarning 패턴은 Hydration 경고를 억제하는 것입니다.
다만 이 패턴은 태그 구조 자체가 바뀌는 심각한 불일치에는 쓰면 안 됩니다.
이 에러 해결에 실패하면, React는 기존 HTML을 폐기하고 처음부터 다시 랜더링하는 비효율성을 맞이합니다.
네비게이션 경험(Navigation Experience)
먼저 우리가 전통적인 a 태그를 사용했던 생태계는 하드 네비게이션 방식을 이용합니다.
a 태그를 클릭하면 브라우저는 현재 보고 있던 페이지를 완전히 파괴합니다.
그리고 서버에 새로운 HTML 문서를 요청한 뒤, 응답이 오면 처음부터 다시 그리기 시작합니다.

이 때 발생하는 것이 화면 깜빡임 현상이며, 그동안 메모리 상의 데이터는 모두 초기화됩니다.
그렇다면 React Router도 소프트 네비게이션 방식을 사용하는데 왜 우리는 Next.js 방식을 써야 할까요?
그것은 내부 동작 원리에 있는데, 일반 React에서는 페이지를 이동할 때 브라우저가 서버에서 JSON만 받아옵니다.
화면 자체가 아니라 원재료만 받아오는 것입니다. 이것은 화면이 온 것이 아닙니다.
즉 다시 이 상황에서 브라우저는 조립하는 과정을 거쳐야 합니다.
반면, Next.js가 제공하는 Link 컴포넌트의 소프트 네비게이션 방식은
JSON이 아닌 RSC payload라는 특수한 JSON을 요청합니다.
여기서 Next.js는 공장에서 조립된 완제품 가구를 보내준다고 생각하면 편합니다.
구체적인 지시서와 완성된 부품이 함께 담겨 있어, 브라우저는 지시서대로 위치만 바꾸기만 하면 됩니다.
이것이 Next.js가 이렇게 더 적은 Javascript 파일로 더 빠르게 화면을 그리는 비결입니다.

특히 Next.js의 Link 컴포넌트는 프리 패칭 전략을 사용하여, 사용자가 클릭하기 전 미리 데이터를 가져옵니다.
링크가 뷰포트에 들어오는 것을 감지하면, 클릭하기도 전에 리모델링 지시서(RSC payload)를 미리 다운로드합니다.
즉 클릭 시 네트워크 대기 시간없이 즉시 화면 전환이 가능합니다.
그리고, 랜더링 시에는 a 태그로 랜더링되어 SEO 측면에서 a 태그를 따라, 크롤링이 가능합니다.
즉 Next.js는 검색 엔진에게는 전통적인 웹처럼 보이고, 사용자에게는 앱처럼 느껴지는 두마리 토끼를 잡은 셈입니다.
