본문 바로가기

JAMstack 아키텍처 이해

사전 생성 마크업·CDN·분리된 API로 구성되는 JAMstack의 성능·보안·배포 특성과 적용 한계를 판단합니다.

웹 서버가 요청마다 HTML을 만들고 데이터베이스를 조회하는 방식에서는, 페이지를 보낼 때마다 애플리케이션 서버가 함께 일합니다. 이 방식은 사용자마다 다른 응답과 최신 데이터를 만들기 쉽지만, 요청량에 맞춰 서버와 데이터베이스, 캐시를 운영해야 합니다. 반대로 미리 만든 HTML 파일을 보내고, 필요한 데이터만 API로 받는 방식도 있습니다. 어느 쪽이 알맞은지는 페이지를 누구에게 공개하는지와 데이터를 얼마나 자주 바꿔야 하는지에 달려 있습니다.

JAMstack은 정적 생성, CDN 캐시, API 분리, 단순한 배포를 기준으로 웹의 구조를 나누는 아키텍처입니다. 이 글에서는 JAMstack의 개념과 구성 요소, 실제로 체감되는 이점과 한계를 살펴보고, 공개 콘텐츠와 사용자별 작업을 어떻게 나누는지 익힙니다. 이 글에서 빌드 시점은 배포할 파일을 만드는 때이고, 요청 시점은 사용자가 페이지나 데이터를 요구하는 때입니다. 정적 파일에도 자바스크립트가 들어갈 수 있으므로, 정적으로 배포했다고 상호작용이 없는 화면이 되는 것은 아닙니다.


JAMstack이란?

JAMstack이라는 이름은 JavaScript, APIs, Markup의 머리글자에서 왔습니다(지금의 공식 사이트는 Jamstack으로 적습니다). 하지만 특정 프레임워크나 세 기술을 모두 써야 하는 규격은 아니며, 핵심은 공개 페이지를 미리 만들어 두는 사전 렌더링(pre-rendering)과, 페이지를 제공하는 일을 필요한 백엔드 기능과 분리하는 것입니다(Jamstack 공식 설명). 미리 만든 공개 HTML만으로 충분한 문서 사이트도 있고, 같은 페이지에 API로 읽는 개인 데이터를 더하는 서비스도 있습니다. 도구의 이름보다 “페이지를 언제 만들고, 무엇을 요청마다 확인해야 하는가?”를 먼저 따져 봅니다.

J(JavaScript)는 브라우저에서 화면의 상호작용을 맡습니다. React, Vue, Angular 같은 프레임워크나 순수 자바스크립트로 동적인 화면을 만들지만, 비밀 값과 권한 확인, 데이터 변경은 서버의 API가 처리해야 합니다. A(APIs)는 데이터베이스 접근, 사용자 인증, 결제처럼 서버에서 해야 하는 기능을 API로 제공하는 부분입니다. 기존 서버나 마이크로서비스, Stripe 같은 외부 서비스, 헤드리스 CMS, 서버리스 함수로 제공할 수 있으며, API를 따로 둔다고 백엔드가 사라지는 것은 아닙니다.

M(Markup)은 HTML과 CSS, 자바스크립트 같은 자원을 배포하기 전에 미리 빌드하는 부분입니다. 정적 사이트 생성기(static site generator)가 마크다운 파일이나 CMS의 데이터로 빌드 시점에 HTML을 만들고, 만든 파일은 CDN에 배포합니다. 어떤 경로를 미리 만들지는 도구와 설정에 따라 정합니다.

JAMstack은 웹을 이루는 요소를 나누고 가능한 한 많은 일을 빌드 시점에 처리해, 요청마다 서버에서 화면을 만드는 일에 덜 의존하게 하는 것을 목표로 합니다. LAMP나 MERN 같은 이름은 쓰는 기술의 조합일 뿐이라, 그 이름만으로 페이지를 요청마다 만드는지는 알 수 없습니다. 예를 들어 MERN의 React 화면을 정적 파일로 배포하고 Express API를 따로 둘 수도 있으므로, 기술 목록과 렌더링, 배포 방식을 구별해야 합니다.

