낭 만 당

첫 3개월, 프론트엔드개발자로의 첫 사회 생활..기술적 회고 본문

Programming Record/Frontend

첫 3개월, 프론트엔드개발자로의 첫 사회 생활..기술적 회고

낭만알파카 2026. 3. 25. 15:05

 

2026년 1월, 회사에 입사하면서 처음으로 “회사에서 웹을 만드는 사람”이 됐습니다.

첫 출근 주에 느낀 감정은 생각보다 단순했습니다..

빨리 도움이 되고 싶고, 동시에 문득문득 “내가 알고 있던 것들이 여기서도 진짜 통할까?” 하는 불안도 같이 따라왔죠.

 

학교 과제나 개인 프로젝트에서는 일정이 조금 밀려도 “내가 감당하면 그만, 다시 하면 그만”이라는 여지가 있었는데, 회사에서 만든 웹은 운영팀/CS/영업/그리고 고객의 시간이 같이 걸려 있었습니다. 그게 체감되는 순간, ‘같은 코드를 짜도 결이 다르다’는 걸 자연스럽게 알게 됐어요.

저에게 할당된 이슈는 온전희 "저만의 것"이었습니다. 제가 짠 코드의 책임은 제가  감당하는 것이었죠,,, (몇번 사고쳤습니다 허헣)

 

돌이켜보면 지난 3개월은 “기능을 만드는 법”보다 “기능이 살아있게 만드는 법”을 배우는 시간이었습니다.

이 글은 그 과정에서 제가 자주 부족함을 느꼈고, 결국 어떤 방식으로 스스로를 메웠는지 정리해보려는 회고입니다.

 

실무 JavaScript, 이론 말고 ‘현장 감각’

1) 비동기는 await 문제가 아니라 “상태” 문제였다

입사 초반에는 저도 당연히 “요청 보내고 성공하면 토스트 띄우고 끝”처럼 접근했어요. 그런데 항공 견적/신청/관리 플로우를 만들다가 운영에서 바로 문제가 터졌습니다.

사용자 입장에서는 화면이 한 번에 끝나는 것처럼 보이는데, 실제로는 이런 것들이 한 흐름으로 연결돼야 하더라고요.

  • 견적 요청 입력(유효성)
  • 요청 생성(로딩/중복 방지)
  • 요청 상태 변화(대기/확정/반려 등)
  • 리스트/상세/수정/취소 같은 화면 전환

문제는 여기서였어요. 예를 들어 사용자가 저장 버튼을 연속으로 누르면 중복 요청이 생길 수 있고, 네트워크가 느리면 로딩 상태가 꼬이면서 사용자가 “저장됐는지” 확신을 못 하게 됩니다. 또 실패했을 때 메시지가 애매하면 CS로 바로 문의가 들어오기도 하고요.

그래서 이후에는 구현 순서를 바꿨습니다.

먼저 실패 시나리오를 적었어요. 중복 클릭, 타임아웃, 권한 오류, 입력 누락 같은 것들. 그리고 그 다음에 “그 실패가 화면에서는 어떻게 보여야 하는지”를 정했습니다. 버튼 disabled는 언제 걸지, 스피너는 어디에 둘지, 에러 문장은 어떤 톤으로 줄지, 재시도 UX는 어떻게 만들지요. 그 다음에 API 호출 코드를 붙였습니다.

그때 확실히 느꼈습니다. 비동기는 결국 “코드가 기다리는 것”이 아니라 “사용자가 기다리는 동안의 정합성”이더라고요.

 

2) 최적화는 성능 수치보다 ‘손맛’에 더 가까웠다

2월 초에 이미지 관리 화면을 개선하면서 밀러컬럼(Miller Columns) 형태의 컴포넌트를 새로 만들었어요. 요구사항대로 기능도 구현했고, 운영에 붙이고 나서 “고도화”가 시작됐습니다.

