학습 자료

React만으로는 부족한 것


Article

React 편 2강에서 이상한 걸 봤다. Vite로 만든 프로젝트의 index.html텅 비어 있었다.

<body>
  <div id="root"></div>
  <script type="module" src="/src/main.jsx"></script>
</body>

그때는 "화면은 전부 자바스크립트가 채운다"고만 하고 넘어갔다. 이번 강의는 거기서 시작한다. 그게 왜 문제인가.

검색엔진이 보는 것

구글 크롤러가 이 페이지를 가져간다고 하자. HTTP 요청을 보내면 서버는 저 HTML을 그대로 돌려준다.

크롤러가 받은 것
  • <body>
  • <div id="root"></div>
  • <script src="/src/main.jsx"></script>
  • </body>
  • 제목도, 본문도, 링크도 없다

아무 내용이 없다. 상품 이름도, 블로그 글도, 아무것도 없다.

크롤러가 자바스크립트를 실행해주기를 기대할 수는 있다. 실제로 구글은 실행한다. 하지만 시간이 오래 걸리고, 늘 성공하지도 않으며, 다른 검색엔진과 SNS 크롤러는 대부분 실행하지 않는다.

카카오톡에 링크를 붙여넣었을 때 미리보기가 안 뜨는 이유가 이것이다.

사용자가 겪는 것

SEO만 문제가 아니다. 첫 화면이 뜨기까지 무슨 일이 벌어지는지 보자.

빈 HTML 로 시작할 때 (CSR)
HTML 받기
빈 껍데기
JS 내려받기
번들 300KB
JS 실행
React 시작
데이터 요청
useEffect → fetch
화면
흰 화면보임
요청JS 실행데이터 도착
느린 네트워크에서는 몇 초 동안 흰 화면만 보인다

React 편 15강에서 useEffect로 데이터를 받아왔다. 그건 화면이 그려진 뒤에 실행된다. 그래서 순서가 이렇게 된다.

폭포처럼 이어지는 대기
  1. 01

    HTML 을 받는다

    빈 div 하나뿐이다. 화면은 흰색이다.

  2. 02

    자바스크립트 번들을 내려받는다

    React 와 우리 코드 전부. 이게 끝나야 아무것도 시작된다.

  3. 03

    React 가 실행되어 화면을 그린다

    이제 로딩 스피너가 보인다.

  4. 04

    그제야 데이터를 요청한다

    useEffect 가 실행된다. 서버 응답을 또 기다린다.

각 단계가 앞 단계를 기다린다. 이걸 요청 폭포(waterfall)라 부른다.

서버에서 미리 그려두면

해결책은 단순하다. 서버가 완성된 HTML을 보내주면 된다.

<body>
  <h1>노트북</h1>
  <p>1,200,000원</p>
  <button>담기</button>
</body>

크롤러는 자바스크립트 없이도 내용을 읽는다. 사용자는 흰 화면 대신 글자를 즉시 본다.

누가 HTML 을 만드는가

CSR

Client Side Rendering

  • 브라우저가 만든다
  • HTML 은 빈 껍데기
  • JS 를 받고 실행해야 보인다
  • 크롤러가 내용을 못 읽는다

SSR

Server Side Rendering

  • 서버가 만들어 보낸다
  • HTML 에 내용이 있다
  • 즉시 보인다
  • 크롤러가 읽는다

그래서 SSR은 CSR을 없애는 게 아니라 앞에 한 단계를 덧붙이는 것이다. 첫 화면은 서버가, 이후 상호작용은 브라우저가 맡는다.

직접 만들면 어떻게 되나

React를 서버에서 실행하는 건 가능하다. renderToString()이라는 함수가 있다.

import { renderToString } from "react-dom/server";

const html = renderToString(<App />);

이 한 줄로 끝날 것 같지만, 실제로 돌아가는 앱을 만들려면 이런 것들이 따라온다.

직접 만들면 챙겨야 할 것들
  • SSR 을 직접 구현한다면
    • 서버 라우팅어느 URL 에 어느 컴포넌트를
    • 데이터 미리 받기렌더링 전에 fetch 를 끝내야 한다
    • 번들링서버용 · 클라이언트용 두 벌
    • 코드 분할페이지마다 필요한 JS 만
    • 하이드레이션서버 HTML 과 클라이언트가 어긋나면 깨진다
    • 캐싱매 요청마다 다시 그리면 서버가 죽는다
    • 이미지 · 폰트 최적화직접