공개 페이지는 미리 만들고 사용자별 작업은 API로 처리합니다

JAMSTACK ARCHITECTURE

공개 페이지는 미리 만들고 사용자별 작업은 API로 처리합니다

Git·CMS 콘텐츠로 정적 파일을 빌드해 CDN에 배포합니다. 브라우저는 그 파일을 받고, 인증·결제·사용자별 데이터가 필요할 때 별도 서버 API를 호출합니다.

공개 페이지는 미리 만들고 사용자별 작업은 API로 처리합니다Git와 CMS의 공개 콘텐츠로 정적 파일을 빌드해 CDN에서 브라우저에 제공합니다. 브라우저의 동적 요청은 서버 API에서 인증·권한·쓰기를 처리하고 데이터 결과를 반환합니다.Git · CMS공개 콘텐츠·템플릿빌드HTML · CSS · JS 생성정적 파일 · CDN배포 단위로 제공브라우저페이지·상호작용서버 API인증·쓰기·권한 확인서버 데이터DB · 비밀 값파일 전달요청응답조회결과

빌드·배포 경로

콘텐츠 변경 → HTML·CSS·JS 생성 → CDN 배포 → 브라우저 전달. Git 변경·CMS 발행을 재빌드와 연결하는 방식은 호스팅의 설정에 따라 달라집니다.

동적 요청 경로

브라우저 요청 → 서버 API의 인증·권한 확인 → DB·외부 서비스 → 응답. API는 기존 서버, 서버리스 함수 등으로 제공할 수 있습니다.

비밀과 개인 데이터의 경계

비밀 키와 서버 권한 검사는 공개 번들에 넣지 않습니다. 사용자별 응답은 공통 정적 파일·공유 캐시와 별도 정책으로 처리합니다.

새 빌드가 사용자에게 보이는 시점

정적 HTML은 빌드 시점의 데이터입니다. 갱신은 재빌드·배포와 캐시 정책을 함께 설계합니다. 해시 자산 URL이나 캐시 무효화는 선택한 도구·배포 방식으로 확인합니다.

위 그림은 JAMstack의 두 경로를 보여 줍니다. 위쪽은 빌드와 배포 경로로, Git과 CMS의 공개 콘텐츠와 템플릿으로 HTML, CSS, 자바스크립트를 빌드하고, 그 결과를 CDN에 배포 단위로 올려 브라우저에 전달합니다. Git의 변경이나 CMS의 발행을 다시 빌드하는 일과 연결하는 방식은 호스팅 서비스의 설정에 따라 다릅니다.

아래쪽은 동적 요청 경로로, 브라우저가 서버 API에 요청을 보내면 API가 인증과 권한을 확인하고 데이터베이스나 외부 서비스를 거쳐 결과를 돌려줍니다. 그림의 설명처럼 비밀 키와 서버의 권한 검사는 공개 번들에 넣지 않고, 사용자별 응답은 공통 정적 파일이나 공유 캐시와 다른 규칙으로 다룹니다. 또 정적 HTML은 빌드한 시점의 데이터이므로, 새 내용이 사용자에게 보이는 시점은 다시 빌드하고 배포하는 일과 캐시 규칙에 함께 달려 있습니다.


JAMstack의 이점

성능

캐시된 공개 페이지는 요청마다 HTML을 만들 필요가 없어 서버의 처리 시간을 줄입니다. CDN의 엣지 캐시는 사용자와 가까운 곳에서 파일을 보내 전송 지연을 줄이고, 정적 파일은 브라우저와 CDN에 캐시하기 쉽습니다. 다만 화면이 완성되는 속도는 이미지와 자바스크립트의 크기, 추가로 부르는 API의 응답에도 영향을 받고, 캐시가 없으면 원본에서 파일을 가져와야 하므로 모든 요청이 같은 속도를 보장받지는 않습니다.

보안

