집을 짓고 나서야 보이는 것들
내가 만든 사이트를 남의 눈으로 보는 법
unstackd.io가 온라인에 올라갔다.
도메인을 연결하고, 네임서버를 바꾸고, 브라우저에 주소를 쳤다. 익숙한 화면이 떴다. 히어로 슬로건, 타임라인, 푸터. 다 있었다. 기분이 좋았다.
그런데 기분이 좋은 것과 제대로 된 것은 다른 문제였다.
사이트를 하나의 "제품"으로 보고, 네 명의 전문가 눈으로 점검해봤다. 시니어 웹개발자, UI/UX 디자이너, 보안 전문가, 서비스 개발자. 물론 네 명 다 AI였다. 하지만 각각의 관점에서 나온 지적은 진짜였다.
결과는 예상보다 길었다.
첫 번째 지적: ignoreBuildErrors: true.
이건 내가 몇 주 전에 빌드 에러가 나올 때마다 붙인 설정이다. "에러가 있어도 일단 빌드해라." 프로그래밍을 모르는 사람의 논리로는 합리적이었다. 에러가 나면 사이트가 안 뜨고, 사이트가 안 뜨면 진행이 안 되니까, 일단 띄우고 보자.
문제는 — 이게 안전벨트를 끊고 운전하는 것과 같았다는 거다.
TypeScript라는 건 코드에서 타입이 맞는지 검사하는 도구다. "이 변수는 숫자여야 합니다"라고 정해두면, 실수로 문자열을 넣었을 때 빌드 단계에서 경고를 준다. 그 경고를 전부 무시하겠다는 설정을 켜둔 거다.
고치는 데 10개의 커밋이 필요했다.
두 번째 지적: 보안.
관리자 API에 입력 검증이 없었다. 무슨 뜻이냐면 — 누군가 서버에 데이터를 보낼 때, 보내야 할 필드("제목", "내용", "상태" 같은 것) 외에 임의의 필드를 끼워넣을 수 있었다는 뜻이다.
1인 운영 사이트에 해커가 올 확률은 낮다. 하지만 "확률이 낮다"는 건 "안 해도 된다"는 뜻이 아니다.
화이트리스트라는 걸 만들었다. 허용된 필드 목록을 미리 정해두고, 목록에 없는 건 전부 버린다. 코드로는 이렇게 생겼다:
const ALLOWED_FIELDS = ['title_ko', 'title_ja', 'title_en', 'desc_ko', ...];
function sanitizeBody(body) {
return Object.fromEntries(
Object.entries(body).filter(([key]) => ALLOWED_FIELDS.includes(key))
);
}
한 줄도 직접 쓰지 않았다. AI가 썼다. 내가 한 건 "이런 보안 취약점이 있으니 고쳐라"고 지시한 것뿐이다.
세 번째는 좀 부끄러운 문제였다.
홈페이지 아래에 거대한 빈 공간이 있었다. 엔트리가 9개밖에 없는데, 스크롤을 한참 내려야 푸터가 나왔다. 원인을 찾아보니 두 가지였다. 히어로 영역에 "최소 높이 75%"가 걸려 있었고, 콘텐츠 영역에 "남은 공간을 전부 채워라"는 속성이 붙어 있었다.
만들 때는 몰랐다. "이렇게 하면 예쁘게 나오겠지" 하고 넣은 속성들이 엔트리가 적을 때 빈 공간을 만들어냈다. 완성된 후에야 보이는 것들.
그리고 가장 혼란스러웠던 문제: Git 구조.
프로젝트 폴더 안에 .git이 두 개 있었다. 루트에 하나, site/ 안에 하나. 둘 다 같은 GitHub 저장소를 가리키고 있었지만, 각각 다른 시점의 코드를 추적하고 있었다. 어떤 수정은 site/site/ 경로에 적용되고, 어떤 건 site/에 적용되어 있었다.
이건 처음 프로젝트를 만들 때 구조를 제대로 잡지 않아서 생긴 문제다. Episode 02에서 "계획이 코드보다 먼저다"라고 배웠는데, 그때의 교훈이 여기서도 반복된 거다.
오늘 site/.git을 삭제하고, 중복 파일들을 정리했다. 이전 버전 프로토타입 3개, 완료된 작업 지시서 3개, 복제된 문서 7개, 빈 폴더, 캐시 파일. 전부 합치면 스무 개가 넘는 불필요한 파일이 프로젝트에 쌓여 있었다.
이 과정에서 깨달은 게 하나 있다.
만드는 것은 흥분되는 일이다. 기능을 추가하고, 화면에 뭔가가 나타나고, 배포 버튼을 누르면 세상에 공개된다. 이건 성취감이 있다.
하지만 점검하는 것은 지루한 일이다. 타입 에러 수정, 보안 검증, 빈 공간 제거, 폴더 정리. 이건 새로운 걸 만드는 게 아니라 이미 만든 걸 다듬는 일이다. 화면에 보이는 변화가 없을 때도 있다.
저녁 시간밖에 없는 사람에게, 이 지루한 시간을 따로 확보하는 건 어렵다. 제한된 시간에 새 기능을 하나 더 만들고 싶은 유혹이 있다.
그래서 이걸 규칙으로 정하기로 했다. 새 기능 세 개를 만들면, 한 번은 점검 시간을 갖는다. 3:1 규칙.
집을 지었으면, 가끔은 지붕을 올려다봐야 한다.
🔧 이 에피소드의 기술 용어 해설
ignoreBuildErrors Next.js 설정에서 TypeScript 타입 에러가 있어도 빌드를 강제 통과시키는 옵션. 개발 초기에는 편리하지만, 배포 단계에서는 런타임 에러를 빌드 시점에 잡지 못하게 만드는 위험한 설정.
입력 검증 / 화이트리스트 (Input Validation / Whitelist) 서버가 외부에서 받는 데이터를 검사하는 절차. 화이트리스트는 "허용된 것만 통과"시키는 방식. 반대는 블랙리스트(금지된 것만 차단)인데, 보안에서는 화이트리스트가 더 안전하다.
Git (.git)
코드의 버전 관리 시스템. 모든 변경 이력을 기록한다. .git 폴더가 프로젝트 안에 두 개 있으면, 두 개의 독립적인 이력이 충돌하게 된다.
flex-1 / min-h
CSS(웹 스타일링 언어)의 속성. flex-1은 "남는 공간을 전부 차지해라", min-h는 "최소한 이 높이는 유지해라"는 뜻. 콘텐츠가 적을 때 의도치 않은 빈 공간을 만들 수 있다.
커밋 (Commit) 코드 변경사항을 하나의 단위로 저장하는 행위. "이 시점의 코드 상태를 기록한다"는 뜻. 10개의 커밋은 10번의 의미 있는 변경이 있었다는 뜻이다.