정적인 미래 (A Static Future)


컴파일 시점 워크플로우의 마법

원문 : https://joshwcomeau.com/gatsby/a-static-future/

정적(Static) 사이트가 부활하여 엄청난 인기를 누리고 있는 요즘, 나는 개츠비(Gatsby)와 같은 도구로 할 수 있는 일에 대해 많은 오해가 있다는 것을 알게 되었다.

특히 내 친구 중 몇 명으로부터, 개츠비를 사용하는 것은 좋아하지만 프로젝트가 "너무 동적(dynamic)"이라서 걱정이라는 얘기도 들었다. 이 말은 데이터베이스가 필요하다는 의미일 수도 있고, 사이트의 상호작용이 너무 많다는 의미일 수도 있다.

몇 개의 개츠비 프로젝트를 만들어본 후, 나는 개츠비가 뭐든지 다 할 수 있다는 사실을 좀 더 확신하게 되었다. 더 나아가서, 심지어 나는 정적 주도적(static-driven)인 접근방식이 동적, 인터랙티브, 데이터 주도적(data-driven)인 웹사이트 혹은 웹 애플리케이션의 미래라고 생각한다.

오늘은 이 주제에 대해 이야기해 보겠다.

"Static"의 정의

단어 "static(정적인)"은 너무 많은 것을 의미하기도 하고, 가끔은 오해를 낳기도 한다. 내가 생각하는 정의는 다음과 같다.

정적인 웹사이트란, 초기 HTML이 요청에 의해 서버로부터 동적으로 생성되는 게 아니라 미리 만들어져 있는 웹사이트다.

이 웹사이트에 요청을 보내면, 미리 만들어진 HTML이 Netlify에 의해 제공된다. HTML 문서를 즉석에서 동적으로 렌더링하기 위한 노드 서버는 존재하지 않는다.

이 말이 굉장히 제한적으로 들릴 수도 있지만, 실제로는 "두 마리 토끼를 다 잡을 수 있다"는 것을 꼭 이야기하고 싶다. 정적으로 생성한다고 해서 반드시 사용성이 풍부하고 동적인 애플리케이션을 포기해야 하는 것은 아니다.

여전히 리액트다

개츠비와 Next.js 둘 다 리액트를 감싸고 있는 랩퍼이다. 리액트로 할 수 있는 것은 모두 다 개츠비와 Next로도 할 수 있다.

예를 들어, 작년에 나는 생성 예술(generative art) 도구인 Tinkersynth를 만들었다.

Tinkersynth는 사용자들이 몇 개의 특이한 컨트롤러를 조정하는 것만으로 독창적인 생성 예술을 만들어낼 수 있는 도구이다. 예상할 수 있듯이, 이 도구에는 많은 클라이언트 자바스크립트가 포함되어 있다. 심지어 브라우저에서만 사용할 수 있는 전문화된 API들도 활용하고 있는데, 연산은 웹 워커에서 이루어지며, 화면은 OffsscreenCanvas을 사용해 그려진다.

이 프로젝트는 처음에 create-react-app을 사용해서 만들었다가 나중에 개츠비를 사용하도록 변경되었다. 그 이유는 복잡한 최적화 작업을 위해 많은 시간을 소비하지 않고도 로딩 성능을 개선하고 싶었기 때문이다.

이처럼 프로젝트를 정적으로 빌드한다는 것은 무슨 의미일까? 이 페이지의 최초 HTML은 다음과 같다.

Initial HTML - Static

이와는 대조적으로, create-react-app(혹은 다른 클라이언트 기반 접근 방식)을 사용했을 때의 최초 HTML은 다음과 같다.

Initial HTML - CRA

두 경우 모두 리액트 애플리케이션이 포함된 자바스크립트 번들을 불러오고, 파싱하고, 실행하는 동안 클라이언트는 최초 HTML 문서를 받아와서 화면에 보여준다. 차이점이 있다면, 개츠비 앱에 있는 최초 HTML이 더 생동감 있다는 점이다. 전체 레이아웃이 표시되고, 뭔가 진행되고 있다는 것을 귀여운 로딩 스피너를 통해 알 수 있다. 체감 성능은 훨씬 더 좋다. 사용자가 실제 작업을 할 수 있게 준비하는 동안 뭔가를 보여주기 때문에 사이트가 더 빠르게 느껴지는 것이다.