정적 파일을 요청할 때는 애플리케이션 서버의 로직이나 데이터베이스에 직접 닿지 않아, 그만큼 공격받을 수 있는 곳이 줄어듭니다. 하지만 별도로 제공하는 API의 권한 검사와 비밀 보호, 설정은 여전히 직접 챙겨야 하고, API와 CMS, 빌드 도구와 그 의존성까지 저절로 안전해지지는 않습니다. 인증이나 결제를 전문 서비스에 맡기더라도 사용자 입력과 권한을 검증하고 비밀 키를 지키는 일은 서비스가 해야 하며, 화면에서 버튼을 숨기는 것으로 서버의 권한 검사를 대신할 수 없습니다.

확장성

캐시된 정적 파일의 요청은 CDN이 나눠 처리하므로 트래픽이 몰려도 잘 버팁니다. 미리 만들어 캐시한 응답은 요청마다 데이터베이스를 조회하지 않아도 됩니다. 다만 로그인이나 주문 같은 동적 요청이 몰리면 API와 데이터베이스의 용량과 제한이 병목이 될 수 있고, 브라우저가 API를 직접 부른다고 데이터베이스의 부하가 줄지는 않습니다. 서버리스 함수도 실행 환경을 자동으로 늘려 주지만 동시 실행 수와 늘어나는 속도에 한도가 있으며(AWS Lambda의 확장 동작), 데이터베이스 연결과 외부 API의 제한도 함께 봐야 합니다.

비용

정적 파일 제공은 요청마다 애플리케이션 서버를 실행하는 비용을 줄입니다. 하지만 실제 비용은 호스팅 요금제와 전송량, 빌드 사용량에 API와 함수의 비용을 더해 비교해야 합니다. 서버리스 함수는 요청 수뿐 아니라 실행 시간과 메모리, 동시 실행 설정이 요금에 영향을 주며(AWS Lambda 요금), 관리형 플랫폼에 운영의 일부를 맡겨도 함수와 권한, 데이터, 비용의 설정은 여전히 관리해야 합니다.

개발 경험

호스팅 서비스의 빌드와 배포 파이프라인을 저장소에 연결하면, Git에 푸시하거나 CMS에서 발행하는 것을 빌드와 배포의 시작 조건으로 정할 수 있습니다. 이런 자동화를 흔히 CI/CD라고 부르며, 푸시했다고 빌드와 배포의 성공이 보장되는 것은 아닙니다. 준비된 API를 조합해 화면 개발에 집중할 수 있지만, API가 약속하는 형식과 권한, 데이터 흐름은 여전히 알아야 합니다. 페이지 수와 데이터 조회량이 늘면 빌드 시간이 길어질 수 있으므로, 자동화와 빠른 빌드는 따로 확인합니다.


JAMstack을 이루는 기술

정적 사이트 생성기

정적 사이트 생성기는 마크다운이나 CMS에서 가져온 콘텐츠를 템플릿과 합쳐 배포할 HTML 페이지를 만들며, CSS와 자바스크립트 자원도 함께 처리할 수 있습니다. 빌드에 넣은 공개 데이터는 그 순간의 스냅샷이라서, 원본 데이터베이스가 나중에 바뀌어도 이미 배포한 HTML은 저절로 바뀌지 않습니다.

React 프레임워크인 Next.js는 SSR과 CSR도 지원하지만, 정적 내보내기(static export) 모드를 쓰면 빌드할 때 만들 수 있는 결과를 정적 파일로 배포합니다. 요청 정보에 기대는 서버 기능은 이 모드와 구별해야 합니다. Astro는 여러 UI 프레임워크를 골라 쓰면서 정적 HTML을 먼저 보내는 콘텐츠 중심 프레임워크이고, Nuxt는 Vue 기반으로 SSG, SSR, CSR을 지원합니다. Ruby 기반의 Jekyll은 블로그와 문서 사이트에, Go 기반의 Hugo는 빠른 빌드 속도로 많이 쓰입니다.

헤드리스 CMS

