사전 서버 컴포넌트
개념

서버 컴포넌트

gabury1고친 사람 github-actions[bot]

서버 컴포넌트는 화면의 한 조각을 서버에서만 만들어 그 결과를 브라우저로 보냅니다. 그 조각을 만든 코드는 브라우저로 내려가지 않습니다. 그래서 서버가 데이터베이스를 바로 읽어 화면을 채울 수 있습니다. 브라우저가 내려받는 자바스크립트도 그만큼 줄어듭니다.

쉽고 빠른 이해

서버 컴포넌트는 화면 조각을 서버가 다 그려서 결과만 넘기는 방식입니다. 쇼핑몰의 상품 목록이라면 서버가 상품을 읽어 목록을 완성합니다. 브라우저는 그 목록을 받아 띄우기만 합니다.

이렇게 하지 않으면 브라우저가 화면 만드는 코드를 먼저 내려받아야 합니다. 그 코드를 돌린 뒤에야 서버에 다시 상품을 물어볼 수 있습니다. 코드가 커질수록, 오가는 횟수가 늘수록 화면이 늦게 뜹니다.

어떻게 도는가:

  1. 요청이 오면 서버가 서버 컴포넌트를 실행합니다. 서버 컴포넌트는 그 안에서 데이터를 읽습니다
  2. 서버는 다 그린 결과를 화면 설명으로 바꿔 브라우저로 보냅니다. 화면 설명은 웹 페이지 문서가 아니라, 그린 결과를 글자로 적어 둔 것입니다
  3. 브라우저는 그 설명대로 화면을 붙입니다. 버튼처럼 클릭에 반응해야 하는 조각은 코드만 따로 받아 돌립니다

대가도 있습니다. 서버 컴포넌트는 클릭에 반응하거나 값을 기억할 수 없습니다. 그리고 지금 쓰는 코드가 서버에서 도는지 브라우저에서 도는지를 늘 가려야 합니다.

상세

밀키트와 식당을 견줘 봅니다. 밀키트는 재료와 조리법을 집으로 보냅니다. 요리는 손님이 냄비를 꺼내 직접 합니다. 식당은 주방에서 요리를 끝내고 완성된 접시만 내옵니다. 손님은 조리법을 볼 일도 냄비를 꺼낼 일도 없습니다.

화면을 만드는 방식도 이렇게 둘로 갈립니다. 브라우저에 코드를 보내 거기서 화면을 만들게 하는 쪽이 밀키트입니다. 서버에서 화면을 다 만들고 결과만 보내는 쪽이 식당입니다. 서버 컴포넌트는 이 식당 쪽에 섭니다.

이 절은 먼저 컴포넌트가 무엇인지 짚습니다. 그다음 서버 컴포넌트가 무엇을 바꿨는지를 옛 방식 둘과 견줍니다. 끝으로 코드 한 벌과 그림 두 장으로 동작을 보입니다.

컴포넌트와 서버 컴포넌트

컴포넌트는 화면을 이루는 조각 하나를 함수 하나로 만든 것입니다. 입력을 받아 그 조각의 생김새를 돌려줍니다. 상품 카드라면 상품 이름과 가격을 받아 카드 한 장의 모양을 돌려주는 함수입니다.

화면 전체는 이런 컴포넌트를 나무처럼 엮어 만듭니다. 페이지 컴포넌트 아래에 머리글과 상품 목록이 붙고, 상품 목록 아래에 상품 카드가 여러 장 붙습니다. 이렇게 엮인 모양을 컴포넌트 트리라고 부릅니다.

서버 컴포넌트는 서버에서만 실행되는 컴포넌트입니다. 실행 결과만 브라우저로 가고, 함수의 코드는 서버에 남습니다. 이 이름과 구조는 React 가 들여왔습니다. React 는 화면을 컴포넌트로 짜는 자바스크립트 라이브러리입니다.

그 반대편은 클라이언트 컴포넌트입니다. 코드가 브라우저로 내려가 브라우저에서도 실행되는 컴포넌트입니다. 클릭에 반응하는 버튼이나 글자를 받는 입력창이 여기에 속합니다. 서버 컴포넌트가 나오기 전에는 모든 컴포넌트가 이쪽이었습니다.

브라우저가 화면을 만들던 방식

서버 컴포넌트가 무엇을 덜어 냈는지 보려면 앞선 방식부터 봐야 합니다. 첫째는 브라우저가 화면을 전부 만드는 클라이언트 사이드 렌더링입니다.

이 방식에서 서버는 거의 빈 문서와 자바스크립트 파일 하나를 보냅니다. 이 파일에는 화면의 모든 컴포넌트 코드와 그 코드가 쓰는 라이브러리가 함께 묶여 있습니다. 여러 코드를 파일 하나로 묶은 것을 번들이라고 합니다.

