Next.js 버전 12.2.0에는 HTML을 정적으로 렌더링하는 새로운 기능인 On-Demand Incremental Static Regeneration이 소개되었습니다. 이 기능은 자주 변경되는 데이터 풀을 가진 기업에게 중요하며, 특히 사용자 브라우저에 최소한의 오래된 데이터가 남도록 필요로 하는 중요한 콘텐츠를 가진 기업에게 필수적입니다. 이 글에서는 정적 생성의 여러 형태를 탐구하고, 각 형태가 지난번 것의 효율성이나 성능 측면에서 어떻게 발전되어 가다가 od-ISR에 도달하는지 살펴볼 것입니다.
정적 사이트 생성
정적 사이트 생성(Static Site Generation, SSG)은 많은 사용 사례에 대해 클라이언트 사이드 렌더링(CSR)과 서버 사이드 렌더링(SSR)에 유용한 대안입니다. 클라이언트 사이드 렌더링은 클라이언트(사용자 브라우저)가 JavaScript를 HTML과 CSS로 렌더링하여 사용자에게 페이지를 제공하는 것에 의존합니다. 현재의 웹 개발 환경에서 CSR은 일반적으로 대량의 번들화된 JavaScript를 프론트엔드로 보내는 단일 페이지 응용 프로그램(Single Page Application, SPA)으로 이어집니다. 이로 인해 페이지를 렌더링하기 위해 필요한 HTML과 CSS를 브라우저에게 위임함으로써 성능이 좋지 않은 사이트를 만들 수 있습니다.
SSR의 경우에는 서버가 더 많은 작업을 처리합니다. 각 페이지 이동은 서버에 요청을 일으키고, 서버는 관련 페이지와 JSON 데이터를 작성하여 응답합니다. 서버는 일반적으로 브라우저보다 강력하며, 페이지를 렌더링하는 데 필요한 내부 회사 데이터와 함께 동일한 위치에 배치될 수 있어 이 콘텐츠를 수집하기 위한 빠른 서버 측 HTTP 요청을 가능케합니다. 일반적으로 CSR은 렌더링 후 반응성이 중요한 매우 동적이고 대화형 응용 프로그램에 적합하며, SSR은 초기 페이지 로드를 더 빠르게 만드는 데 유리합니다. CSR은 캐싱된 에셋 및 콘텐츠 또는 CDN 또는 CDN이있는 CMS(예 : Sanity)에 데이터를 가져와 사용자에게 제공하여 페이지를 렌더링합니다.
많은 현대적인 프레임워크는 일부 루트가 구성 파일에 따라 SSR을 통해 렌더링되고 다른 루트가 CSR을 통해 렌더링되는 하이브리드 렌더링 형태를 지원합니다. 예를 들어, Nuxt는 Nuxt 구성 파일에서 지정된 대로 다양한 루트를 대상으로 렌더링 할 수 있습니다.
정적 사이트 생성(SSG)은 CSR 및 SSR과는 완전히 다른 방식입니다. SSG는 모든 웹 페이지가 빌드 시간에 생성되어 서버에 저장되는 아키텍처입니다. 일반적으로 이에는 일종의 템플릿 기능이 포함됩니다. 블로그를 예로 들어보겠습니다. 모든 블로그 페이지가 동일한 형식으로 표시되는 경우를 생각해보세요. 정적 사이트 생성기는 일반적인 블로그 글 페이지에 대한 HTML 템플릿을 저장하고, 구조화된 데이터 형식 (예: JSON)을 사용하여 각 별도의 블로그 페이지를 위한 HTML 템플릿을 배포할 정보를 저장합니다. 클라이언트가 요청을 하면 저장된 사전에 생성된 HTML, CSS, 클라이언트 측 JavaScript (예를 들어 사용자 상호 작용에서의 HTTP 요청 제작용) 및 데이터가 즉시 사용자의 브라우저로 전송됩니다. 페이지를 브라우저에서 빌드하는 작업은 필요하지 않습니다. 이로 인해 초기 페이지 로드가 빠릅니다.
위 다이어그램은 SSG를 통해 렌더링된 블로그 사이트 아키텍처의 단순한 예제를 보여줍니다. 빌드 시기에 (1) Next는 콘텐츠 관리 시스템 (CMS)에 서버 측 호출을 수행하여 (2) 데이터를 받아 (3) 블로그 게시물을 정적으로 생성합니다. 또한 Next는 추가로 하나 이상의 API (4)를 호출하여 이 콘텐츠를 생성하거나 추가 데이터를 받을 수 있습니다 (5). 클라이언트가 요청을 보낼 때 (6), 미리 빌드된 페이지가 응답으로 제공되며, 클라이언트 측 JavaScript, HTML, CSS 및 관련 데이터 (7)가 포함됩니다. SSG는 사이트에서 상호 작용을 금지하지 않으며, 각 페이지와 함께 전송되는 클라이언트 측 JavaScript에 의해 하나 이상의 API 또는 서비스 (예: Stripe)로 추가 CRUD 요청이 수행될 수 있습니다 (8, 9). 정적 사이트 생성기에 의해 생성된 페이지는 엔드 유저에게 가능한 한 가까운 위치에 있는 CDN에 캐시될 수도 있습니다 (여기서는 생략). 요청-응답 주기에서 증가된 거리가 가장 큰 성능 억제 요인 중 하나이므로, 이를 통해 페이지 로드 시간을 더욱 줄일 수 있습니다.
정적 사이트 생성은 매우 뛰어난 로드 시간 성능을 제공합니다. 그러나 큰 단점이 하나 있습니다. 순수 SSG를 통해 렌더링된 사이트에서 하나의 페이지를 변경하면 서버에 저장된 모든 페이지를 완전히 다시 빌드해야 합니다. 이것이 순수 SSG의 가장 큰 병목 현상입니다. 밤 늦은 글쓰기 세션 중에 블로그 게시물 하나에서 철자를 잘못 입력한 경우를 상상해보세요. 사이트에 게시물이 얼마나 많이 있는지에 따라, 그것은 치명적인 실수일 수 있습니다.
stale-while-revalidate 및 증분 정적 재생
순수 SSG의 명백한 단점은 자주 변경되는 콘텐츠에 대해 아키텍처를 쓸모없게 만든다는 것입니다. SSG로 돌아가서, 모든 페이지가 생성되고 서버에 저장될 때 (다시 말해 캐시되어 있을 때), 개발자가 이 내부 캐시를 무효화하여 페이지를 다시 만드는 것을 막는 것은 무엇인가요? 여기에서 나타나는 것이 바로 SWR(지속-중재 확율)입니다. HTTP Cache-Control 헤더에 설정된이 값은 동반하는 max-age 값에 따라 캐시된 응답의 유효성을 결정합니다. SWR이 있는 Cache-Control HTTP 헤더는 다음과 같이 설정할 수 있습니다:
Cache-Control: max-age=604800, stale-while-revalidate=86400
이 헤더는 받는 서버, CDN 또는 클라이언트에게 콘텐츠를 재검증하기 전에 7일 동안 HTTP 응답을 캐시하도록 지시합니다. 일곱 일이 지나면 캐시된 응답은 더 이상 유효하지 않습니다. 이때 특정 URL의 특정 서버, CDN 또는 브라우저에 대한 요청이 발생하면, 즉시 스테일 콘텐츠가 응답으로 제공되고 백그라운드에서 내용을 다시 확인하고 캐시를 새로 고침하기 위해 원본 서버에 새 요청이 이루어집니다.
여기에서 몇 가지 네이밍 컨벤션 혼란이 발생합니다. SWR을 렌더링 모드로 부르고 CDN의 존재 여부에 따라 Incremental Static Regeneration (ISR)와 의미론적으로 구분하여 네이밍을 혼동할 수 있습니다. CDN이 없는 경우 SWR은 렌더링 아키텍처이고 CDN이 있는 경우 ISR이 아키텍처이므로 (Nuxt 3 참조).
SWR의 아이디어를 렌더링 모드와 분리하는 것이 현실적입니다. 이는 Cache-Control 헤더에 설정된 값을 의미하며, 해당 응답의 캐시 동작을 제어할 수 있습니다 (서버, CDN 또는 클라이언트가 stale-while-revalidate을 Cache-Control 값으로 수락할 수 있다면). Cache-Control 헤더의 must-revalidate 값과 마찬가지로 일반적입니다.
그러나 증분 정적 재생성은 SWR 헤더를 사용하여 정적으로 생성된 콘텐츠를 저장하고 다시 유효성을 검사하는 서버 또는 CDN에서 SWR 헤더를 활용한 아키텍처로 취급할 수 있습니다.
위 다이어그램은 ISR 기반 아키텍처가 어떻게 보일 수 있는지 간단히 보여줍니다. 이를 현대적인 뉴스 사이트로 생각해보면 지친 기자들이 악화되는 정치적 기후에 대응하고, AI가 생성한 실수에 대처하고, 독자들의 트위터에서의 소란한 피드백에 대응하며 자주 변경되는 컨텐츠를 가질 것입니다. 여기서 Next 서버는 모든 페이지를 빌드 시간에 생성합니다(1), CMS(2) 또는 API(4)로 서버 콜을 통해 관련 데이터(3, 5)를 수신합니다. 이러한 페이지는 TTL(Time-To-Live) 값(12)으로 생성되며, 이 경우 60초입니다. 이는 Cache Control 값을 나타내는 max-age 와 동일합니다. 만약 클라이언트가 이와 같은 뉴스 기사에 대한 요청을 할 경우, 우리 아키텍처에서 요청은 먼저 CDN(8)에 도달합니다. TTL이 만료되지 않았고 60초 창 안에 있다면, CDN는 즉시 페이지를 제공(9)하고 추가 조치가 필요하지 않습니다.
만약 60초의 TTL이 지났다면, CDN는 페이지를 사용자에게 제공하는데 문제가 없습니다. 사용자는 CDN 캐시에서 즉시 정적으로 생성된 페이지를 받습니다(9). CDN는 이로써 끝나지 않으며, 서버에 신선하게 생성된 페이지를 요청합니다(6). 이 시점에서 서버는 CMS 또는 API에서 데이터를 다시 가져오도록 서버쪽에서 스크램블하여 이 특정 페이지(2, 4)를 재검증하며 해당 데이터를 받아 다시 해당 페이지를 생성합니다(3, 5). 그런 다음, 해당 페이지를 CDN에 다시 캐시하기 위해 보냅니다(7). 이러한 방식으로 생성된 사이트는 사용자가 HTTPS 요청을 통해 클라이언트 측 JavaScript(10, 11)를 사용하여 하나 이상의 API 또는 서비스에 대한 CRUD 작업을 시작하는 등 추가 상호작용을 제한하지 않습니다.
ISR보다 SSG의 혜택은 즉각적으로 나타납니다. 이제 한 가지 오타를 고치거나 기사 일부를 철회하거나 불운한 제목을 수정하기 위해 전체 사이트를 다시 빌드할 필요가 없습니다. 이로 인해 많은 비용이 드는 전체 사이트 재구축을 피할 수 있습니다. 콘텐츠가 한 페이지에서만 변경된다면, 왜 모든 페이지를 재구축해야 할 때와 같은 시간과 컴퓨팅 비용을 낭비해야 할까요? 또한 사용자 상호 작용을 통해 페이지를 다시 생성함으로써 불필요한 재구축을 최소화합니다. 드물게 방문하는 페이지는 상호 작용이 있을 때에만 다시 빌드됩니다.
그러나 이 아키텍처에는 단점도 있습니다. ISR의 경우 실시간 데이터 업데이트는 여전히 진정한 기능이 아닙니다. 예를 들어 기자가 7일 TTL이 설정된 뉴스 기사를 게시했다고 가정해봅시다. 모든 QA 확인을 통과하고 게시된 후에 기사 제목에 철자 오류가 있다는 것을 깨닳기 전에 사용자들에게 즉각적으로 알려져 조롱하는 트위터 게시물에 응답을 받습니다. 만약 창조자가 게시물을 편집하고 그냥 기다린다면, 기사가 새롭게 업데이트되기까지 전체 7일이 걸릴 수 있습니다. 캐시는 수동으로 무효화되어야 할 것입니다. 또한 서버가 페이지를 재생성하는 데 시간이 걸립니다. 그동안 모든 사용자는 CDN이나 서버가 답변으로 반환하는 제목 오타가 있는 낡은 콘텐츠를 받게 됩니다. 게다가 위에서 언급한 드물게 방문하는 페이지가 오랜 기간 동안 재구축되지 않으면 문제가 발생할 수 있습니다. 한 달 동안 방문하지 않은 페이지가 있다고 생각해보세요. 질문이 생깁니다 — 너무 오래된 것은 과연 얼마나 오래 된 것인가요?
온디맨드 증분 정적 재생성
On-Demand Incremental Static Regeneration (od-ISR, 동료 엔지니어가 만든 비표준 약어)은 위에 나열된 많은 질문에 대해 다룹니다. TTL에 기반한 캐시 무효화 대신, 실시간 데이터 변경을 다루지 못하는 시간 기반 지표가 아닌 동적으로 만들어낼 수 있습니다. 정적 콘텐츠의 이 온디맨드 재구성 유형은 중요한 데이터를 가진 웹사이트에 필수적입니다.
전형적인 예로 전자 상거래 사이트를 들 수 있습니다. 하루 종일 잘리지 않는 상품 데이터의 대부분은 변경되지 않지만, 뒷면에서는 제품 매니저가 여러 가지 이유로 다양한 제품 풀에서 설명, 제목 및 카테고리를 편집할 수 있습니다. 그들은 당신(개발자)이 이러한 변경 사항을 빠르게 인식하는 사이트를 만들도록 요구합니다, 특히 위험 요인이 높은 상황에서.
시카고 기반의 휴일 전자 상거래 사이트이며 여러 공급 업체의 제품을 제공합니다. 구운 과자, 의상, 크리스마스 트리 등 신선하게 수확한 지역 유기농 코벗 홀리 홀리 리스를 판매합니다. 비즈니스는 번창하고 분당 수십 명의 고객이 사이트에서 제품을 둘러보고 아름다운 홀리 홀리 리스를 신속하게 구매합니다. 휴일 정신이 가득한 상황입니다. 그러나 홀리 홀리 리스에 신선하게 꽃을 포함하는 지역 공급 업체 중 한 명이 실제로는 홀리 홀리 리스에 약속한 제품을 보내지 않을 것이라고 알려주기 위해 당신에게 전화를 걸어옵니다.
그는 매우 사과하며 전액 환불을 약속하지만 여전히 문제가 발생합니다. 그 홀리 홀리 리스는 여전히 당신의 사이트에 있으며, 시카고 사용자들에게 지역 무기화로 유기농 신선한 꽃이 들어가 있습니다. 이미 여러 사용자의 장바구니에 들어가 있습니다(하지만 이 부분은 고객 서비스에 넘기죠). 지금 이 혼란을 막아야 합니다. 새로운 지역 공급 업체를 발견했습니다. 꽃을 사용하지만 유기농 제품은 아니며 홀리만 유기농입니다. od-ISR이 이 문제를 어떻게 완화하는지 알아봅시다.
od-ISR에서, Next는 모든 페이지를 빌드 시간에 생성합니다 (1). CMS 및/또는 API로부터 데이터를 전달받아 이러한 페이지를 작성하는 이전 화살표는 생략했지만, 이러한 호출은 여전히 빌드 시간에 발생합니다. 우리의 매혹적인 이야기를 계속해 봅시다. 어느 날 밤, 서둘러 콘텐츠 제작자나 제품 관리자가 올해 가장 바쁜 판매일 전에 네 시간 전에 전화를 받고 성수기 활동(편집, 설명, 이미지 및 기타 데이터)를 즉시 CMS에서 수정해야 한다는 전화를 받는 상황이 벌어집니다. 홀리 리스 태그를. 가까이 있는 하인드만 충실한 콘텐츠 제작자는 그런 일을 합니다 (2). CMS는 데이터 풀의 변경 사항에 대해 특정 조치를 실행하는 웹훅으로 구성되어 있습니다. 이 경우, 인증을 위해 숨은 토큰을 전달하기 위해 Next 웹 서버에 요청을 보냅니다 (3). 이것은 대신 내부 회사 서버나 기계를 사용하여 수행할 수 있으며, 여기서는 API라고 부르고 클라이언트 측 상호 작용을 위해 무균형적으로 재사용할 수 있게 했습니다 (4). Next는 즉시 데이터 변경에 영향을 받는 지정된 페이지 또는 페이지를 재구축합니다 (5). 이제 새 홀리 리스 페이지를 얻었습니다.
그런데, CDN이 잘못된 정보를 표시하는 홀리 리스가 있는 냅을 즉시 제공 대신, 그 구식이라고 하는 위험 콘텐트를 제공해버릴 때 어떻게 해야 할까요? 이 아키텍처에서 가장 어려운 부분입니다. 많은 CDN은 캐시의 일부 또는 전체를 무효화하거나 지우는 기능을 제공하므로, CDN이 즉시 구식 페이지를 제공하는 대신 오리진 서버로 반환하도록 강제합니다 (12). 때로는 이 기능이 GUI에 기반하거나 콘솔을 통해 수행되기도 하며 때로는 API를 통해 프로그래밍적으로 제공됩니다. 일반적으로 질문하는 CDN에 특정하며, 특정 CDN을 사용하는 개발자들은 임시방편을 활용하고 있습니다. 위의 개발팀이 CDN을 무효화하는 방법을 갖고 있다고 가정합시다, 이는 이 문서의 범위를 벗어난 것입니다. 클라이언트가 CDN에 요청을 보내면 (8), 오리진 서버로 돌아가 (6) 신선한 페이지를 응답하고 (7) 사용자에게 반환되며 (9) 다시 한번 CDN에 캐시됩니다 (표시되지 않음). 다시 말해, 이러한 유형의 아키텍처는 클라이언트 측 JavaScript에서 한 개 이상의 API나 서비스로 추가 CRUD 요청을 만드는 것을 배척하지 않습니다 (10, 11).
여기서 우리는 stale-while-revalidate ISR 콘텐츠 생성의 시간 기반 지표에서 상당한 개선을 볼 수 있습니다. 여기서 콘텐츠는 요청에 따라 업데이트됩니다. 사용자가 방문하지 않아도 페이지가 ‘너무 오래된’ 상태가 되지 않을 것입니다. 대신 콘텐츠 생성자와 제품 관리자가 CMS나 데이터베이스를 업데이트하면 해당 콘텐츠는 요청을 받은 후 Next에 의해 다시 생성됩니다. 이는 웹훅이나 간단한 HTTP 요청을 통해 매개될 수 있으며 (Next의 경우) 비밀 토큰의 포함으로 인증됩니다. 페이지 재생성은 더 이상 사용자 상호작용을 통해 이루어지지 않습니다.
이 재생성은 여전히 즉각적이지 않다는 점을 강조해야 합니다. 심지어 한 페이지의 재생성에도 시간이 걸릴 수 있습니다. 하나의 제품을 변경하는 것은 웹사이트 구조에 따라 여러 페이지의 재생성이 필요할 수 있습니다. Next는 요청을 받고 페이지를 재구성하고 CDN을 무효화할 수 있으며, 이 작업이 GUI를 통해 수동으로 이루어지지 않을 경우입니다. 그 동안 사용자는 여전히 오래된 콘텐츠를 받게 될 것입니다. 실제로, 적절하게 설계된 애플리케이션에서 이러한 변경 사항은 캐시된 콘텐츠가 만료되기를 기다릴 수 없는 빈번한 데이터 변경을 필요로 하는 동적 웹사이트의 요구를 충족시키기에 충분히 빠를 것으로 예상됩니다.
우리 탐험이 시작될 때로 돌아가보면, 다양한 사용 사례에 대해 다른 렌더링 방법 대비 od-ISR이 상당한 개선임이 명백합니다. 대규모 전자상거래 사이트는 CSR만 있는 아키텍처에서 느린 로딩 속도를 겪을 것입니다. 서버에 추가된 계산 성능이 있더라도, 하나의 페이지가 여러 API 및 CMS로부터 데이터를 요구할 가능성이 높아 여전히 SSR 기반 아키텍처로 인해 더 긴 로드 시간이 필요할 수 있습니다.
게다가 제품이 자주 변경되지 않는 페이지에서는, 동일한 페이지를 사용자 요청마다 서버에서 반복해서 다시 렌더링하는 비효율성이 이 사이트에 이상적이지 않습니다. SSG는 사이트에 작은 변경 사항을 위해 전체 재구성이 필요하며, 수천 개 제품을 보유한 비즈니스는 단일 페이지나 심지어 한 줄의 변경에 대해 엄청난 재생성 시간을 겪게 될 것입니다. od-ISR은 자주 변하는 내용을 필요로 하는 사이트에 이상적이지만 변경 사항을 최대한 빨리 반영해야 하는 사이트에는 적합하지 않습니다. od-ISR은 온디맨드 변경의 이점과 정적으로 생성된 콘텐츠에 대상 페이지 재생성을 결합하여, 높은 리스크, 동적이고 빈번하게 변경되는 데이터를 보유한 비즈니스에 완벽하게 적합합니다.
지금 시점에서 Next는 온디맨드 증분 정적 재생성을 제공합니다. Vue 기반의 Nuxt는 ISR을 제공하지만 Next와 같이 온디맨드 페이지 재구성은 아직 제공하지 않습니다. 그러나 수많은 Github 이슈 티켓과 응답에 따르면 해당 기능은 준비 중에 있습니다! Next에서 od-ISR에 대해 더 알고 싶다면 여기에서 문서를 확인해보세요.
Stackademic
끝까지 읽어주셔서 감사합니다. 그 전에:
- 작가를 추천하고 팔로우해주시는 걸 고려해주세요! 👏
- 트위터(X), 링크드인, YouTube에서 우리를 팔로우해주세요.
- Stackademic.com을 방문하여 전 세계에서 무료 프로그래밍 교육을 민주화하는 방법에 대해 더 많이 알아보세요.