헤드리스 CMS(headless CMS)는 콘텐츠를 관리하는 기능과 사용자에게 보여 주는 화면을 분리한 콘텐츠 관리 시스템입니다. 보통 API로 콘텐츠를 전달하고, 페이지를 어떻게 보여 줄지는 별도의 프런트엔드가 정합니다. 헤드리스라는 말이 편집자용 관리 화면까지 없다는 뜻은 아닙니다. CMS의 공개 콘텐츠는 빌드할 때 읽을 수도 있고 실행 중에 API로 읽을 수도 있으며, CMS에서 발행한 변경을 정적 HTML에 반영하려면 다시 빌드하고 배포하는 과정을 연결해야 합니다. Strapi, Contentful, Sanity, DatoCMS가 대표적이며, 예전 이름이 Netlify CMS인 Decap CMS는 콘텐츠를 Git에 저장하는 방식이라 API 중심 CMS와 저장 방식이 다릅니다(Decap CMS 소개).

서버리스 함수

서버리스 함수(serverless function)는 요청이나 이벤트에 반응해 코드를 실행하며, 서버를 준비하고 실행 환경을 관리하는 일을 플랫폼에 맡기는 방식입니다. 실행할 서버는 분명히 있고, 개발자는 코드와 권한, 비밀 값, 시간 제한, 데이터 연결을 설계해야 합니다. JAMstack에서는 API 계층을 만드는 데 쓰며, AWS Lambda, Azure Functions, Google Cloud Run functions, Netlify Functions, Vercel Functions 등이 있습니다.

CDN

CDN은 여러 지역의 엣지 서버에 콘텐츠를 캐시해 전달합니다. 이 글에서는 미리 빌드한 파일을 제공하는 역할에 집중하지만 CDN이 정적 파일만 다루는 것은 아니며, 캐시의 유효 기간과 갱신, 무효화 규칙에 따라 사용자가 받는 버전이 달라질 수 있습니다. Cloudflare, Amazon CloudFront, Fastly가 대표적이고, Netlify와 Vercel은 자체 CDN을 갖추고 있습니다.


언제 만들고 언제 확인할까

공통 콘텐츠의 갱신 주기와 사용자별 데이터 경계를 따로 정합니다

DATA PLACEMENT

공통 콘텐츠의 갱신 주기와 사용자별 데이터 경계를 따로 정합니다

모든 사용자에게 같은 공개 데이터인지, 얼마나 오래된 내용을 허용하는지 먼저 확인합니다. 비밀·결제·개인별 응답은 공개 페이지의 생성 시점과 별도로 서버에서 검증합니다.

Build time · 문서·마케팅 페이지

배포 때 확정해도 되는 공개 콘텐츠를 사전 생성합니다. CDN은 만들어 둔 파일을 제공하며, 내용이 바뀌면 다시 빌드·배포합니다.

Revalidation · 공개 상품 목록

잠깐 오래된 내용을 허용한다면 갱신 정책을 둡니다. 증분 재생성이 가능한 서버·플랫폼에서는 주기나 이벤트에 맞춰 재생성할 수 있습니다. 정적 파일 호스팅만으로 이 기능이 자동 제공되지는 않습니다.

Request time · 최신 공개 데이터

요청 시점의 데이터가 필요한 경우 API나 요청 시 렌더링을 사용합니다. 최신성은 데이터 원본·응답 캐시 정책도 함께 결정합니다. 매번 생성한다는 사실만으로 항상 최신임을 보장하지 않습니다.

개인별 읽기 · 내 주문 목록

인증된 사용자의 권한 범위로 서버에서 조회합니다. 응답과 캐시를 사용자에 맞게 분리하고, 다른 사용자에게 공유되지 않도록 처리합니다.

서버에서 검증 · 비밀·결제·데이터 변경

비밀 키는 서버에 두고 사용자 입력·권한을 검증합니다. 상품 목록은 사전 생성하더라도 주문 가격·재고·결제는 작업 시점에 서버에서 확인할 수 있습니다. 한 페이지도 여러 시점과 계층을 조합합니다.