브라우저는 번들을 내려받아 실행합니다. 실행된 컴포넌트는 그제야 API(Application Programming Interface, 애플리케이션 프로그래밍 인터페이스)로 서버에 데이터를 요청합니다. 응답이 와야 화면이 채워집니다.

여기에는 문제가 둘 있습니다. 하나는 번들의 크기입니다. Moment.js 같은 날짜 형식 라이브러리 하나를 써도 그 코드가 전부 번들에 실려 모든 방문자에게 내려갑니다. 다른 하나는 차례입니다. 코드를 받고, 실행하고, 데이터를 물어보는 일이 하나씩 끝나야 다음이 시작합니다.

서버 사이드 렌더링과 다른 대목

둘째 방식인 서버 사이드 렌더링은 첫 화면을 서버가 만듭니다. 서버가 컴포넌트를 실행해 HTML(HyperText Markup Language, 하이퍼텍스트 마크업 언어)을 만들어 보내므로 브라우저는 받자마자 내용을 띄웁니다.

하지만 컴포넌트 코드는 여전히 번들에 실려 브라우저로 갑니다. 브라우저는 받은 HTML 위에서 같은 컴포넌트를 한 번 더 실행합니다. 버튼마다 클릭에 반응하는 코드를 붙이려는 것입니다. 이렇게 서버가 만든 화면에 반응하는 코드를 다시 붙이는 일을 하이드레이션이라고 합니다.

그래서 서버 사이드 렌더링은 첫 화면을 빨리 띄워도 번들 크기는 줄이지 못합니다. 서버 컴포넌트는 여기서 갈라집니다. 서버 컴포넌트의 코드는 번들에 아예 안 실립니다. 브라우저에서 하이드레이션도 받지 않습니다.

데이터를 읽는 방법도 갈립니다. 서버 사이드 렌더링에서는 같은 컴포넌트가 브라우저에서 한 번 더 돌기 때문에, 컴포넌트 안에 데이터베이스를 읽는 코드를 둘 수 없습니다. 그래서 서버가 컴포넌트 밖에서 데이터를 읽어 넘겨 줍니다. 서버 컴포넌트는 서버에서만 도니 컴포넌트 안에서 바로 읽습니다.

아래 표는 세 방식을 한 줄씩 놓은 것입니다.

클라이언트 사이드 렌더링 서버 사이드 렌더링 서버 컴포넌트
컴포넌트를 실행하는 곳 브라우저 서버, 그리고 브라우저에서 한 번 더 서버만
브라우저가 받는 컴포넌트 코드 전부 전부 클라이언트 컴포넌트 것만
데이터를 읽는 방법 브라우저가 API 로 요청 서버가 컴포넌트 밖에서 읽어 넘김 서버가 컴포넌트 안에서 바로 읽음
클릭에 반응 된다 하이드레이션 뒤에 된다 안 된다

마지막 줄이 서버 컴포넌트의 한계를 보여 줍니다. 그래서 서버 컴포넌트만으로 화면을 다 짜지 않고, 반응이 필요한 조각만 클라이언트 컴포넌트로 남깁니다.

서버 컴포넌트 한 벌

이 소절은 데이터베이스에서 상품 이름을 읽어 목록을 만드는 서버 컴포넌트 하나를 코드로 봅니다. 코드는 JSX(JavaScript XML, XML 은 Extensible Markup Language)로 적었습니다. JSX 는 자바스크립트 안에 HTML 과 비슷한 꼴로 화면 모양을 적는 문법입니다.

jsx
async function ProductList() {
  const rows = await db.query(
    "select name from product");
  return (
    <ul>
      {rows.map(r => <li>{r.name}</li>)}
    </ul>
  );
}

이 함수는 데이터베이스를 함수 안에서 바로 읽습니다. 브라우저 코드였다면 데이터베이스에 바로 닿을 수 없어 API 를 하나 더 만들어야 했습니다. 서버 컴포넌트는 서버에서 도니 백엔드 코드처럼 데이터베이스를 읽습니다.

브라우저로 가는 것은 함수가 돌려준 결과뿐입니다. 상품이 셋이면 이름 세 줄이 든 목록 하나가 갑니다. 쿼리 문장도, 데이터베이스에 접속하는 코드도 브라우저에서는 보이지 않습니다.

같은 이유로 비밀 값도 서버 컴포넌트 안에서는 써도 새지 않습니다. 결제 서비스의 API 키처럼 밖에 보이면 안 되는 값이 여기에 속합니다.