또 다른 보너스 : 헤더와 푸터에 있는 링크들은 첫 번째 프레임부터 완벽하게 동작한다. 자바스크립트 로딩이 완료되기 전에 "Contact"를 클릭하더라도 그 페이지로 바로 이동할 것이다! 동작 가능한 요소 중 동작하지 않는 요소는 없다.

여전히 데이터베이스에서 데이터를 가져올 수 있다

데이터베이스에 저장된 데이터에 의존하는 애플리케이션은 어떨까?

예를 들어, 아래의 헬로키티 커피 머신과 같은 특이한 제품을 파는 온라인 쇼핑몰을 만든다고 가정해 보자.

Hello Kitty Coffee Machine

놀랍게도, 이 제품은 실제로 존재한다!

전형적인 서버 렌더링 기반의 애플리케이션에서는 다음과 같은 절차로 요청이 처리된다.

  • 클라이언트가 /shop/hello-kitty-coffee-machine 요청을 보낸다.
  • 서버가 CMS나 데이터베이스에 질의문을 보내서 요청된 아이템의 정보를 가져온다.
  • 서버가 커피 머신을 위한 HTML 파일을 생성하면서 관련된 정보를 하이라이팅한다 (이름, 가격, 커피를 끓일 때 나는 고양이 소리...)
  • 서버가 HTML 파일을 클라이언트에게 전송한다.

정적인 개츠비 사이트에서 이 과정을 어떻게 따라 할 수 있을까? 두 가지 선택지가 있다.

아마 가장 일반적으로 사용될 첫 번째 방법은, 정적인 웹이 최초 콘텐츠만 제공하고, 이후에 API나 앱 서버가 관여하는 방법이다.

  1. 클라이언트가 /shop/hello-kitty-coffee-machine 요청을 보낸다.
  2. 서버가 즉시 기본 콘텐츠만 가진 HTML 파일을 반환한다. 이 HTML 파일은 고양이나 커피 머신에 대한 어떤 정보도 포함하지 않는다. 아마 스피너 정도만 보여줄 것이다.
  3. HTML을 파싱한 후에 클라이언트가 fetch를 실행해서 앱 서버에 특정 제품에 대한 정보를 요청한다.
  4. 앱 서버가 CMS나 데이터베이스에 질의문을 보내고, 전달받은 데이터를 JSON 형태로 전송한다.
  5. 클라이언트가 전달받은 JSON 데이터를 사용해서 뷰를 갱신한다.

이 방법은 기회비용(trade-off)이 있다. UI를 훨씬 더 빨리 그려서 사용자들이 일반적인 정보에 빨리 접근할 수 있다는 것은 장점이다. 하지만 반대로, 특정 제품에 대한 정보가 표시되기까지는 더 많은 시간이 걸린다.

그런데, 꼭 한 가지를 희생할 필요가 없다면 어떨까? 모든 정보가 포함된 HTML 문서를 웹 서버로부터 제공받을 수 있다면 어떨까?

사고방식의 전환이 필요하다

애플리케이션을 만들 때 우리는 모든 것을 실행 시점(runtime)을 기준으로 생각하는 경향이 있다. "템플릿"에 데이터베이스에서 가져온 데이터를 적용해서 콘텐츠를 만들고, 이 과정은 요청이 올 때마다 실시간으로 발생한다.

그런데, 모든 것을 컴파일 시점에 빌드할 수는 없을까? 전체 쇼핑몰 상품을 위해 필요한 모든 HTML 페이지를 미리 갖고 있다면 어떨까?

다음과 같은 과정이 될 것이다.

  1. 클라이언트가 /shop/hello-kitty-coffee-machine 요청을 보낸다.
  2. 웹 서버가 즉각적으로 고양이 커피 머신의 모든 정보를 포함한 완성된 HTML을 반환한다.

두 단계면 끝이다! 데이터베이스 검색도, RPC 요청도 전혀 필요 없다. 우리는 풍부한 사용성의 데이터 기반 온라인 쇼핑 플랫폼을 "hello world"를 표시하는 정적 사이트와 동일한 속도로 제공할 수 있다. 또한 이 페이지는 순수한 HTML 이기 때문에, 엣지(edge)에 캐쉬 될 수 있다. "매우 빠른"이라는 표현은 잊어라. 우리는 미친 듯이 빠른 영역에 있다.