위 그림은 데이터를 빌드 시점과 요청 시점 가운데 어디서 처리할지 나누는 기준입니다. 먼저 모든 사용자에게 같은 공개 데이터인지, 얼마나 오래된 내용을 허용하는지 확인합니다. 문서나 마케팅 페이지처럼 배포할 때 확정해도 되는 공개 콘텐츠는 빌드 시점에 미리 만들고, 내용이 바뀌면 다시 빌드해 배포합니다. 공개 상품 목록처럼 잠깐 오래된 내용을 허용할 수 있다면 다시 확인(revalidation)하는 규칙을 둘 수 있는데, 증분 재생성을 지원하는 서버나 플랫폼에서만 주기나 이벤트에 맞춰 다시 만들 수 있고, 정적 파일 호스팅만으로는 이 기능이 생기지 않습니다.

요청할 때의 최신 데이터가 필요하면 API나 요청 시점의 렌더링을 씁니다. 다만 데이터 원본과 응답의 캐시 규칙도 최신성을 정하므로, 매번 만든다고 항상 최신인 것은 아닙니다. 내 주문 목록처럼 사용자마다 다른 데이터는 인증된 사용자의 권한으로 서버에서 조회하고, 응답과 캐시를 사용자별로 나눠 다른 사용자에게 공유되지 않게 합니다. 비밀 키와 결제, 데이터 변경은 서버에서 사용자의 입력과 권한을 검증하며, 상품 목록을 미리 만들었더라도 주문할 때의 가격과 재고, 결제는 그 순간 서버에서 확인합니다. 그림 아래의 설명처럼 한 페이지도 여러 시점과 계층을 함께 씁니다.

쇼핑몰 페이지로 나눠 보기

공개 상품 소개와 로그인한 사용자의 주문 내역이 함께 있는 쇼핑몰을 생각해 봅니다. 첫 그림의 위쪽인 Git과 CMS, 빌드, CDN, 브라우저로 이어지는 공개 파일 전달 경로와, 아래쪽인 브라우저, 서버 API, 서버 데이터로 이어지는 요청과 응답 경로가 서로 다른 시점에 일한다는 점이 핵심입니다.

정보와 작업이 사례의 처리 위치이유
모든 사용자에게 같은 상품 소개빌드한 HTML과 CDN바뀐 뒤 다시 빌드할 때까지의 지연을 허용하는 공개 정보
지금의 재고와 주문 가격 확인주문할 때의 서버 API오래된 소개 페이지나 브라우저가 보낸 가격을 그대로 믿지 않음
로그인한 사용자의 내 주문인증과 권한을 확인하는 API사용자마다 결과가 다르고 다른 사람에게 공개할 수 없음
결제 서비스의 비밀 키서버의 비밀 설정공개 HTML이나 자바스크립트에 넣으면 사용자에게 그대로 전달됨

개인 주문 API는 요청마다 접근 권한을 확인해야 합니다(OWASP의 권한 검증 지침). 개인 응답을 공통 정적 파일로 만들거나 공유 CDN 캐시에 넣지 않도록 캐시 규칙도 정합니다. 저장하면 안 되는 민감한 응답에는 서버가 Cache-Control: no-store를 보낼 수 있고, private는 공유 캐시에는 저장하지 않되 개인 브라우저 캐시에는 저장을 허락한다는 점에서 no-store와 다릅니다(MDN의 Cache-Control).

CMS를 고쳤는데도 이전 상품 소개가 보인다면, 콘텐츠 원본이 바뀌었는지, 새 빌드가 성공했는지, 배포된 버전이 바뀌었는지, CDN과 브라우저의 캐시가 갱신되었는지를 차례로 확인합니다. 데이터베이스의 변경이나 Git 푸시 하나만 보고 새 파일이 사용자에게 전달되었다고 결론 내릴 수는 없습니다. 둘째 그림의 다시 확인하는 방식도 지원하는 서버나 플랫폼에서 설정해야 하며, Next.js의 정적 내보내기는 앞에서 말한 증분 재생성, 곧 증분 정적 재생성(incremental static regeneration, ISR) 같은 서버 기능을 지원하지 않습니다(Next.js 정적 내보내기의 지원 범위). Next.js에서 ISR이 정해 둔 주기나 이벤트에 맞춰 페이지를 다시 만드는 과정은 증분 정적 재생성 (ISR)에서 다룹니다.