라이브러리도 마찬가지입니다. 앞에서 본 날짜 라이브러리처럼 큰 라이브러리를 서버 컴포넌트에서 써도 번들은 그대로입니다. 라이브러리 코드도 서버에 남기 때문입니다.

서버 컴포넌트가 못 하는 일

서버 컴포넌트는 서버에서 한 번 돌고 끝납니다. 결과를 보낸 뒤 브라우저에는 그 함수가 남아 있지 않습니다. 그래서 브라우저에서 일어나는 일에 반응할 방법이 없습니다.

하려는 일 서버 컴포넌트 클라이언트 컴포넌트
데이터베이스·파일 읽기 ✓ ✗
비밀 값 쓰기 ✓ ✗
클릭·입력에 반응 ✗ ✓
화면에 띄운 값을 기억하고 바꾸기 ✗ ✓
브라우저 기능 쓰기 ✗ ✓

다섯 줄 모두 한쪽이 잘하는 일을 다른 쪽은 못 합니다.

넷째 줄의 「기억하고 바꾸기」는 장바구니 수량 같은 값을 말합니다. 화면에 떠 있는 동안 바뀌는 이런 값을 컴포넌트의 상태라고 부릅니다. 다섯째 줄의 브라우저 기능은 창 크기를 재거나 localStorage 같은 브라우저 저장소에 값을 적는 일입니다.

React 는 어느 파일이 클라이언트 컴포넌트인지를 파일 맨 위의 한 줄로 가립니다. 그 줄이 use client 입니다. 실제로는 파일 첫 줄에 'use client'; 처럼 따옴표로 감싼 문자열 하나를 적습니다. 이 줄이 붙은 파일과 그 파일이 불러오는 코드는 브라우저로 갑니다. 그 밖의 컴포넌트는 서버 컴포넌트로 취급합니다.

두 종류를 한 트리에 섞기

한 페이지에는 두 종류가 같이 섭니다. 서버 컴포넌트가 트리의 위쪽을 맡습니다. 반응이 필요한 잎사귀만 클라이언트 컴포넌트로 둡니다. 쇼핑몰 페이지라면 아래와 같이 나뉩니다.

flowchart TD
    subgraph S["서버에서만 도는 조각"]
        P["페이지"] --> H["머리글"]
        P --> L["상품 목록"]
        L --> C["상품 카드"]
    end
    subgraph B["브라우저에서도 도는 조각"]
        K["장바구니 담기 버튼"]
        Q["검색창"]
    end
    C --> K
    H --> Q

머리글과 상품 카드는 내용을 보여 주기만 하니 서버에 남습니다. 장바구니 담기 버튼과 검색창은 클릭과 입력에 반응해야 하므로 브라우저로 갑니다. 번들에 실리는 것은 「브라우저에서도 도는 조각」 상자의 두 조각뿐입니다.

서버 컴포넌트가 클라이언트 컴포넌트에 값을 넘길 때는 조건이 붙습니다. 그 값은 서버에서 브라우저로 건너가야 하므로 글자로 바꿔 보낼 수 있어야 합니다. 값을 글자나 바이트로 바꿔 보내는 일을 직렬화라고 합니다. 숫자와 문자열과 목록은 넘길 수 있고, 함수는 넘길 수 없습니다.

서버에서 브라우저로 가는 것

서버 컴포넌트를 쓰면 서버가 보내는 것은 HTML 한 장이 아닙니다. 다 그린 컴포넌트 트리를 글자로 적은 화면 설명입니다. 서버 컴포넌트가 있던 곳에는 실행 결과가 들어 있습니다. 클라이언트 컴포넌트가 있던 곳에는 「이 코드 파일을 불러 이 값으로 실행하라」는 표시가 들어 있습니다.

sequenceDiagram
    participant 브라우저
    participant 서버
    participant 데이터베이스
    브라우저->>서버: 페이지를 요청한다
    서버->>데이터베이스: 상품을 읽는다
    데이터베이스-->>서버: 상품 목록
    Note over 서버: 서버 컴포넌트를 실행한다
    서버-->>브라우저: 화면 설명을 보낸다
    브라우저->>서버: 버튼 코드만 요청한다
    서버-->>브라우저: 버튼 코드
    Note over 브라우저: 화면을 붙이고 버튼을 실행한다

그림의 앞 절반은 서버 안에서 끝납니다. 데이터베이스를 읽고 컴포넌트를 실행하는 동안 브라우저는 기다리기만 합니다. 브라우저가 따로 받는 코드는 뒤 절반의 버튼 코드뿐입니다.

화면 설명은 다 만들어질 때까지 붙들고 있지 않아도 됩니다. 준비된 조각부터 나눠 보내고, 늦는 조각은 뒤따라 보낼 수 있습니다. 이렇게 응답을 조각조각 흘려보내는 방식을 스트리밍이라고 합니다.