처음엔 단순히 “컬럼을 늘려가며 탐색하는 UI”가 되면 된다고 생각했는데, 실제 데이터가 쌓이고 클릭이 빠르게 이어지니까 문제가 드러났습니다.

  • 선택이 바뀔 때마다 리스트 전체가 다시 렌더링되면서 클릭 반응이 느려지고
  • 스크롤 위치가 예상과 다르게 튀면서 사용자가 “내가 뭘 선택했지?”를 놓치기도 했고요

그래서 결론은 이거였어요. 성능보다 중요한 건 “반응”이었습니다. 사용자가 느끼는 끊김을 없애는 게 핵심이었고, 결국 저는 아래처럼 접근을 바꿨습니다.

상태를 덩어리로 들고 가지 말고 컬럼 단위로 나눠서 변경 범위를 줄이고, 무거운 렌더는 메모이제이션/키 설계를 다시 손봤습니다. 그리고 빈 상태/로딩 상태/에러 상태를 각각 분명하게 보여주면서, UI가 적어도 “지금 무슨 일이 일어나고 있는지”를 말해주게 만들었습니다.

그때 처음으로 “몇 ms 줄이기”가 아니라 “끊기지 않는 경험”이 최적화라는 걸 제대로 이해했어요.

memozation의 중요성...깨달았습니다.

 

3) TypeScript는 ‘안전장치’라기보다 경계를 세우는 도구였다

초반에는 타입 오류를 없애는 것 자체가 목표였는데, 시간이 지나니까 TypeScript가 저한테 계속 말 걸어주는 느낌이 들더라고요. “너 여기서 뭐가 뭔지 모호하지 않게 분리해봐”라고요. (주접,,)

디자인 에디터에서 템플릿/페이지별 백업 및 버전 관리 기능(3/3~3/4)을 만들 때 이게 특히 크게 왔습니다. 처음엔 API 응답 형태를 그대로 화면 상태로 들고 가려 했는데, 금방 한계가 왔어요. 백업 단위(템플릿/페이지)랑 버전 규칙(정렬/식별), 그리고 복원/비교/삭제 권한 같은 것들이 한 화면 로직 안에서 서로 섞이면서 복잡해졌거든요.

그래서 결국 세 덩어리로 나누는 쪽을 택했습니다.

  • 서버 응답 타입
  • UI에서 실제로 필요한 도메인 모델(화면에서 최소 형태)
  • 폼 입력 상태(사용자 편집 상태)

타입을 나눈다는 건 결국 “레이어가 뭘 책임질지”를 정하는 일이더라고요. 그 이후로 기능 추가가 훨씬 덜 무섭게 느껴졌습니다.

 

협업을 하면서 배운 ‘함께 만드는 소프트웨어’

신입 때 제가 가장 힘들었던 건 기술 자체라기보다, “내가 만든 변경이 다른 팀 일에 어떤 영향을 줄까?”를 상상하는 일이었습니다. 기획/디자인/운영과 대화하면서 반복해서 같은 교훈을 얻었어요.

 

1) 요구사항은 구현 전에 한 번 더 다시 써야 한다

예를 들어 고객센터 1:1 문의를 계층형 쓰레드 구조로 바꾸는 작업은 단순히 UI만 바꾸는 문제가 아니었습니다. “대화가 흐름으로 이어져야 한다”는 요구는 결국 추상적이었고, 그걸 그대로 구현하면 사람마다 해석이 달라지더라고요.

그래서 처음엔 저도 기획 문장을 그대로 코드로 옮기려 했는데, 계속 흔들렸습니다.

  • 부모 문의/답변 구분이 어디서부터 확정되는지
  • 운영자는 어디를 어떤 단위로 찾아야 하는지
  • 사용자는 어떤 순서로 읽어야 하는지

기본 정의가 없으니 구현이 흔들리는 건 당연하더라고요.

그래서 그 다음부터는 이해한 요구사항을 제가 다시 “내 말로” 정리해서 공유했습니다.

  • 사용자가 보는 흐름(읽는 순서)
  • 운영자가 처리하는 흐름(검색/정렬/상태)
  • 예외 케이스(삭제된 글, 비공개, 답변 지연)

