본문으로 건너뛰기Hwanseok Sim

작성일

VisitCall 개발기 4 — 관리자 웹: 정적 페이지에서 Next.js까지

관리사무소가 쓰는 관리자 웹을 문자 사용량 화면, 정적 HTML 통계 페이지, Next.js 대시보드로 세 번 다시 만든 과정과, 두 DB에 직접 붙는 구조의 비용(2025년 4~9월).

VisitCall 개발기 시리즈 4/8편. 전체 목록은 visitcall 태그에서 볼 수 있다.

입주민은 앱을 쓰지만 관리사무소는 웹을 쓴다. 방문 차량이 몇 대 들어왔는지, 세대별로 이번 달 방문 주차시간을 얼마나 썼는지, 업체가 직접 등록한 차량(택배, 점검 등)은 무엇인지를 한눈에 봐야 한다. 이 관리자 웹은 2025년에 세 번 만들었고, 세 번 모두 새 저장소에서 시작했다.

1차: 문자 사용량을 보던 React 앱

2025-04-07 첫 커밋의 관리자 웹은 Create React App(react-scripts) 기반의 단순한 앱이었다. 로그인, 대시보드, 세대 목록, 메시지 사용량 화면이 있었고 관리자 API 라우트 문서(admin_api_routes.md)를 같이 두었다. 1편의 SMS 링크 방식이 전제였으므로 할 일 메모의 첫 줄이 "메시지 사용량 메뉴 구체화, 전체 문자 로그를 호스트 ID 등으로 필터링"이었다. 이 저장소의 마지막 커밋은 4월 12일이다. 방문 등록이 문자 링크에서 앱 입력으로 바뀌고 나서는 문자 사용량을 볼 이유가 없어졌다.

업체 DB를 직접 읽는 통계 페이지

7월 16일에 새 저장소를 열었다. Express 서버 한 개(첫 커밋 기준 server.js 611줄)와 정적 HTML 한 장, 브라우저 스크립트 한 개(약 1,000줄)로 만든 통계 페이지였다. 업체 주차관제 DB(SQL Server)와 우리 MySQL에 동시에 연결했다.

server.js
const sql = require('mssql');          // 업체 주차관제 DB
const mysql = require('mysql2/promise'); // 우리 서비스 DB
// 두 DB의 접속 정보는 모두 .env에서 읽는다

업체의 입출차 이력과 우리 쪽 예약·세대 정보를 번호판으로 맞대면 등록한 방문 차량이 실제로 들어왔는지 볼 수 있다. 번호판 여러 개를 한 번에 붙여 넣는 다중 검색을 넣었다가 얼마 뒤 지우고 화면을 단순하게 줄였다. 지원센터 방문 차량 화면은 입주민이 아니라 관리사무소나 업체가 직접 등록한 택배·점검 차량을 따로 모아 보는 곳이다.

이 저장소에는 개선 제안 문서(WEB_IMPROVEMENTS_REPORT.md)가 있다. 서버·화면·성능을 훑고 적은 체크리스트다.

제안상황
일부 엔드포인트가 문자열 보간으로 WHERE 절을 만든다. 파라미터 바인딩으로 바꿀 것SQL 인젝션 위험
인증·권한이 없다. 최소한 API 키나 IP 제한을 둘 것사내망 전용이어도 필요
날짜가 YYYYMMDDHHMMSS 문자열이라 변환 코드가 흩어져 있다공통 유틸로 모을 것
정렬·페이지네이션이 브라우저에서 이뤄진다대용량이면 서버 측으로
전역 상태가 큰 단일 스크립트모듈로 나눌 것

이 목록은 개선 계획이면서 이 버전의 한계 목록이었다. 정적 HTML에 스크립트 하나로 시작한 구조는 화면이 늘수록 한 파일에 상태가 쌓였고, 보안 항목은 "나중에"로 밀렸다.

Next.js 14와 shadcn/ui로 다시

8월 31일에 새 저장소를 만들어 Next.js 14, TypeScript, Tailwind CSS, shadcn/ui로 다시 시작했다. 기능은 이전 버전과 비슷했고 구조를 바꿨다. 화면은 app/ 아래 페이지로, API는 app/api/ 아래 라우트 핸들러로 나눠서 통계, 입출차 이력, 방문 차량 목록, 지원센터 방문 차량, 세대별 방문, 허용 주차시간 API가 각각 하나의 라우트가 됐다. 차트는 recharts, 표와 폼은 shadcn/ui 컴포넌트를 썼다.

9월 4일에는 월별 허용 주차시간(세대마다 한 달에 방문 차량이 쓸 수 있는 시간)이 들어갔고, 이 기능은 월 이월 스케줄러까지 달고 관리자 웹의 중심이 된다(6편). 9월 11일에는 현장 단지용 저장소로 갈라 냈다.

9월 19일에는 일별 할당을 삭제했다. 11월 커밋에서는 일별 한도 값을 선택 항목(기본값 비움)으로 바꾸고 월별 허용시간만 필수로 다룬다. 한 세대에 일과 월 두 가지 한도가 있으면 관리하기 어려워서 월 단위 하나로 정리했다.

두 DB에 직접 붙는 구조의 비용

Next.js로 바꿔도 구조는 같았다. 관리자 웹이 서비스 서버의 API를 거치지 않고 MySQL과 SQL Server에 직접 붙는다. 화면 하나를 만드는 가장 짧은 길이라 기능을 빨리 늘릴 수 있었다. 2.0을 준비하며 다시 읽어 보니 대가가 컸다.

  • 인증이 없어서 주소를 아는 사람은 누구든 세대 정보를 볼 수 있다.
  • DB 접속 정보를 관리자 웹 서버가 직접 들고 있다.
  • 서비스 서버와 같은 테이블을 쓰는데 컬럼 정의가 서로 달라 어긋나기 시작했다. 허용시간 테이블이 대표적이다.
  • 입출차 기록 비활성화를 웹이 DB에 직접 쓰고 서버는 1분 캐시로 읽어서 반영이 한 박자 늦다.

이 중 상당수는 운영에서도 실제 문제로 나타났고(6편), 2.0에서 "관리자 웹은 DB에 붙지 않고 서버의 관리자 API만 호출한다"로 결정한 배경이 됐다. 기능은 세 번 이어받았지만 구조 개선은 이어받지 못했다.

이전·다음 글