서버 컴포넌트가 도는 때

서버 컴포넌트가 꼭 요청마다 도는 것은 아닙니다. 내용이 잘 안 바뀌는 페이지라면 배포 전에 빌드할 때 한 번만 돌려 결과를 저장해 둘 수 있습니다. 요청마다 내용이 달라지는 페이지라면 웹 서버가 요청이 올 때마다 돌립니다.

서버 컴포넌트를 돌리는 도구

서버 컴포넌트를 돌리려면 번들러와 서버가 맞물려야 합니다. 이 소절은 둘이 각각 무엇을 맡는지, 그 둘을 누가 묶는지를 봅니다.

번들러는 여러 코드 파일을 번들로 묶는 도구입니다. 서버 컴포넌트를 쓰면 번들러는 어느 코드가 브라우저로 갈지를 갈라야 합니다. use client 가 붙은 파일과 그 파일이 불러오는 코드만 번들에 넣는 일입니다.

서버는 요청이 오면 서버 컴포넌트를 실행해 화면 설명을 만들어 보내야 합니다. 이 두 일을 한데 묶어 주는 것이 웹 프레임워크입니다. 웹 프레임워크는 웹 애플리케이션의 뼈대를 미리 짜 둔 도구 모음입니다. 그래서 서버 컴포넌트는 React 하나만으로 쓰기보다 Next.js 같은 웹 프레임워크를 거쳐 씁니다.

쓰는 경우와 안 쓰는 경우

서버 컴포넌트로 얻는 것은 화면이 보여 주기 위주일수록 커집니다. 블로그 글, 상품 목록, 문서 페이지처럼 데이터를 읽어 띄우기만 하는 화면이 그렇습니다. 이런 화면은 대부분을 서버에 두고 버튼 몇 개만 브라우저로 보낼 수 있습니다.

반대로 화면 전체가 클릭과 입력에 반응해야 하면 얻을 것이 적습니다. 그림판, 표 계산 화면, 채팅 입력처럼 쉴 새 없이 바뀌는 화면이 그렇습니다. 이런 화면은 어차피 대부분이 클라이언트 컴포넌트가 됩니다.

서버 컴포넌트가 치르는 대가

첫째 대가는 경계를 늘 의식해야 한다는 것입니다. 컴포넌트마다 서버 쪽인지 브라우저 쪽인지 정해야 합니다. 그 경계를 넘는 값은 직렬화되는 것이어야 합니다. 서버 컴포넌트에 클릭 처리를 적으면 오류가 납니다.

둘째는 서버의 일이 늘어난다는 것입니다. 브라우저가 하던 화면 그리기를 서버가 가져가므로 요청마다 서버의 CPU(Central Processing Unit, 중앙 처리 장치)를 더 씁니다. 그래서 정적 파일만 내주는 호스팅으로는 요청마다 도는 서버 컴포넌트를 돌릴 수 없습니다.

셋째는 바뀐 내용을 보려면 서버에 다시 물어야 한다는 것입니다. 서버 컴포넌트가 그린 목록은 브라우저 안에서 스스로 바뀌지 않습니다. 새 상품을 띄우려면 서버에 다시 요청해 화면 설명을 새로 받아야 합니다.

관련 항목

서버 컴포넌트가 속하는 상위 분류

컴포넌트 · React · 프론트엔드 · 렌더링 · 웹 프레임워크 · JavaScript

서버 컴포넌트와 맞세워지는 렌더링 방식

클라이언트 사이드 렌더링 · 서버 사이드 렌더링 · 정적 사이트 생성기 · 프리렌더링 · 하이브리드 렌더링 · 아일랜드 아키텍처

서버 컴포넌트와 한 트리를 이루는 부품

클라이언트 컴포넌트 · use client · use server · 서버 함수 · props · 컴포넌트 트리

서버 컴포넌트가 줄이려는 비용

번들 · 번들 크기 · 하이드레이션 · 요청 워터폴 · 첫 화면 표시 시간 · 코드 분할 · 지연 로딩

서버 컴포넌트가 화면을 보내는 데 쓰는 기술

직렬화 · 스트리밍 · HTML · DOM · 가상 DOM · 번들러

서버 컴포넌트를 채택한 프레임워크

Next.js · App Router · React Router · Remix · Waku

서버 컴포넌트가 서버에서 바로 닿는 자원

데이터베이스 · API · API 키 · 환경 변수 · 파일 시스템 · 캐싱

다른 이름: Server Components · Server Component · React Server Components · RSC · 리액트 서버 컴포넌트