그리고 이걸 문장으로 정리해서 확인받고 나서 코드를 썼더니, 중간 수정이 줄고 커뮤니케이션 비용도 확 내려갔습니다.

 

2) UX는 ‘있다/없다’가 아니라 “찾을 수 있느냐”였다

1월 말 메시지 센터에서 필터/검색 기능을 붙였고(1/28), 1월에는 쿠폰 삭제/회수/검색/필터링 및 UI도 구현했습니다(1/26). 기능을 추가했다고 생각했는데, 현장에서는 그게 “업무 속도”로 평가되더라고요.

제가 초반에 놓쳤던 디테일은 결국 이런 것들이었습니다.

  • 빈 결과일 때 무엇을 보여줄지
  • 검색 조건이 남아 있을 때 사용자가 “왜 안 나오지?”를 이해할 수 있는지
  • 필터 초기화/해제 흐름이 직관적인지
  • 회수/삭제 같은 위험 작업에서 실수를 막는 문장과 확인 UX가 있는지

결국 제품 신뢰도는 화려한 기능이 아니라, 사용자가 실수하지 않게 만드는 작은 장치에서 나온다는 걸 배웠습니다.

 

기술적 한계를 마주하고 극복한 사례

제가 “이건 혼자 머리로 들고 가면 위험하겠다”라고 느낀 순간은 주로 데이터와 정책이 복잡해지는 기능에서 왔어요. 가장 기억에 남는 건 디자인 에디터 백업/버전 관리(3/3~3/4)였습니다.

백업이 템플릿 단위/페이지 단위로 나뉘고, 버전이 쌓이며(정렬/식별) 복원/삭제 같은 되돌리기 어려운 작업도 생길 수 있고, 운영자가 실수하면 고객 화면이 바로 망가질 수도 있는 구조였거든요.

그래서 “일단 만들고 고치자”가 잘 통하지 않는 영역이었습니다.

그때 제가 했던 대응은 두 가지였어요.

  • 먼저 규칙을 정했습니다. 백업의 단위, 버전 이름 규칙, 기본 정렬, 삭제 가능 조건 같은 정책을 먼저요.
  • 그 규칙을 코드가 아니라 UI에도 반영했습니다. 사용자가 규칙을 어길 수 없게, UI가 자연스럽게 흐름을 따르도록 만든 겁니다.

그리고 이 과정에서 제일 도움이 됐던 건 ‘혼자 판단하지 않기’였습니다. 중간중간 더 경험 많은 동료에게 설계를 보여주고, 애매한 부분은 빨리 질문해서 피드백을 반영했어요. 신입일수록 “잘 모르겠다”를 빨리 꺼내는 게 팀 전체의 비용을 줄인다는 걸 배웠습니다.

컨디션이 안 좋은 날도 있었고(사랑니…ㅠ), 그럴수록 생산성보다 “사고 확률”을 낮추는 쪽으로 방향을 잡게 됐습니다. 그래서 이후엔 아래 원칙을 개인 규칙처럼 지키려고 했어요.

  • 하루 단위로 “오늘 끝낼 수 있는 최소 단위”를 정의하기
  • 막히는 작업은 숨기지 않고, 현재 상태/리스크/다음 액션을 문장으로 공유하기
  • 에너지가 떨어진 날은 신규 기능보다 리팩터링/문서화/예외 케이스 정리처럼 실수를 줄이는 작업으로 무게중심을 옮기기

 

다음 3개월의 목표

이제 앞으로의 3개월은 “돌아가는 코드”보다 “지속 가능한 코드” 쪽에 더 힘을 주려고 합니다.

  • 변경 비용을 줄이는 구조(경계, 책임, 도메인 모델)를 먼저 고민하고 기능을 붙이기
  • 사용자와 운영을 함께 보기. 실패/예외/권한/로깅까지 포함해서 “운영 가능한 화면”을 기본값으로 만들기
  • 협업의 언어를 더 단단하게 만들기. 요구사항을 내 말로 다시 쓰고 의사결정 근거를 남겨서 팀이 같은 그림을 보게 만들기