더 깊이: 브라우저와 CDN에 서로 다른 캐시 기간을 주는 방법

CDN을 쓰는 사이트는 같은 응답을 브라우저에는 짧게, CDN에는 길게 캐시하고 싶을 때가 많습니다. CDN의 캐시는 운영자가 새 배포에 맞춰 비울 수 있지만, 사용자마다 흩어진 브라우저의 캐시는 서버가 원하는 때에 비우기 어렵기 때문입니다. Cache-Control의 s-maxage가 이 구분을 해 줍니다. 브라우저 같은 개인 캐시는 s-maxage를 무시하고, CDN이나 프록시 같은 공유 캐시는 max-age나 Expires 대신 s-maxage를 따릅니다. 그래서 Cache-Control: max-age=60, s-maxage=3600은 브라우저에는 1분, 공유 캐시에는 1시간 동안 신선하다는 뜻이 됩니다.

공유 캐시에도 여러 단계가 있습니다. 회사의 프록시와 CDN이 함께 있다면 s-maxage는 둘 모두에 적용되므로, CDN에만 다른 규칙을 주기 어렵습니다. 2022년 6월에 나온 RFC 9213은 특정 종류의 캐시만 겨냥한 헤더의 규칙을 정하고, 그 첫 예로 CDN-Cache-Control을 정의했습니다. 이 헤더를 지원하는 CDN은 응답에 이 헤더가 있으면 Cache-Control과 Expires를 무시하고 이 값만 따르며, 다른 캐시는 계속 Cache-Control을 따릅니다.

어느 방법이든 캐시해도 되는 응답에만 써야 합니다. 로그인한 사용자의 주문 내역처럼 사람마다 다른 응답은 앞에서 본 것처럼 private나 no-store로 공유 캐시에 남지 않게 합니다. 지시어의 정의는 MDN의 Cache-Control 설명과 RFC 9213에 있습니다.


JAMstack이 알맞은 경우와 한계

JAMstack은 블로그, 포트폴리오, 마케팅 페이지, 문서 사이트처럼 모든 사용자에게 같은 공개 내용을 보여 주고, 바뀐 뒤 다시 빌드해 배포하기까지의 지연을 허용할 수 있는 사이트에 잘 맞습니다. 트래픽이 갑자기 몰릴 수 있는 이벤트 페이지나, 상품 목록은 미리 만들고 결제처럼 동적인 부분은 API로 처리하는 쇼핑몰 같은 혼합 방식에도 쓸 수 있습니다.

한계도 있습니다. 콘텐츠와 페이지가 아주 많아지면 빌드 시간이 길어지므로 증분 빌드 같은 최적화가 필요합니다. 실시간 채팅이나 주식 시세처럼 사용자마다 계속 바뀌는 데이터는, 다음 글에서 볼 WebSocket이나 Server-Sent Events 같은 실시간 통신과 API를 조합해야 해서 구성이 복잡해집니다. 인증을 다룰 때는 API의 인증과 권한 검사, 쿠키를 보낼 도메인과 요청 규칙, 사용자별 응답의 캐시를 함께 설계해야 하며, JAMstack이 세션 쿠키를 금지하거나 JWT를 꼭 쓰라고 정한 것은 아닙니다. 서버 중심 개발에 익숙한 개발자라면 새 작업 흐름과 도구에 적응하는 시간도 필요합니다.