엣지에 캐쉬 된다는 말은, 콘텐츠가 내 현재 위치와 물리적으로 가장 가까운 서버에 캐쉬 된다는 의미이다. 즉, HTML을 가져오기 위해 바다를 건너 지구 반대편까지 날아갈 필요가 없다는 의미이다.

컴파일 시점에 데이터 가져오기

한 가지 명확하게 할 것은, 데이터는 여전히 데이터베이스나 CMS에서 가져온다는 것이다. 여전히 데이터를 가져온 후에 HTML을 생성하는 작업은 필요하다. 하지만 이 작업을 컴파일 시점, 즉 사이트를 빌드하는 시점에 할 수 있다.

gatsby build(혹은 사이트 배포를 위한 어떤 빌드 커맨드든)를 실행하면, 사이트에 있는 모든 HTML을 생성하기 위해 필요한 모든 API 요청을 빌드 시스템이 호출한다. 빌드 시스템은 리액트의 서버 렌더링 API를 사용해서 리액트 컴포넌트 트리를 하나의 큰 HTML 문서로 변경한다.

이 전략을 위해 개츠비에는 풍부한 생태계가 마련되어 있다. 이들을 소스 플러그인이라 부른다. 개별 플러그인은 데이터를 각기 다른 소스(출처)에서 가져와서 빌드 과정에 컴포넌트가 사용할 수 있도록 도와준다.

소스 플러그인의 종류는 아주 많은데, 여기서는 몇 가지 예제만 소개하겠다.

이 외에도 다양한 플러그인이 있다. 다른 출처에서 데이터를 가져와야 한다면, 용도에 맞는 플러그인을 찾을 수 있을 것이다.

개츠비 클라우드Netlify와 같은 CI/CD 서비스를 사용하면 깃허브에 코드를 푸쉬할 때마다 클라우드에서 일련의 모든 작업이 진행된다. 변경사항은 빌드가 완료되자마자 자동으로 배포될 것이다.

개츠비가 HTML을 미리 생성하기 위해 서버 사이드 렌더링을 어떻게 사용하는지에 대해 더 자세히 알아보고 클라이언트에서 발생하는 리하이드레이팅(rehydrating)이 어떤 영향을 끼치는지 알고 싶다면, 내 블로그 글인 리하이드레이션의 위험성을 참고하기 바란다.

이게 과연.. 실현 가능한 걸까?

언뜻 생각하면, 이 이야기는 정말 터무니없게 들린다. 쇼핑몰의 상품 종류가 5만 개라면? 백만 개가 넘는다면? 빌드할 때마다 백만 개의 API 요청을 보내라는 걸까?

지금 당장 이 개념을 적용할 수 있는 범위에 한계가 있는 건 사실이다. 하지만 미래는 빠르게 다가오고 있다.

개츠비 팀은 점진적 빌드(incremental build)를 위해 열심히 작업하고 있다. 점진적 빌드란 코드나 콘텐츠가 변경될 때 "전체를" 다시 빌드할 필요가 없도록 해 준다.

예를 들어 우리의 헬로키티 커피 메이커가 잘 팔리지 않는다고 해 보자. 20% 할인을 적용하면 좋을 것 같다. 이를 위해 CMS에 로그인해서 세일 가격을 활성화한다. "점진적 빌드"의 세계에서는 다시 빌드하는 과정이 몇 초면 끝날 것이다. 왜냐하면 페이지 하나만 다시 빌드하면 되기 때문이다. 이 변경 사항은 정적 웹 호스트에 자연스럽게 전달되고, 웹 서버는 새로운 HTML 파일을 거의 즉시 제공할 것이다.

코드 변경도 동일한 방식으로 동작한다. 리액트 컴포넌트의 코드를 수정하면, 해당 컴포넌트를 사용해서 렌더링하는 페이지만 새로 빌드하면 된다.

캐쉬 되지 않은 전체 페이지를 다시 빌드할 때는 시간이 꽤 걸릴 것이다. 사이트의 모든 페이지에서 표시되는 헤더 컴포넌트를 수정한다면 빌드를 위해 꽤 오랜 시간을 기다려야 할 수도 있다. 하지만 이런 업데이트가 필요한 경우는 드물며, 도구와 시스템도 계속해서 개선되고 더 빨라질 것이다. 이런 문제들의 해결 가능성은 우리가 생각하는 것보다 훨씬 높다.

