Minibase API의 JS 렌더링 원리

URL을 Markdown으로 바꾸는 일에서 어려운 쪽은 Markdown이 아닙니다. fetch입니다. 현대 웹의 절반은 콘텐츠를 JavaScript로 그리기 때문에, 평범한 HTTP 요청에는 빈 껍데기만 돌아옵니다. 순진한 해법, 즉 모든 페이지를 헤드리스 브라우저로 렌더링하는 방식은 확실하지만 느리고 비쌉니다. Minibase API는 계층형 접근을 택했습니다. 그래서 대부분의 페이지는 비용이 거의 들지 않고, 렌더링이 정말 필요한 페이지만 그 값을 냅니다.

1계층: 서버 측 fetch

모든 요청은 실제 브라우저 지문을 쓰는 평범한 fetch()로 시작합니다. 무언가 나가기 전에 URL은 SSRF 방어를 통과하는데, 여기서 사설 주소와 루프백, 클라우드 메타데이터 주소가 차단됩니다. 고객을 대신해 임의의 URL을 가져오는 엔드포인트라면 타협할 수 없는 부분입니다.

HTML은 스트리밍 파서(Cloudflare의 HTMLRewriter — DOM 라이브러리도, 추가 메모리도 없습니다)로 처리합니다. script, style, nav, footer, aside 같은 잡음을 걷어내면서 제목, 문단, 목록, 인용, 코드, 링크, 이미지 같은 콘텐츠 요소를 모읍니다. 엔티티는 디코딩하고, 제목에서는 사이트 이름 접미사를 정리합니다. 정적 페이지와 서버 렌더링 페이지, 즉 웹의 대부분은 여기서 이야기가 끝납니다. 빠르고 저렴합니다.

밀도 검사

1계층 추출이 끝나면 엔진은 하나만 묻습니다. 이게 실제 콘텐츠인가, 아니면 빈 앱 껍데기인가? 몇 가지 신호를 봅니다.

  • 추출된 텍스트가 의심스러울 만큼 짧은가?
  • 텍스트 대 마크업 비율이 지나치게 작은가? (즉 <div>만 잔뜩이고 문장은 거의 없는가)
  • HTML이 루트 노드가 비어 있는 싱글 페이지 앱 마운트 지점처럼 보이는가?
  • 서버가 봇 차단 상태 코드(403/429/503)를 돌려줬는가?

통과하면 1계층 결과를 그대로 받습니다. JS 껍데기로 보이면 요청의 단계가 올라갑니다.

2계층: 헤드리스 렌더링

단계가 올라가면 페이지를 헤드리스 브라우저에 태우고, 네트워크가 잠잠해질 때까지 기다린 뒤, 완전히 렌더링된 HTML을 가져와 똑같은 추출 파이프라인에 다시 넣습니다. Markdown 품질은 같고, 이번에는 JavaScript로 그려진 콘텐츠까지 들어 있습니다.

중요한 것은 이 일이 밀도 검사가 필요하다고 판단할 때만, 혹은 여러분이 명시적으로 render: "always"를 넘길 때만 일어난다는 점입니다. render: "never"를 넘기면 아예 끌 수 있습니다. 저렴한 계층이 저렴하게 유지되는 이유가 여기 있습니다. 브라우저가 필요 없던 페이지에 브라우저 값을 내지 않는 것입니다.

플랫폼 어댑터

어떤 URL은 HTML을 긁는 것보다 공식 채널을 쓰는 편이 낫습니다. 호스트가 알려진 플랫폼과 일치하면 엔진은 페이지를 가져오는 대신 어댑터로 보냅니다. 예를 들어 YouTube는 자막 채널을 통합니다. 저희가 선을 긋는 지점도 여기입니다. 유령 계정도, 로그인 뒤를 긁는 일도 없습니다.

캐싱

결과는 URL과 옵션을 키로 24시간 캐시됩니다. 같은 페이지를 다시 읽으면 즉시 돌아옵니다. 캐시를 건너뛰고 새로 가져오려면 fresh: true를 넘기면 됩니다.

이 구조가 중요한 이유

계층형 설계가 가격을 정직하게 만듭니다. 단순 Markdown 변환은 저희에게 1센트의 일부밖에 들지 않고, 그래서 1,000페이지당 $2입니다. 렌더링은 더 들기 때문에 더 높은 별도 계층이고, 렌더링이 실제로 돌았을 때만 청구합니다. 모든 응답에 담긴 meta.tier 필드가 어느 경로로 여러분의 페이지가 처리됐는지 정확히 알려줍니다.

가장 까다로운 페이지로 시험해 보세요 →

Continue reading

더 스마트하게 저장할 준비가 되셨나요?

클릭 한 번으로 모든 웹페이지를 Markdown으로 변환하세요.

Chrome에 추가