노잼선거 구성도
5일 만에 만든 시민 사이트의 4-레이어 구성. 시민 → 도메인 → Next.js → 데이터·API·Redis.
시민
누적 3,705명, 페이지뷰 16,291회 (D-day 기준). 디시인사이드·카톡 단톡방·구글/네이버 검색으로 유입. Bounce Rate 25% — 진성 유저 중심.
도메인 · 호스팅
nojam.kr (가비아 .kr 도메인, 연 ₩28,000). Vercel Hobby 플랜 (무료 — 100GB 대역폭/월 한도, 노잼선거는 ~3GB 사용). Edge에서 동적 렌더링.
Next.js 14 앱
App Router 기반. TypeScript + Tailwind CSS. 서버 컴포넌트 위주, 인터랙티브한 부분만 클라이언트("use client"). 페이지 8개 카테고리:
- 메인 — 카운트다운, Top 5, 내 동네 선택
- 지역 (16개) — 5종 선거직 탭, 후보 카드, 동네 순위 카드
- 통계 (7개) — Top 5, 재산, 정당별 전과, 구청장·시·도의원·구·시·군의원, 무투표 당선
- 게시판 — 비회원 글쓰기 + 본인 글 삭제
- 관리자 — 데모/원본 토글 (쿠키 기반 인증)
- 회고 — 시민 사이트의 짧은 일생 정리
- 소개·개인정보 — 사이트 정체성·중립성·정보 처리 방침
데이터 · API · Redis
정적 데이터는 빌드 시점 JSON 파일 (선거 끝나면 더 안 바뀜). API Routes는 동적 데이터용 (투표·게시판·인증, Edge runtime). Upstash Redis (Vercel KV)는 카운터·게시글·rate limit 저장 — 무료 50MB 한도 안.
nojam-crawler (별도 Python 프로젝트)
6,714명 후보 데이터는 노잼선거 본 사이트가 아니라 nojam-crawler라는 별도 Python 프로젝트에서 수집했다. 선관위(info.nec.go.kr)에서 5종 선거직별로 크롤링 → 엑셀 다운로드 → JSON 변환 → 노잼선거 data/*.json으로 복사.
- 왜 Playwright? 선관위가 일반 HTTP 요청을 차단해서 (가드 로직 있음). 실제 브라우저 자동화로 우회.
- 왜 크롤러 4개로 분리? 선거직마다 페이지 구조·파라미터가 달라서. 시·도의원·구·시·군의원은 선거구 단위가 복잡해서 별도 처리.
- 코드 라인 수: 약 917 LOC.
nec_candidate_crawler.py274 +nec_crawler_playwright.py206 + 의원 크롤러 2개 × 188. - 실행 흐름: 시·도 코드 조회 → 선거구 코드 조회 → 후보자 표 HTML 파싱 → 엑셀 다운로드 → JSON 변환.
- 유지보수 부담: 선관위 페이지 구조 바뀌면 깨짐. 다행히 4년 단위라 다음 지선에 한 번만 점검.
※ 크롤러 자체는 노잼선거 본 사이트에 포함되지 않고 별도 리포지토리로 관리. 본 사이트는 크롤러가 만든 JSON을 정적으로 import 할 뿐.
이 구성도를 본인 프로젝트로 설명할 수 있게 공부하려면 → 공부하기 가이드
기술 도식 너머의 5일짜리 이야기가 궁금하면
→ 회고 읽기