Next.js팀도 이 기능을 개발하고 있다! 지난주에 팀 넛켄스(Tim Neutkens)가 작성한 RFC를 살펴보길 바란다. 점진적 빌드와 관련된 다양한 활동들이 진행되고 있어 정말 기쁘다.

(역자 주: 이 글의 원문이 포스팅된 지 몇 주 후에, 실제로 개츠비에 점진적 빌드 기능이 도입되었다. 자세한 내용은 개츠비 블로그에서 확인할 수 있으며, 이 글의 저자가 작성한 또 다른 글에도 잘 정리되어 있다.)

본질적으로 다르다

빌드 성능, 즉 새로운 버전의 사이트를 빌드하기 위해 걸리는 시간을 생각할 때, 우리는 보통 점진적인 개선의 측면에서 생각한다. 만약 빌드 시간이 10분에서 5분으로 줄어든다면 삶의 질이 상당히 개선되었다고는 할 수 있겠지만, 엄밀히 말해서 본질적으로 다른 경험은 아니다.

점진적 빌드는 완전히 다른 이야기다. 거대한 사이트의 변경 사항을 몇 초 만에 적용할 수 있다면, 이는 사용 가능한 아키텍처와 워크플로우의 관점에서 새로운 가능성의 문을 여는 것이나 마찬가지다.

개츠비 팀 동료인 맥스 스토이버스펙트럼이라는 도구를 만들어 운영하고 있는데, 이는 트위터, 레딧, 슬랫의 기능을 섞어 놓은 커뮤니티 플랫폼이다. 이 도구는 굉장히 동적인 애플리케이션이라서 정적으로 빌드할 수 있을 거라고는 상상할 수도 없을 정도이다. 하지만 그게 가능한 시점이 점점 눈앞으로 다가오고 있다. 맥스는 다음과 같이 말했다.

만약 새로운 콘텐츠를 빌드하고 배포하는 과정이 충분히 빠르고 안정적이라면(1초 이내), 그 콘텐츠는 순식간에 전 세계 모든 곳에서 사용할 수 있을 것이다. 단지 수많은 정적 HTML 파일만으로 이루어져있기 때문에, 데이터베이스가 어디에 있는지는 상관이 없다. 이 아키텍처를 사용해서 개발했다면 확장성, 속도, 안정성 면에서 이론상 훨씬 뛰어났을 것이다! 🤯🤯🤯

소중한 보물과도 같은 이점들

정적 사이트에 대한 이야기는 보통 성능에 초점이 맞춰져 있다. 사실 동적인 데이터로 만들어진 사이트를 거의 즉각적으로 로드할 수 있다는 것은 강력한 장점이니 당연할 것이다. 하지만 정적 사이트라는 상자에는 훨씬 더 놀라운 보물들이 숨겨져 있다.

복원력(Resilience)

다음의 상황을 가정해보자. 1년 동안 만들어오던 SaaS 제품을 지금 막 프로덕트 헌트(Product Hunt)에 런칭했다. 이 제품이 엄청난 인기를 끌면서 1위를 차지했다! 팀 멤버들이 다 같이 모여 축배를 들면서 서로를 축하하고 있는 와중에 누군가의 핸드폰이 불길하게 울리기 시작한다. 또 다른 사람의 핸드폰도. 또 다른 사람도.

알고 보니, 서버가 부하를 견디지 못해서 페이저 듀티(Pager Duty)가 전력을 다해 문자를 보내는 중이었다. 이런 결정적인 순간에, 우리의 제품이 인터넷의 관심을 한 몸에 받고 있을 때, 웹사이트가 모두에게 500 에러를 보내주고 있다. 호감을 가진 잠재적 고객들이 랜딩 페이지에 접근할 수조차 없다. 이런 재난이 또 있을까!

확장(Scaling)은 굉장히 어려운 문제지만, 미리 컴파일된 HTML 파일을 제공하면 문제가 엄청나게 쉬워진다. HTML 파일은 패슬리(Fastly)클라우드플레어(Cloudflare)와 같은 서비스를 사용해서 수천 개의 미러 사이트에 배포할 수 있다. 그중 하나가 다운되더라도, 다른 하나가 우아하게 대신 사용될 것이다.