정리

  • JAMstack은 공개 페이지를 미리 만들어 CDN으로 보내고, 사용자별 작업은 분리된 API로 처리하는 아키텍처이며, 세 기술을 모두 써야 하는 규격은 아닙니다.
  • 빌드 시점에 만든 HTML은 그 순간의 스냅샷이라, 원본이 바뀌면 다시 빌드하고 배포해야 하며, 사용자가 새 내용을 보는 시점은 캐시 규칙에도 달려 있습니다.
  • 정적 파일 제공은 성능과 확장성, 비용에 유리하지만, API와 데이터베이스의 용량, 권한 검사, 비밀 보호는 여전히 직접 관리해야 합니다.
  • 정적 사이트 생성기, 헤드리스 CMS, 서버리스 함수, CDN을 조합하며, 서버리스에도 서버와 한도, 비용이 있습니다.
  • 공개 정보는 빌드 시점에, 최신 데이터와 개인 데이터는 요청 시점에 처리하고, 가격과 재고, 결제는 작업할 때 서버에서 확인합니다.
  • 개인 응답은 공유 캐시에 넣지 않고, no-store와 private의 차이를 알고 캐시 규칙을 정합니다.

연습 문제

1. 세 기능의 처리 위치

강의 소개는 모든 사용자에게 같고 하루 뒤에 반영되어도 됩니다. 수강생별 결제 내역은 본인만 읽을 수 있어야 하고, 환불에는 서버의 비밀 키가 필요합니다. 세 기능을 각각 어디서 처리할지 이유와 함께 설명해 보세요.

해설 보기

강의 소개는 허용된 하루의 지연 안에서 다시 빌드해 배포하는 공개 HTML로 제공할 수 있습니다. 결제 내역은 인증된 사용자의 권한을 서버 API에서 확인해 조회하고, 사용자별 응답을 공유 캐시에 섞지 않습니다.

환불은 비밀 키를 가진 서버에서 권한과 환불 조건을 검증한 뒤 실행합니다. 화면에서 환불 버튼을 숨기는 것은 권한 검증이 아닙니다.

2. 오래된 가격

오전 10시에 가격 10,000원을 넣은 HTML을 빌드해 배포했습니다. 10시 2분에 서버의 실제 가격이 12,000원으로 바뀌었고, 다음 빌드와 배포는 10시 5분입니다. 가격을 따로 조회하는 API가 없을 때, 10시 4분에 이 페이지를 연 사용자는 어떤 가격을 볼까요? 그 사용자가 10,000원으로 주문하면 서버는 무엇을 기준으로 판단해야 할까요?

해설 보기

10시 4분에는 아직 10시의 HTML을 제공하므로 10,000원이 보입니다. 원본의 가격이 바뀌어도 이미 만든 파일의 내용은 바뀌지 않습니다.

주문을 받은 서버는 그 순간의 실제 가격 12,000원과 재고, 권한을 확인해야 합니다. 가격이 바뀌었다면 새 가격을 알리고 다시 확인받거나 주문을 거절하는 등 정해 둔 규칙으로 처리하며, 브라우저가 보낸 금액을 그대로 결제 가격으로 믿으면 안 됩니다. 10시 5분에 새 HTML을 배포해도 이미 열린 탭에는 옛 가격이 남을 수 있고 캐시도 영향을 주므로, 다시 빌드하는 것은 표시를 최신으로 만드는 수단일 뿐 주문할 때의 서버 확인을 대신하지 못합니다.

3. 정적 화면은 되고 API는 실패

CDN의 공개 상품 HTML은 잘 보이는데, 주문 API가 503을 돌려줍니다. 상품 소개와 주문 완료 가운데 무엇을 보여 줄 수 있을까요? API가 실패했을 때 빈 주문 목록이나 주문 성공으로 표시해도 될까요?

해설 보기

CDN이 보내는 상품 소개는 보여 줄 수 있지만, API가 실패한 주문을 완료되었다고 표시하면 안 됩니다. 실패는 “주문을 처리하지 못했습니다” 같은 별도의 상태로 보여 주고, 빈 목록 같은 성공 응답으로 바꾸지 않습니다.

다시 시도하는 버튼을 둔다면, 주문 생성처럼 데이터를 바꾸는 작업은 같은 주문이 두 번 만들어지지 않도록 막는 방법도 함께 설계해야 합니다. 공개 파일을 전달하는 쪽이 정상인지와 API가 정상인지는 따로 확인합니다.

다음 글 실시간 웹 통신에서는 폴링, Server-Sent Events, WebSocket처럼 서버와 브라우저가 실시간으로 데이터를 주고받는 방법을 비교합니다.