지난 3개월은 “나는 개발자다”라는 확신을 주기보다는, “배워야 할 게 너무 많다”는 사실을 선명하게 보여준 시간이었습니다.

다만 그 불안이 방향을 만들어줬다는 점에서, 저는 이 시간동안 많은 것들을 배우고 경험한 것 같습니다.

 

했던 작업을 기술적으로 풀어쓴 기록 (업무기록 기반)

아래는 “무슨 기능을 했다”가 아니라, 제가 실제로 어떤 식으로 설계/구현했는지(데이터, 상태, 예외, UX, 유지보수 관점) 기준으로 정리한 내용입니다.

 

1) 쿠폰 삭제/회수/검색/필터링 기능 + UI 구현

 

- 문제: 쿠폰 데이터는 많고, 운영자가 원하는 건 “빠르게 찾고, 실수 없이 회수/삭제하는 것”이었습니다. 단순 검색만으로는 작업 속도가 안 나왔고, 위험한 액션(회수/삭제)에서 실수가 나올 가능성이 컸습니다.

- 구현 포인트

    - 검색: 검색바 좌측에 ‘필드 선택 드롭다운’을 넣고, 전체/ID/쿠폰코드/쿠폰명/할인가격/주문최소금액/사용방식/메모/비고/생성일/상태 등으로 스코프를 제한해 검색 정확도를 올렸습니다. 필드에 따라 placeholder가 동적으로 바뀌게 해서 사용자가 “지금 어떤 기준으로 찾는지”를 잊지 않게 했습니다.

    - 회수(일괄 처리): 테이블 체크박스로 다건 선택 후 일괄 회수하도록 했고, 이미 사용된 쿠폰(status: 100)은 선택 불가로 막아서 ‘불가능한 작업’ 자체가 UI에서 안 일어나게 만들었습니다.

    - 삭제(가드레일): 회수된 쿠폰(status: 99)만 삭제 가능하도록 정책을 걸었습니다. 즉 “삭제”는 단독 기능이 아니라 “회수 → 삭제”라는 워크플로우로 강제해서 데이터 무결성과 운영 안정성을 확보했습니다. 삭제 전 확인 다이얼로그로 실수 방지도 추가했습니다.

    - 상태 모델링: 회수됨(status: 99)을 추가해서 쿠폰의 생명주기가 코드로도 명확히 보이도록 했고, 미구현 상태는 주석 처리/정리로 혼선을 줄였습니다.

 

2) 메시지 센터(카카오 알림톡/SMS·MMS/이메일) 필터 및 검색 

 

- 목표: 운영자가 발송 내역을 “기간 + 상태”로 빠르게 좁히고, 수신자 정보(이름/전화/이메일)로도 바로 찾을 수 있게 하는 것이었습니다.

- 구현 포인트

    - 공통 필터 설계: 3개 탭(카카오/SMS·MMS/이메일)이지만 필터 UX는 통일되게 가져가되, 탭별로 유효한 검색 필드만 활성화되게 구성했습니다.

    - 날짜 필터: 발송일 기준으로 기본값을 넉넉하게 잡고(예: 2025-01-01~오늘), 전체/오늘/이번주/이번달/지난 7일/지난 30일 같은 프리셋을 제공해 운영자가 반복 작업을 빠르게 하게 했습니다.

    - 검색 필드: 수신자/전화번호/이메일로 검색이 가능하도록 했고, 리스트 컬럼에도 분류/수신자/전화/이메일을 명시해서 “검색 결과가 왜 나왔는지”가 화면에서 설명되게 했습니다.

    - 수신 실패 토글: 날짜 조건 안에서 실패 케이스만 모아 보는 토글을 추가해서, 운영 이슈 대응(실패 재처리/확인)이 빨라지도록 했습니다.

 

3) 이미지 관리 카테고리 분리 + 밀러컬럼 컴포넌트(탐색 UI) 구현 

 