사용자들이 매번 데이터베이스에 새로운 요청을 하지 않고도 동적인 데이터에 접근할 수 있다는 건 어마어마한 일이다. 그동안 짊어졌던 무거운 짐을 얼마나 덜어낼 수 있을지 생각해 보자.

  • 데이터베이스가 다운되고 그 이유를 찾을 수 없더라도 긴급한 상황은 아니다. 사이트는 여전히 혼자서 잘 동작하고 있기 때문이다.
  • 한 개발자가 GraphQL API를 망가뜨리는 코드를 배포하는 바람에 새 버전의 사이트를 빌드할 수 없는 상황이라도, 현재 버전의 사이트는 이미 빌드 당시에 필요한 요청을 보내서 만들어졌으므로 영향을 받지 않는다.

어떤 악마 같은 마법사가 데이터베이스 서버를 다른 차원으로 이동 시켜 버린다고 할 지라도, 사용자들은 문제가 발생했다는 사실조차 모를 것이다.

이 주제에 대한 내용은 샘이 작성한 개츠비를 사용하는 팀은 왜 밤에 꿀잠을 자는가라는 글에 잘 정리되어 있다.

SEO

검색 엔진은 로딩 속도가 빠른 웹사이트를 좋아한다. 콘텐츠를 빨리 전달할수록 모바일 기기에서의 구글 랭킹을 올릴 수 있다.

여기서 또 하나의 불편한 문제를 손쉽게 피해갈 수 있다. 페이지가 최초에 여러 개의 스피너만 보여주고 있을 때, 구글 로봇이 페이지를 너무 빨리 크롤링한다면? 구글은 API 요청을 통해 주입하는 모든 알짜배기 키워드를 놓치게 될 것이고, 결국 이 페이지들은 순위권에 들지도 못하게 될 것이다!

유기적인 트래픽에 의존하는 사이트에는 정적인 빌드가 엄청난 차이를 만들어낼 수 있다.

비용과 확장

한 페이지를 하루에 100명이 방문할 때, 하루에 10번 빌드하는 방식으로 변경한다면, 데이터베이스에 보내는 요청은 90%나 줄어들게 된다. 즉, 비용을 엄청나게 절감할 수 있다.

생성된 HTML 페이지는 캐시 될 수 있고, 캐시 된 콘텐츠를 제공하는 것은 훨씬 저렴하다.

한계

사실 미리 생성할 수 없는 것들도 있다. 예를 들면 현재 로그인된 사용자의 개인 페이지에 보이는 콘텐츠, 즉 아바타나 사용자 이름 등이 있다.

분석 정보 대시보드와 같이 특정 사용자에 대한 데이터가 대부분인 페이지(특히 데이터가 개인정보에 민감한 경우)에는, 아마 정적 빌드가 어울리지 않을 것이다. 하지만 온라인 쇼핑몰의 장바구니와 같이 특정 사용자에 대한 데이터가 일부만 포함된 페이지에서는 여전히 미리 렌더링하는 방식의 이점을 누릴 수 있다.

더 구체적으로 말하면, 일단 컴파일하는 과정에서 제품에 대한 동적인 정보를 포함한 수많은 페이지를 렌더링한다. 그 후에 클라이언트가 API 요청을 보내서 장바구니의 내용물 등 누락된 데이터를 채워 넣을 수 있다. 로컬 스토리지를 사용하면 데이터를 가져오는 과정을 좀 더 빠르게 만들 수도 있다.

콩나무 줄기

(역자 주: 잭과 콩나무 이야기의 나무줄기처럼, 아직은 작은 줄기지만 어디까지 성장할지 모르는 큰 잠재력을 가졌다는 의미)

동적이고 데이터베이스 중심인 웹 애플리케이션을 정적으로 렌더링한다는 것은 굉장히 획기적이면서도 모호한 부분이 많은 개념이다. 많은 잠재력을 가진 파격적인 개념이긴 하지만 아직은 매우 초기 단계라 할 수 있다.

이 전략을 사용한 꽤 큰 규모의 사이트들이 생겨나기 시작하고 있으며, 기술의 발전과 함께 잠재된 새로운 사용 방식도 발견하게 될 것이다. 이 기술이 어디까지 발전할 수 있을지 정말 큰 기대가 된다! 어쩌면 이 급진적인 개념이 머지않아 주류가 될 거라는 생각도 해 본다.

더 읽어보기


김동우, FE Development Lab2020.05.08Back to list