전부 만들 수 있다. 다만 그게 우리 앱의 기능은 아니다. 매번 똑같이 반복되는 일이다.

Next.js 가 하는 일

Next.js는 이 목록을 전부 가져간다.

필요한 것Next.js 가 주는 것
라우팅폴더 구조가 곧 URL
서버 렌더링기본값. 아무것도 안 해도 된다
데이터 가져오기컴포넌트에서 바로 await
번들링 · 코드 분할Turbopack 이 자동으로
캐싱"use cache" 한 줄
이미지 · 폰트next/image · next/font
SEO 메타데이터metadata 객체를 내보내면 끝

React 편 1강에서 **"React는 라이브러리다"**라고 했다. 화면 그리는 일만 하고 나머지는 직접 고르라는 뜻이었다.

Next.js는 프레임워크다. 나머지를 대신 정해준다.

라이브러리와 프레임워크

React

라이브러리

  • 화면 그리기만 한다
  • 라우팅 · 통신 · 빌드는 직접
  • 내가 React 를 부른다
  • 자유롭지만 결정할 게 많다

Next.js

프레임워크

  • 앱의 구조를 정해준다
  • 규칙대로 파일을 두면 동작한다
  • Next.js 가 내 코드를 부른다
  • 편하지만 규칙을 배워야 한다

"내가 부르는가, 불리는가"가 둘을 가르는 기준이다. Next.js에서는 page.tsx라는 이름으로 파일을 두면 Next.js가 알아서 그 파일을 찾아 실행한다. 우리가 부르지 않는다.

무엇을 얻고 무엇을 잃나

정직하게 짚는다.

얻는 것잃는 것
SEO 와 링크 미리보기서버가 필요하다 (정적 배포만으로 부족할 때가 있다)
빠른 첫 화면배울 규칙이 늘어난다
최적화가 기본값프레임워크의 방식을 따라야 한다
서버 코드와 클라이언트 코드가 한 곳에그 경계를 늘 의식해야 한다

마지막 줄이 이 시리즈의 절반을 차지한다. 어디까지가 서버이고 어디부터가 브라우저인가. Part 3에서 본격적으로 다룬다.

이 시리즈가 다루는 것

Next.js에는 두 가지 라우터가 있다. 옛날 방식인 Pages Router와 지금 방식인 App Router다.

이 시리즈는 App Router만 다룬다. 새 프로젝트는 전부 이쪽이고, 서버 컴포넌트를 비롯한 최신 기능도 여기에만 있다.

기준 버전은 Next.js 16이다. 이 버전에서 꽤 많은 것이 바뀌었다.

Next.js 16 에서 달라진 것들 (미리 훑기)
  1. 01

    Turbopack 이 기본 번들러가 됐다

    빌드가 2~5배, 새로고침이 최대 10배 빨라졌다. 설정할 게 없다.

  2. 02

    캐싱이 완전히 opt-in 으로 바뀌었다

    예전에는 Next.js 가 알아서 캐싱했다. 이제는 "use cache" 를 쓴 곳만 캐싱된다. 13강에서 다룬다.

  3. 03

    middleware.ts 가 proxy.ts 로 바뀌었다

    이름과 실행 환경이 명확해졌다. 18강에서 다룬다.

  4. 04

    params 와 cookies 를 반드시 await 해야 한다

    동기 접근이 완전히 사라졌다. 5강에서 만난다.

인터넷에 있는 예전 글이 지금 버전과 다르게 말하는 경우가 많다. 이 시리즈는 16 기준으로만 쓴다.

정리하면

  • React만 쓰면 브라우저가 HTML을 만든다. 그래서 크롤러가 빈 페이지를 본다.
  • 사용자도 손해를 본다. JS를 받고 실행하고 데이터를 요청하는 동안 흰 화면을 본다.
  • 서버가 완성된 HTML을 보내면 둘 다 해결된다. 이후 자바스크립트가 이벤트를 붙이는 과정이 하이드레이션이다.
  • SSR을 직접 만들 수는 있지만, 우리 앱의 기능이 아닌 일에 시간을 쓰게 된다.
  • React는 라이브러리, Next.js는 프레임워크다. 내가 부르는가, 불리는가의 차이다.
  • SEO가 필요 없는 앱이라면 순수 React로 충분하다.
  • 이 시리즈는 App RouterNext.js 16만 다룬다.

다음 강의에서는 프로젝트를 만들고, 폴더를 만드는 것만으로 URL이 생기는 걸 본다.

React만으로는 부족한 것 — 디코드랩(DCODELAB)