- 배경: 기존 이미지 관리 카테고리가 ‘상품코드 여행 지역’ 분류와 결합되어 있었는데, 이러면 이미지 분류 정책이 상품 정책에 종속되어 운영이 어려워집니다. 다만 기존 분류 DB는 보존해야 했고, 이후에 카테고리를 이동시킬 수 있어야 했습니다.

- 구현 포인트

    - 데이터 전략: ‘기존 카테고리/DB는 유지’하면서, 이미지 관리 전용 카테고리 체계를 별도로 운영할 수 있게 분리했습니다(마이그레이션 강제 X, 점진적 이동 가능).

    - UI 모드 2가지 제공

        - 계층 구조(밀러컬럼): 폴더를 따라가며 탐색하는 구조로, 파일 정보 조회(이름/형태/생성/수정/위치), 파일명 수정, 삭제, 이동, 업로드까지 한 흐름에서 처리할 수 있게 했습니다.

        - 테이블 구조: treeview UI로 전체 구조를 한눈에 보면서 이동/정리가 가능하도록 제공했습니다.

    - 컴포넌트 설계: 탐색 UI 특성상 상태가 커지기 쉬워서 “선택 경로(컬럼 스택)”와 “선택된 노드/파일 리스트”를 분리해 관리하는 방향으로 설계했습니다.

 

4) 밀러컬럼 공용 컴포넌트 구현 

 

- 핵심: 운영 데이터가 커졌을 때 클릭/선택/스크롤이 끊기지 않게 하는 쪽에 초점을 맞췄습니다.

- 개선 포인트(정리)

    - 상태 범위를 줄이기 위해 컬럼 단위로 변경 영향도를 제한하고,

    - 렌더링이 무거운 리스트는 메모이제이션과 key 설계를 조정해 불필요한 리렌더를 줄였습니다.

    - 빈 상태/로딩 상태/에러 상태를 명확히 분리해 “현재 무슨 일이 일어나고 있는지”가 UI에서 설명되게 만들었습니다.

 

5) OAAS1 → OAAS2 항공 및 견적요청 관리 페이지 기능 구현

 

- 구현한 핵심 기능 중 하나는 ‘통합 검색(Full-Text Search) 로직’이었습니다.

- 구현 포인트

    - 기존에는 특정 필드만 비교하는 방식이었다면, postId/title 같은 기본 정보 + 상세 JSON 전체를 문자열로 병합(JSON.stringify)해서 하나의 검색 타겟을 만들었습니다.

    - 코드값으로 저장되지만 화면엔 한글 라벨로 보이는 값(진행상태/견적유형 등)도, 사용자가 입력하는 검색어와 매칭될 수 있도록 라벨 매핑을 포함시켰습니다.

    - 최종적으로는 정규화(lowercase) 후 포함 여부로 필터링해, 비고/상세 일정 같은 “데이터 깊은 곳”도 누락 없이 찾을 수 있게 했습니다.

 

6) 고객센터 1:1 문의 ‘계층형 쓰레드’ 구조로 개편

 

- 목표: 기존에는 문의/답변이 평면적으로 보여 맥락 추적이 어렵고, 재문의가 쌓일수록 흐름이 끊기는 문제가 있었습니다.

- 구현 포인트

    - 루트 질문을 ‘최상위 댓글 헤더’처럼 렌더링해서 전체 UI 톤을 통일했습니다.

    - 질문 아래 답변들은 들여쓰기(pl) + left border로 물리적인 트리 구조가 화면에서 바로 인지되게 했습니다.

    - 루트 질문 하단에 ‘답변 달기’ 버튼을 둬서, 질문에 답변을 다는 경험도 댓글 작성 UX와 동일하게 맞췄습니다.

    - 파일 첨부도 대화 맥락에서 끊기지 않게, 질문 본문과 함께 묶어 보여주도록 정리했습니다.

 

7) 항공 견적 페이지 신청/관리 플로우 구현 및 고도화

 

- 핵심: 단순 CRUD 화면이 아니라, 역할(운영사 vs 판매점)에 따른 정책과, 운영자가 “많은 요청을 빠르게 처리”할 수 있는 UI가 동시에 필요했습니다.

