프로젝트 사례

Canada Mortgage Compare

캐나다 주요 5개 은행이 공시한 모기지 금리를 하루 두 번 모아 비교해 주는 독립 레퍼런스 사이트입니다. DB도 백엔드도 없는 순수 정적 Next.js 사이트로, 금리는 GitHub Actions에서 TypeScript 수집기가 긁어와 JSON으로 커밋합니다.

라이브 프로젝트 보기 →
카테고리
Data Product / Rate Comparison
플랫폼
Next.js
클라이언트
SERVITH (Internal product)
유형
Web App (Static Data Product)
역할
1인 개발로 수집기 구조와 모기지 계산, 데이터 무결성 규칙, UI, CI 스케줄링까지 맡았습니다.
목표
캐나다에서 집을 사려는 사람이 주요 은행의 공시 금리를 정직하게 비교할 수 있게 합니다. 계산은 정확하게 하고, 데이터의 한계는 분명히 밝힙니다.
범위
정적 페이지 6개(비교, 계산기, 히스토리, 방법론, 면책, 홈)와 은행별 수집기 5개, 하루 두 번 도는 GitHub Actions 파이프라인으로 구성했습니다.

개요

Canada Mortgage Compare는 캐나다 주요 은행이 공시한 모기지 금리를 모아 나란히 보여 줍니다. 첫 화면에는 부풀리지 않고 딱 하는 일만 적어 두었습니다. "Compare advertised Canadian mortgage rates."

지금은 은행 5곳의 상품 64개를 추적하고 있습니다. CIBC, National Bank of Canada, RBC, Scotiabank, TD이며, 하루 두 번 갱신합니다.

이 프로젝트를 설명하는 가장 좋은 방법은 무엇을 두지 않았는지 말하는 것입니다. DB도, 백엔드도, 유료 서비스도 없습니다. 사이트 전체가 Cloudflare Pages에 올라간 정적 Next.js 빌드이고, 금리 데이터는 저장소에 커밋된 JSON으로 존재합니다.

정직성 문제

금리 비교 사이트에는 구조적인 신뢰 문제가 있습니다. 레퍼런스인 척하지만 실은 대출 상담을 넘기기 위한 유입 퍼널인 경우가 대부분이기 때문입니다. 이 프로젝트는 반대편에 서기로 했고, 그 입장을 코드로 강제했습니다.

*

공시 금리는 제안이 아닙니다. 실제로 받게 될 금리는 신용도와 소득, 계약금, 물건에 따라 달라집니다. 이 사실을 흐리지 않고 그대로 밝힙니다.

*

`Offer`나 `Product` 구조화 데이터를 넣지 않습니다. SEO 마크업은 Organization, WebSite, BreadcrumbList까지만 씁니다. 보장되지도 않는 금리에 Offer 마크업을 붙이는 것은 코드 주석에 적어 둔 대로 기계가 읽는 형식으로 거짓말을 하는 일이고, 이는 글로 하는 거짓말보다 나쁩니다.

*

BMO는 등록해 두었지만 꺼 두었습니다. 켜려면 허가를 받아야 하기 때문이며, 그래서 BMO 금리는 공개하지 않습니다.

구조

DB를 두지 않은 이유

금리는 하루에 두 번 바뀌고 데이터의 양도 적습니다. DB를 두면 운영할 것만 늘어나고 얻는 것이 없습니다. 그래서 이런 흐름으로 만들었습니다. CI에서 수집기가 돌고, 검증을 거쳐, JSON을 커밋하면, 그 커밋이 Cloudflare Pages 배포를 일으킵니다.

next.config.ts에는 output: "export"와 trailingSlash: true를 설정해 두었습니다. 정적 빌드에서는 SSR과 ISR, 라우트 핸들러, 서버 액션을 쓸 수 없기 때문에 누군가 이런 것을 추가하면 빌드가 그 자리에서 실패합니다. 제약이 스스로를 지키는 셈입니다.

수집기

은행별 수집기를 TypeScript로 직접 작성해 collectors/에 두고 한곳의 레지스트리에 등록했습니다. HTML 파싱은 Cheerio가 맡습니다. Playwright도 준비해 뒀지만 필요할 때만 설치하도록 해 두었고, 지금 켜져 있는 수집기가 전부 평범한 fetch로 동작하기 때문에 실제로는 한 번도 실행되지 않았습니다.

가드레일은 실제로 일어나는 사고를 막기 위한 것입니다. 은행이 페이지를 개편하는 바람에 셀렉터가 엉뚱한 칸을 집어 오는 경우입니다.

*

금리는 0.5~15% 범위 안에 들어와야 합니다

*

1.5%p가 넘게 튀면 이상치로 봅니다

*

타임아웃 30초, 요청 간격 1.5초, 이상치는 한 번만 재시도합니다

타임존을 따진 스케줄

GitHub Actions 크론은 0 12,21 * * *입니다. 하루 두 번이지만 일부러 12시간씩 나누지 않고 9시간과 15시간 간격으로 두었습니다. 12:00 UTC는 토론토 오전 8시라 밤사이 바뀐 금리를 개장 시점에 잡을 수 있고, 21:00 UTC는 토론토 오후 5시로 마감 뒤이면서 밴쿠버로는 오후 2시입니다. 앞서 쓰던 0 6,18은 06:00 UTC가 토론토 새벽 2시여서 한 번을 버리는 셈이라 접었습니다.

파이프라인은 데이터가 실제로 바뀌었을 때만 커밋합니다. 커밋 한 번이 곧 배포 한 번이기 때문입니다.

운영 측면에서 달라진 점

*

은행이 계산하는 방식 그대로입니다: 캐나다식 반기 복리와 은행이 가속 납입액을 실제로 정하는 방식을 따라, 미국식 추정치가 아니라 현실에 맞는 숫자를 보여 줍니다.

*

믿을 수 있는 금리 히스토리: 실제로 바뀐 것만 기록하고 새 상품은 은행이 밝힌 유효일자로 남기기 때문에, 멀쩡한 상품이 사라졌다고 나오거나 없던 변화가 기록되지 않습니다.

*

오해를 부르는 선택지가 없습니다: 데이터가 뒷받침할 때만 필터를 보여 줍니다. 주별 필터는 특정 주에서만 파는 상품을 가진 은행이 들어오면 자동으로 다시 나타납니다.

*

문제는 방문자보다 먼저 잡힙니다: 은행별 페이지를 자동으로 테스트하기 때문에, 은행이 사이트를 바꾸면 라이브 사이트에 잘못된 숫자가 뜨기 전에 먼저 알 수 있습니다.

*

가볍고 운영 비용이 낮습니다: DB도, 백엔드도, 유료 서비스도 없이 돌아가고, 금리 그래프도 무거운 차트 도구 없이 직접 그려서 보여 줍니다.

이 프로젝트가 보여주는 역량

*

문제에 맞는 가장 작은 구조를 고르고, 그 선택을 빌드가 지키게 만든 사례

*

데이터 무결성을 기술 문제가 아니라 편집 책임으로 다룬 관점

*

당연해 보이는 구현이 오히려 틀리는 영역에서 도메인 계산을 제대로 맞춘 경험

*

가드레일과 테스트, 정직한 실패 방식을 갖춘 스크래핑 파이프라인

*

인프라 비용을 사실상 들이지 않고 실제 데이터 제품을 운영하는 방법


Live: https://canadamortgage.servith.com/

Role: 1인 개발 (수집기 → 모기지 계산 → 무결성 규칙 → UI → CI)

고객 포털무료 견적 받기