- 발행일
바닐라 Jinja 화면과 React SPA가 한 저장소에 같이 있었다 — 세 번 시도한 전환이 어디까지 갔나
바닐라 Jinja 화면과 React SPA가 한 저장소에 같이 있었다
이 글은 2026년 9월에 당시 코드를 다시 보며 정리한 것입니다. 저장소는 퇴사 전에 스냅샷으로 보관한 것이라 커밋 이력이 없고, 경력기술서와 당시 쓴 지원서·이력서에 적은 담당 범위와 코드를 대조해 썼습니다. 날짜는 코드에 남은 시기 단서 기준입니다.
2021년 4월에 첫 직장으로 들어간 산업 AI 솔루션 기업에서 처음 맡은 화면은 공항 보안 검색 판독 시스템이었습니다. 엑스레이 영상 위에 AI가 찾은 물체를 상자로 그려 주고, 검색과 통계를 붙인 장비용 화면이에요. 이 화면을 바닐라 JavaScript에서 React SPA로 바꾸는 것이 그 시기 제가 제안해 관철한 일이었고, 경력기술서에도 그렇게 적어 두었습니다.
그런데 그 저장소를 지금 열어 보면 src/html/ 아래에 옛 화면의 서버 템플릿 다섯 개가 그대로 있습니다. 새 화면과 옛 화면이 한 저장소에 같이 살고 있었던 거예요. 그리고 2022년 중반에 제가 쓴 지원서를 다시 읽으니, 전환은 한 번이 아니라 세 번에 걸쳐 일어난 일이었습니다.
지원서가 말하는 세 번의 시도
당시 지원서의 경력 항목은 이 프로젝트를 세 토막으로 적고 있습니다. 회사와 발주처 이름은 빼고 옮깁니다.
| 시기 | 지원서 원문(발췌) |
|---|---|
| 2021.04~06 | "3차년도 FE 개발을 위해 리액트로 리팩토링을 시작하면서 예전의 바닐라 자바스크립트 코드를 고치고자 노력을 하였음 … flask로 되어 있는 백엔드와 협업 … 로그인, 설정 초기화, 분석, 통계를 위주로 개발을 하면서 sse 구조, 통계, 그래프 등에 대해 깊게 알 수 있는 기회가 있었으나 처음 개발을 하다보니 요구사항 분석이 잘 되지는 않았음" |
| 2021.10 | "예전에 사용하던 JS에서 나오지 않는 로딩창과 듀얼 모니터 부분에 대한 이슈 사항들을 고쳐서 완료 시키고자 함" |
| 2022.03~05 | "납품건으로 인한 … 제품을 react로 리팩토링 … 4/18 보안 전시회 사용(single monitor version) … 4월 두번째 주 납품 전 확인 명세서 받음(multi monitor version)" |
입사 첫 달부터 React로 다시 짜기 시작했고, 그해 10월에는 옛 JS를 다시 고치고 있었고, 이듬해 봄에 납품을 계기로 다시 리팩토링했습니다. "제안해서 관철"은 맞지만 한 번에 갈아탄 게 아니라, 옛 화면을 붙들고 있던 시기가 중간에 있었어요. 이력서에는 이 프로젝트의 개발 인원이 프런트 3명·백엔드 1명이고 제 담당은 "로그인, 설정, 웹소켓 아이콘"이라고 적혀 있습니다. 전환을 제안한 건 저였지만 화면 전체를 혼자 옮긴 건 아닙니다.
src/html/에 옛 템플릿이 남은 것도 이 시간표와 맞물립니다. 2021년 10월에 옛 JS의 로딩창과 듀얼 모니터 문제를 고치려면 옛 화면이 저장소 안에 살아 있어야 했을 테고, 그 뒤 납품용 리팩토링이 새 화면 쪽에서만 진행되면서 옛 파일은 지워지지 않은 채 남았다고 읽힙니다. 다만 그게 실제 순서였는지는 커밋이 없어 확인할 수 없습니다.
옛 화면은 어떻게 생겼었나
src/html/에는 파일 다섯 개, 703줄이 남아 있습니다. inspection.html이 436줄, settings.html이 242줄이고 나머지 셋은 로그인과 알림 창이에요. 판독 화면인 inspection.html은 이렇게 시작합니다.
<div id="mainPage-img-left" style="display: inline-block; position: absolute; width: 1920px; height: 848px;
background: url({{ url_for('video_1') }})">
<div id="boxes">
<svg id="boxes_svg" width="1920px" height="848px" version="1.1">
</svg>
</div>
</div>
{{ url_for('video_1') }}는 Flask의 Jinja 문법입니다. 지원서에 "flask로 되어 있는 백엔드"라고 쓴 그 서버가 영상 스트림 주소를 템플릿에 심어 주고, 그 위에 1920×848 고정 크기 SVG 하나를 얹어 판독 상자를 그리는 구조였어요. 상단 메뉴는 통계·판독·검색·설정·전원 다섯 개였고, id는 stats·detect·search였습니다.
여기서 하나 정직하게 적어야 할 게 있습니다. 경력기술서에는 "화면을 이동할 때마다 전체를 다시 그리니 답답했다"고 썼는데, 이 파일을 보면 검색 화면(#searchPage)과 통계 화면(#stats-page)이 같은 파일 안에 display: none으로 숨겨져 있습니다. 판독·검색·통계는 한 HTML 안에서 div를 켜고 끄는 식이었고, 페이지 단위로 갈라져 있던 건 로그인과 설정 창이었어요. 이 화면을 움직이던 스크립트는 스냅샷에 안 남아 있어서 실제로 화면 전환 때 무엇을 다시 그렸는지는 확인할 수 없습니다. 경력기술서의 그 문장은 코드보다 세게 쓴 셈입니다.
새 화면은 같은 메뉴를 어떻게 옮겼나
React 쪽 진입점인 src/App.js는 210줄이고, 라우트는 열 개입니다.
<Route path="/" element={<><VideoTop/><Main isSingleView={isSingleView} isMultiView={isMultiView}
isDualView={isDualView}/></>}/>
<Route path="/status" element={<><VideoTop/><StatusPage ... /></>}/>
<Route path="/search" element={<><VideoTop/><SearchList ... /></>}/>
<Route path="/thumb" element={<><VideoTop/><SearchThumb ... /></>}/>
<Route path="/detail" element={<><VideoTop/><Detail ... /></>}/>
<Route path="/main_side" element={<><VideoSide/><Dual ... /></>}/>
<Route path="/status_side" element={<><VideoSide/><StatusPage ... /></>}/>
...
옛 화면의 판독·검색·통계가 /·/search·/status로 왔고, 검색은 목록(/search)과 섬네일(/thumb)로, 상세(/detail)는 따로 라우트가 됐습니다. 그리고 그 다섯 개가 _side를 붙여 한 벌 더 있어요. 위쪽 다섯은 VideoTop, 아래쪽 다섯은 VideoSide를 머리에 얹습니다. 지원서의 "듀얼 모니터"와 "multi monitor version"이 가리키는 두 번째 창의 주소로 읽히는데, 이 복제를 누가 언제 넣었는지는 커밋이 없어 남아 있지 않습니다.
react-router-dom은 6.2, React는 18.2, 빌드는 react-scripts 4.0.3이었습니다. 설정 하나로 화면 모드를 가르는 코드도 여기 있어요.
const isSingleView = (config['config'] === "M1X1")
const isMultiView = (config['config'] === "M1X2")
const isDualView = (config['config'] === "M2X2")
서버의 set_config 응답에서 M1X1·M1X2·M2X2를 읽어 불리언 셋으로 만들고, 그 셋을 열 개 라우트의 모든 화면에 prop으로 넘깁니다. 같은 응답의 target(납품처 종류)으로 클래스 목록을 고르는데, 그 갈래는 따로 적었습니다. 경력기술서 2.3절에 "옵션을 설정으로 뺐다"고 쓴 것의 실제 모양이 이겁니다. 설정으로 뺀 건 맞는데, 그 설정이 컴포넌트마다 prop 세 개로 스며드는 방식이었어요.
옮기다 만 자리
옛 파일이 남아 있는 것 자체는 정리를 안 한 것이지만, 옛 구조가 새 코드에 그대로 배어 있는 자리가 따로 있습니다.
첫째, SVG 상자입니다. 옛 화면의 #boxes_svg라는 이름이 React 쪽 MainImage.js·MainBottom.js·DualImage.js 세 파일에 그대로 쓰입니다. 상자를 그리는 방식은 React polygon으로 바뀌었는데, 붙잡는 이름은 옛 것을 썼어요.
둘째, 화면 상태를 localStorage에 둔 것입니다. 옛 화면은 detect_on·alarm_on 같은 버튼을 class="on"으로 켰다 껐다 했는데, 새 화면은 같은 값을 localStorage의 detect·alarm 키에 씁니다. 소켓으로 detectOn·alarmOff 같은 메시지가 오면 localStorage에 적고, 설정이 저장되면 storage 이벤트로 창을 통째로 새로고침해요. SPA로 바꾸자고 한 이유가 "전체를 다시 그리지 말자"였는데 설정이 바뀌면 reload를 부르는 셈인데, 이 구조는 따로 적었습니다.
셋째, Redux입니다. package.json에 redux·react-redux가 있고 src/modules/에 리듀서 파일이 다섯 개 있지만, index.js의 Provider와 createStore는 주석 처리돼 있습니다. useSelector를 import한 파일이 셋인데 호출은 0건이에요. 경력기술서 기술 스택에 Redux를 적었는데, 이 스냅샷 기준으로는 붙어 있지 않습니다.
그때의 선택을 지금 읽으면
1년차였던 제가 제안한 건 "React로 다시 만들자"였고, 그건 실제로 됐습니다. 라우트가 생겼고 컴포넌트로 쪼개졌고, 경력기술서에 쓴 대로 회사별 화면을 조립할 수 있게 됐어요. 그건 코드가 뒷받침합니다. 다만 지원서가 보여 주듯 그 과정은 한 번의 전환이 아니라 첫 시도, 옛 JS 수리, 납품용 재작업의 세 토막이었고, 세 사람이 나눠 한 일이었습니다.
그리고 전환이 파일 단위로만 일어났다는 것도 코드가 보여 줍니다. 상태를 어디 둘지, 창끼리 어떻게 맞출지, 설정이 바뀌면 무엇을 다시 그릴지 같은 질문은 옛 화면의 답(전역 저장소에 쓰고 새로고침)을 그대로 가져왔어요. 제안서에 쓴 "전체를 다시 그리지 않는다"는 라우팅에서는 이뤄졌고 설정 변경에서는 안 이뤄졌습니다. 지원서 첫 항목에 스스로 "처음 개발을 하다보니 요구사항 분석이 잘 되지는 않았음"이라고 적은 것과 같은 자리예요.
검증
- 날짜 근거:
MainBottom.js의 주석 처리된 경로에2021-12-23시각 문자열이 6건 있고, 스냅샷 커밋은 2022-07-24입니다. 이 글의 날짜는 앞의 것을 썼습니다. 지원서의 시간표(2021.0406 시작, 2021.10 옛 JS 수정, 2022.0305 재작업)와 어긋나지 않습니다. src/html/5파일 703줄,inspection.html436줄,settings.html242줄은wc -l로 셌습니다.url_for는inspection.html에만 1건입니다.App.js라우트 10개(_side5개 포함),M1X1/M1X2/M2X2·AIRPORT/ENTERPRISE/INTEGRATION분기,storage이벤트reload는 파일에서 직접 확인했습니다.boxes_svg를 쓰는 React 파일 3개,useSelectorimport 3파일·호출 0건,Provider/createStore주석 처리는grep으로 확인했습니다.- 지원서·이력서 인용은 원문 PDF를 읽고 옮겼고, 회사·발주처 이름만 뺐습니다.
| 항목 | 경력기술서 | 지원서·이력서 | 코드 | 판정 |
|---|---|---|---|---|
| React SPA 전환 | 제안해서 관철 | 2021.04 시작, 10월 옛 JS 수정, 2022.03 재작업 / 프런트 3명 | src/html/ ↔ App.js 라우트 10개 | 일치, 단 세 번에 걸침·3인 작업 |
| 화면 이동마다 전체 재렌더 | 그렇다고 씀 | 언급 없음 | 판독·검색·통계가 한 HTML 안 display:none | 코드보다 세게 씀 |
| Flask 백엔드·SSE | 언급 없음 | "flask 백엔드", "sse 구조" | url_for, EventSource('/draw_box') | 일치 |
| 듀얼 모니터 | 단화면·양화면 모드 | 2021.10 듀얼 모니터 이슈, multi monitor version | _side 라우트 5개, M1X2/M2X2 | 일치 |
| CONFIG→TARGET 구분 | 언급 없음 | 2022.05 "CONFIG 값 … TARGET 대상으로 구분" | config(화면 모드)·target(클래스 목록) | 일치 |
| 기술 스택 Redux | 적음 | 적지 않음 | 스토어 미연결, 호출 0건 | 경력기술서와 어긋남 |
남은 것 · 한계
- 커밋 이력이 없습니다. 옛 파일이 언제 남았는지,
_side라우트를 누가 넣었는지, 프런트 세 사람 사이의 경계가 어디인지 이 저장소로는 알 수 없습니다. 이 글이 "제가 했다"고 쓴 범위는 경력기술서·이력서가 본인 작업으로 적은 SPA 전환 제안과 로그인·설정·웹소켓 아이콘까지입니다. - 옛 화면의 스크립트가 없습니다. HTML만 남아서, 실제로 화면 전환 때 무엇이 다시 그려졌는지는 확인하지 못했습니다. 경력기술서의 "전체를 다시 그린다"는 문장을 코드로 확인할 수도, 반박할 수도 없습니다.
- 세 번의 시도 사이에 무엇이 바뀌었는지는 모릅니다. 지원서는 시기와 목적만 적었고, 스냅샷은 마지막 상태 하나뿐입니다. 2021년 4월판과 2022년 봄판의 차이는 남아 있지 않습니다.
- 설정 변경 시 새로고침 구조는 그대로 남겼습니다. 4년 뒤 관계도 11개 화면을 렌더러 한 곳에서 바꿨다에서 설정을 뷰로 올려 화면 열한 개를 한 곳에서 바꾼 것과 견주면, 여기서는 설정이 바뀌면 창을 통째로 다시 띄웠습니다.
- 발주처 이름과 회사 로고가
inspection.html의title에 박혀 있습니다. 인용에서는 뺐고, 이 글에서 회사명은 쓰지 않았습니다.
관련 글: 산업 AI 솔루션 기업 프런트엔드 2021–2022 · 관계도 11개 화면을 렌더러 한 곳에서 바꿨다 · WebSocket connection to wss failed · 설정을 localStorage에 두고 소켓 메시지 16갈래로 창끼리 맞췄다 · 클래스 팔레트 20종이 다섯 파일에 복사돼 있고 Redux는 import만 남았다