- 구현 포인트

    - 거대한 폼 분리: 수백 줄의 견적 폼을 기능 단위 컴포넌트로 분리(BasicInfo/Airline/Schedule/Remark 등)해서 변경 비용을 줄였습니다.

    - RBAC(역할 기반 권한): 운영사만 댓글 작성/삭제가 가능하고, 판매점은 조회만 가능하도록 UI 레벨에서도 제약을 걸었습니다.

    - 비밀 댓글(노출 제어): 운영사가 작성한 댓글 중 파트너 노출 여부에 따라 판매점 화면에서 민감 정보가 보이지 않도록 필터링 로직을 구성했습니다.

    - 상태 동기화: 상세 Drawer에서 댓글을 작성/수정하면 리스트의 commentCount가 즉시 반영되게 해서, 사용자가 “저장됐나?”를 의심하지 않게 만들었습니다.

    - 전역 상태(zustand): 목록 로딩 시 메타 데이터(조회수/댓글수/리액션 등)를 파싱해 객체에 주입하는 전처리 로직을 확장해 데이터 처리 흐름을 정리했습니다.

    - 트러블슈팅: 기타 요청사항(remark)의 글자 수를 댓글 수로 잘못 표기하던 오류를 찾아 실제 commentCount 도메인 데이터를 사용하도록 수정했습니다.

 

8) 디자인 에디터 템플릿/페이지별 백업 및 버전 관리 

 

- 목표: 페이지/위젯 단위로 “현재 버전 저장 → 기록 확인 → 복구 → 서버 즉시 저장”이 안정적으로 이어지는 버전관리 워크플로우를 만드는 것이었습니다.

- 구현 포인트

    - 타입 안정성: any를 제거하고 인터페이스를 명확히 해서 런타임 오류 가능성을 줄였습니다.

    - KST 파일명: toISOString() 기반 파일명은 날짜가 어긋날 수 있어, 로컬 시간 기준으로 파일명을 만들도록 수정했습니다.

    - 경로 안전성: 다운로드/삭제 요청에 encodeURIComponent를 적용해 특수문자/슬래시가 있어도 안정적으로 동작하게 했습니다.

    - 백업 전략 이원화

        - 페이지 백업: 레이아웃/메타 포함 전체 JSON 백업

        - 위젯 백업: HTML/CSS/JS 등 핵심 설정만 추출해 순수 데이터로 백업

    - Re-wrap: 위젯 복구 시 백업된 순수 데이터를 현재 위젯 객체 구조로 다시 감싸 데이터 깨짐을 방지했습니다.

    - 저장 버튼 활성화: 버전 불러온 직후 저장 버튼이 비활성화되어 새로고침 시 데이터가 휘발되는 문제를, “복구 직후 즉시 저장 가능”하도록 UX/로직을 보완했습니다.

 

9) 판매점 전용 홈페이지 디자인 GNB 생성/관리 기능

 

- 목표: ‘저장 안 됨’ 같은 운영 이슈를 줄이고, 설정 토글/저장 흐름이 예측 가능하게 만드는 것이었습니다.

- 구현 포인트

    - 토글 버그 수정: 홈페이지 사용 O/X가 X로 바꿔도 다시 O로 돌아오는 문제는 상태/저장 로직이 두 단계로 분리되면서 레이스/덮어쓰기 위험이 있었고, 이를 단일 핸들러로 통합해 한 번의 흐름으로 끝나게 만들었습니다.

    - 서버 데이터 정규화: SITE.homepage가 "false" 같은 문자열로 올 때도 정확히 boolean으로 해석하도록 유틸(getHomepageUseFlag)을 추가했습니다.

    - GNB 저장 이슈 보완: 템플릿 없이 GnbManageForm을 재사용할 때 diff 기준이 없어 저장 흐름이 깨지는 케이스가 있어, compareGnb 옵션을 추가해 기준 데이터가 없을 때도 diff를 만들 수 있게 보완했습니다.

    - 검증: 린트 에러 없이 정리했고, 프로덕션 빌드까지 확인했습니다.