원래 24년 한 해를 정리하는 회고글을 한 번 작성하려고 했었는데, 다른 글들에 밀리다 결국 작성하지 못 했다.
취업 한파 치고 정말 다행히도 신입 프론트엔드 개발자가 된 기념으로, 처음 프론트 개발을 시작한 24년부터 한달차가 되가는 지금까지를 한 번 돌아보려고 한다.
데이터사이언스 / AI쪽 학부생이었던 나는 2-2 때까지는 그냥 수업만 열심히 들었고, 수업 내용에 흥미는 별로 없었다.
내 터닝 포인트는 2024년이다. 학교에서 친한 형이 종강하면 같이 앱을 하나 만들어보자는 제안을 해줬다. 그러나 당시 내가 알던 코딩은 C, Java, Python밖에 없었다. 일단 앱을 어떻게 만드는지도 몰랐다!
당시 팀플 빌런(...)으로 힘들어했던 형은 "검증된 팀원"을 구하는 앱을 만들자는 의견을 냈고, 나는 일단 OK했다. 그리고 Notion에 필요한 기능들을 정리한 뒤, 개발 계획을 세웠다.
그 후 뭘로 개발할지 결정해야 했는데, 형이 안드로이드와 IOS를 동시에 개발할 수 있다는 React Native(이게 뭔데?)를 제안했다. 언어는 JavaScript, DB는 Firebase를 쓰자고 했다.
민폐 끼치기 싫어하는 성격상 이틀정도를 개발 환경 설정에 시간을 갈아넣고, 프로젝트 생성 시 제공되는 템플릿을 열심히 해독해본 뒤, 약 3개월간 매일 학교에서 함께 개발했던 기억이 난다.
내가 담당했던 대표적인 부분은 게시물 리스트 뷰, 게시물 검색 기능, 레이아웃 수정, FCM(푸시 알림) 연결 등이 있었다.
당시 GPT-3 시절이고 RN 정보가 많이 부족해 LLM 도움을 거의 못 받았는데, 이 과정에서 공식 문서를 신뢰하는 좋은(?) 습관이 생겼던 것 같다.
JS와 React를 처음 사용하며 경험했던 재밌는 삽질들은 대표적으로
그냥 기본의 "기" 자도 모르고 박치기로 구현했지만, 이 과정에서 배운 것이 많았다.
또 당장 하고 싶은 것, 배우고 싶은 것들이 너무 많아졌다.
앱 개발 프로젝트를 진행하면서 처음으로 개발에 큰 흥미를 가지게 됐다.
학부의 오픈소스소프트웨어, 컴퓨터네트워크 수업을 열심히 들으며, 동시에 React 개인 프로젝트를 진행했다.

2-1 과정에 오픈소스소프트웨어라는 수업이 있었다.
오픈소스의 역사, 라이선스들에 대한 이해를 시작으로, HTML / CSS / JS, git / Github 사용법, 웹 개발 분야의 변화, Linux 명령어 및 간단한 운영체제 개념, 프록시 / 리버스프록시 개념 및 NginX로 정적 웹 배포, 백엔드의 개념과 역할 및 FastAPI, 컨테이너 가상화와 Docker까지를 한 학기에 맛보는 수업이다.
내가 배우고 싶은 모든 게 모여있는 웹개발 종합 패키지 같아 1순위로 수강신청을 시도했는데, 실패해서 OT때 교수님께 빌넣까지 해 겨우 듣게 되었다.
수업을 들으면서 평소 궁금했던 부분들이 많이 해소돼서 정말 좋았다. 예를 들면 서버가 대체 뭔지, 웹사이트 배포는 어떻게 이루어지는지(...) 궁금했던 이론과 개념들을 배우고 간단한 실습들도 진행하면서 부족했던 기초를 어느정도 다질 수 있었다.
수업 최종 과제가 자기 소개 페이지를 AWS EC2에 배포하는 것이었는데, NginX로 html을 서빙하고, Docker로 FastAPI를 띄워 방명록 기능을 연결했던 기억이 난다.
그리고 수업 때 CORS를 다루고 시험에도 나왔는데, 아래 프로젝트에서 호되게 당한 경험 덕분에 쉽게 이해하고 넘어갈 수 있었다.
React 학습을 위한 개인 프로젝트도 병행했다. 당시 좋아하던 게임 중 "원신" 이라는 (씹덕)게임이 있는데, 비공식적으로 게임 계정 데이터 검색 서비스를 제공하고 있는 Enka.network가 API를 오픈해둔 것을 Github에서 발견했다.
이 API를 활용해 UID를 입력하면 계정 내 캐릭터들의 정보를 보여주는 웹페이지를 만드는 것을 목표로 이번에도 일단 박치기를 시작했다.

React, Axios, TailwindCSS를 기반으로 다양한 시도들을 하며 재밌게 공부했다. 구현하고 싶은 기능을 찾아보며 직접 만들어보며 학습하고, 레이아웃을 반응형으로 구현하는 과정에서 CSS에도 익숙해질 수 있었다.
백엔드로 요청을 날리고 데이터를 받아와 가공하는 과정에서 ES6 문법에도 많이 익숙해질 수 있었고, client-server 모델에 대해서도 이해할 수 있었다.
client-server 모델은 클라이언트가 요청을 보내면, 서버가 그에 응답하는 구조를 의미한다.
이 때 CORS를 처음 경험했는데, 해결하는 데 3일이나 걸렸다. 왜 API 응답을 못 가져오는지 이해가 안되서 블로그를 많이 찾아봤는데, 설정을 백엔드가 해줘야 한다고 나와있어 관리자에게 디스코드로 직접 문의하기도 했다. (관리자가 등록해줘야만 API를 사용할 수 있는 줄 알았다.. ㅎ)
다행히 관리자님이 친절하게 FastAPI를 사용한 프록시 서버를 구축해서 사용해보라는 답변과 예시 코드까지 작성해주셔서, 오픈소스 수업보다 먼저 FastAPI를 사용해보게 됐다. 이 때 CORS가 존재하는 이유와 해결법을 이해할 수 있었다.
CORS는 브라우저 측 보안 정책이다. 브라우저에서 실행 중인 JS의 출처와 다른 출처에 HTTP 요청을 보내 응답을 받아오게 되면, 브라우저는 SOP(Same-Origin Policy)정책에 의해 이 응답을 차단하게 된다. 여기서 "백엔드는 응답을 잘 했다"는 것을 잊으면 안 된다.
만약 다른 출처에서 HTTP 응답을 받아 사용해야 하는 경우, 백엔드에서 Access-Control-Allow-Origin 헤더를 추가해 브라우저가 차단하지 않도록 하는 것이다. 이걸 CORS(Cross-Origin Resource Sharing) 정책이라고 부른다.
보통 CORS 문제나 CORS 이슈라고 부르다 보니 원인이 CORS 같지만, 사실 SOP가 원인이고 CORS 덕분에 다른 출처로 HTTP 요청이 가능한 것이다!

사실 2-1때 한 번 들었지만, 복습도 열심히 안 하고 어렵게 느껴져서 C+를 받았었다.
그러나 이번에는 네트워크 개념 전반에 대한 이해가 매우 중요하다는 걸 깨닫고, 진짜 이악물고 당일 복습 & 안 되면 될때까지 공부했다.
아쉽게도 딥한 내용들은 장기 기억이 되지 못 한 것 같지만, 네트워크 계층과 각 계층이 담당하는 것들, 그리고 간단한 동작 원리와 의미는 머리에 남은 것 같다.
신기하게도 첫 수강 때는 시험 문제 4~5개중 1~2개 겨우 맞췄는데, 재수강 때는 중간 기말 다 맞췄다..
학습 효율에 Why가 정말 중요하다는 것을 알 수 있었다.왜 필요한지 깨닫고 공부하면 학습 효율이 좋은 것 같다..!
![]() | ![]() |
|---|
1학기 종강 후, 오픈소스 수업 때 만든 자기소개 페이지와 React 학습용 개인 프로젝트 Genshin.gg를 직접 배포해보고 싶었다. 그런데 AWS는 과금 위험 때문에 무서웠고, Netlify같은 서비스는 배포 과정을 내가 알 수 없다는 것이 싫었다.
그래서 홈 서버에 눈을 돌렸다. 10년정도 된 노트북을 가지고 새양님의 블로그를 참고하며 하나 하나 설정하기 시작했다.
네트워크 수업 때 배운 것처럼, 공유기의 NAT(Network Address Translation)으로 인해 외부에서 내부로 접근할 수 없다. 이를 해결하기 위해 공유기 설정에서 포트포워딩과 고정 IP 할당을 진행한 뒤, 우분투 방화벽을 열고, 오픈소스 개인 프로젝트를 배포할 때처럼 NginX와 Docker를 사용해 IP 기반 HTTP 배포를 진행했다.
그리고 같이 앱을 만들었던 형한테 자랑했다.
"제가 만든 웹페이지는 localhost가 아니라서 형도 접속할 수 있어요."
"오 근데 주소가 좀 그렇네, 도메인 같은거 적용하는게 좋을듯. HTTPS도 적용하고."
그렇다. 내가 배포한 형태는 http://xxx.xxx.xx.xx:xxxx과 같은 형태였고, 브라우저는 위험한 웹사이트라며 경고를 뿜어댔다(...)
이를 개선하기 위해 FreeDNS를 사용해 도메인을 만들고, Certbot을 활용해 HTTPS 설정을 했다.
그랬더니 이번엔 CORS가 아닌 Mixed Content 에러가 발생했다. 프론트가 HTTPS인데, 백엔드가 HTTP라서 secure를 보장할 수 없는 문제였다.
HTTPS는 간단히 말해 HTTP에 TLS(Transport Layer Security)를 더한 구조로, HTTP로 데이터를 전송하기 전에 암호화를 진행해 중간에서 가로채도 읽을 수 없도록 해준다.
TLS는 OSI 7계층 기준으로 Application과 Transport 계층 사이에서 실행된다.
이를 해결하기 위해 기존에는 Docker로 띄워 바로 접근하던 백엔드 역시 NginX를 거쳐 리버스 프록시 되도록 한 뒤 도메인과 HTTPS를 설정해줬다. 이후부터는 서버가 필요해지면, 홈 서버를 활용할 수 있게 되었다.
직접 홈 서버를 구축해보면 배울 수 있는 것이 많은 것 같다.
7~9월 선배들의 제안 덕분에 총 두 번의 해커톤을 경험할 수 있었다.
선배 중 한 명이 새싹 해커톤 참여를 제안해주셨다. 총 3명이 함께했지만, 어쩌다보니 개발 파트는 혼자 진행하게 되어 자연스럽게 풀스택 형태로 진행하게 되었다.
우리는 시각 장애인 분들을 위한 접근성 향상 앱을 만들기로 결정하고, 예선에서 프로토타입을 만든 뒤 본선에 참여하게 되었다.
사진 촬영이나 캡쳐 시 Base64로 변환하고 백엔드로 요청을 날리면, GPT-4o에게 프롬프트와 이미지를 함께 주입해 안내 메시지를 생성하고 앱 쪽으로 응답한다. 그러면 이 텍스트를 TTS로 읽어주고, 원하는 경우 STT를 활용해 이미지에 대한 추가적인 질문을 할 수 있도록 계획했다.
RN과 FastAPI를 활용해 GPT API 호출은 FastAPI에서 하고, 앱에서는 STT & TTS 처리를 진행하는 간단한 흐름이었다.
팀원들이 구현한 python 기반 코드들을 쉽게 통합하기 위해 FastAPI를 채택했다.
그런데 마감 시간이 다가오면서 여러 버그와 함께 FastAPI의 응답이 간헐적으로 앱에 반영되지 않는 문제가 발생해, 조급한 마음에 땜빵식으로 구현했던 기억이 난다(...)
결국 심사 때 앱이 제대로 작동하지 않아 어버버거리고, 본선에서 광탈한 뒤 우울하게 집으로 돌아오는 길에 구글링하다 해결책을 찾게 되었다.
Bottom tab navigator 사용 시, 한 번 마운트된 페이지는 나가도 언마운트되지 않는다.
나갔다 들어올 때마다 마운트된것처럼 작동해야 한다면 useFocusEffect를 사용해야 한다.
당시 useEffect의 의존성 배열과 실행 타이밍 문제를 계속 의심했으나 해결하지 못했는데, 해커톤이 끝난 뒤에야 해결책을 찾게 된 것이다... 집에 도착하자마자 고쳐봤더니 바로 해결되었다 ㅠㅠ
그래도 1박 2일동안 밤을 새며 코딩에 몰입하는 경험과, 오프라인에서 모든 사람들이 노트북만 보면서 초췌해진 몰골로 개발하는 환경은 뭐랄까.. 정말 설렜던 것 같다.

새싹 해커톤에 참여했던 팀원 중 한 명이 이번에는 NASA 해커톤을 제안해주셨다.
그런데 정보와 문서들이 전부 영어고, 다양한 주제 중 하나를 선택해야 했는데 솔직히 조금 겁이 났다. 팀원들이 모두 3D를 원했고, 기대치가 높았기 때문이다.
이번 해커톤은 시작 전까지 사전 구현이 금지되어 있었기 때문에, 작년 공개 데이터를 활용해 미리 Three.js와 R3F(React-Three-Fiber) 연습을 한 달간 진행했다.
동시에 팀원들은 배경 지식(데이터를 기반으로 좌표, 궤도, 밝기 등을 계산하는 법 등..)들을 정리해 개발하기 쉽도록 준비해줬고, 해커톤 당일 숙소를 잡아 진행했다. (이 해커톤은 온라인으로 진행되었으나, 팀원들끼리는 만나서 진행했다)
![]() | ![]() |
|---|
실제 데이터는 팀원들이 정리해준 배경 지식으로 이해가 가능했으나, 문제는 데이터 수가 매우 많았다. 모든 외계 행성 csv 데이터를 json으로 변환해 좌표로 변환하는 유틸 함수에 집어넣은 다음 렌더링하니 3~7프레임밖에 나오지 않았다(...)
이를 해결하기 위해 useMemo, useCallback, memo와 같은 메모이제이션 함수를 적극적으로 사용했다. 변하지 않는 3d 오브젝트 컴포넌트나, 초기 계산 이후 변하지 않는 좌표 데이터 등을 적극적으로 메모이제이션 한 결과 18~42프레임으로 개선할 수 있었다.
비록 수상하지는 못 했지만, 지난 새싹 해커톤과 달리 팀원들과 얘기했던 핵심 기능을 전부 구현하는데 성공했다. 그리고 투박한 디자인이지만 여러 가지 기능을 구현하고, 데이터 파싱을 프론트에서 진행하며 많은 걸 배울 수 있었다. 무엇보다 웹에서 3D를 활용하는 방법을 어느정도 배울 수 있어 좋았다.
해커톤이 끝나면 심장이 뛰고 해냈다는 기분이 강하게 드는데, 이게 너무 좋았다.
밤새가며 열심히 만들던 것이 마무리되며 느껴지는 쾌감..?
2학기 때는 21학점과 동시에 프로젝트도 많았어서 A+을 많이 받지 못 했다...
(복습할 시간이 많이 없었다는 핑계 ㅎㅎ...)
정보 디자인 이론과 함께 JS를 좀 더 깊게 배우고, React를 얕고 넓게 배운 뒤 팀 프로젝트를 진행하는 수업이었다.
당시 모던 자바스크립트 튜토리얼 문서를 자주 읽어보던 시기인데, 아무리 읽어도 클로저가 잘 이해되지 않아 교수님께 꼬리 질문을 수없이 드렸던 기억이 난다.
클로저를 간단하게 정리하면, 상위 렉시컬 스코프를 참조하는 함수라고 할 수 있다. 함수를 반환하는 함수가 있을 때, 자식 함수가 부모 함수의 렉시컬 스코프를 참조한다면, 자식 함수는 클로저다.
예를 들자면 하나의 useState 함수를 통해 여러 state와 setState를 반환받아 사용하지만, 각 state 및 setState 간에는 영향을 주지 않고 캡슐화가 되어 있는 것을 떠올릴 수 있다.
아쉽게도 해당 수업에서 React는 깊이 배우지 않았지만, JS에 대해 최대한 많이 알아가려고 노력했다.
하지만 역시 수업 하나로 JS의 모든 것을 깨달을 수는 없었다. 오히려 내가 모르는 것들이 훨씬 많다는 사실을 깨닫고 더 열심히 공부하는 계기가 되었던 것 같다.
정보디자인프로그래밍이 프론트엔드 수업이었다면, 웹서비스설계및실습은 백엔드와 배포 관련 수업에 가까웠다. 대략적으로 Express, JWT, WebSocket, WebRTC 등을 다룬다.
미리 올라온 녹화강의를 듣고, 매주 1회 실습을 하며 과제와 팀플을 수행하는 수업인데, 교수님께서 과제 진행 시 프론트는 자유롭게 구현해도 괜찮다고 하셨다.
그래서 가장 자신있던 Vite + React + TS 템플릿으로 과제를 진행하기 시작했다.
1차 과제는 토큰/세션 없이 반쪽짜리 회원가입과 로그인을 구현하는 것이었다. 어렵지 않아 다른 과목 개념 공부하다 지칠 때 후딱 구현해버렸다 ㅎㅎ
2차 과제는 JWT와 MongoDB를 사용해서 제대로 된 회원가입과 로그인을 구현하는 것이었다. 역시 어렵지 않아 시험 공부 하기 싫을 때 후다닥 구현했다 ㅋㅋ
그런데 3차 과제가 너무 어려웠다. WebSocket과 WebRTC를 활용해 최대 3인까지 접속 가능한 화상 채팅방을 구현해야 했는데, 카메라/마이크 각각 온오프/변경이 가능해야 했다.
교수님께서는 HTML + JS 기반으로 WebRTC를 강의해주셨는데, React 라이프사이클 내에서 이를 구현하는게 꽤 어려웠다. (시그널링 순서와 관련한 에러가 많이 발생했다)
3차 과제와 딱 맞는 레퍼런스도 없어서 시간 날 때마다 계속 고민하고 적용해보기를 3주 가량 반복하다가, 제출 기한을 3일정도 남기고 겨우 완성해냈다..!
이 때 구현에 쫓기며 제대로 이해하지 못하고 작성한 코드가 많아 아쉬웠어서, 종강 후 해당 내용을 처음부터 다시 정리하며 회고를 진행했다. WebRTC 시리즈
과제 외에도 팀 프로젝트가 존재했는데, 선배 한 명이 나를 간택해줬다. 3인 팀에서 나는 프론트엔드를 담당하고, 선배 두 명은 백엔드를 담당했다.
함께 선정한 주제가 신박하고 재미있었다. 지렁이 키우기 같은 게임인데, 랜덤한 위치에 방해 요소가 나타나고, 충돌하면 패널티가 부과되는 환경에서 여러 플레이어가 모여 실시간 쓰레기 수집 순위를 경쟁하는 게임이다. 그리고 쓰레기를 수집해서 상점으로 이동하면, 돈으로 환전해서 아이템을 사거나 팔 수도 있다.

가장 먼저 게임 화면을 어떻게 구현할지 고민했다. 다른 팀의 경우 div와 position을 활용해 구현하기도 했는데, 나는 HTML5의 canvas를 활용해보고 싶었다.
테스트를 위해 canvas에서 사각형을 방향키로 이동시키며 빨간 사각형과의 충돌 처리를 구현해봤는데, 뭔가 어색했다. React를 사용하고 있음에도 게임 화면 코드는 대부분 명령형인 것을 느끼고, 선언적으로 canvas를 제어할 수 있는 라이브러리를 찾아보았다.
라이브러리를 찾아보던 중 Pixi.js와 pixi-react를 발견했다. Pixi.js는 fastest, most flexible 2D WebGL renderer 라고 소개되고 있으며, 선언적으로 사용할 수 있도록 래핑된 pixi-react도 존재해 사용하기 좋을 것 같았다. 공식 문서가 친절한 점도 좋았다.
상태 관리는 Zustand를 활용했다. 게임 화면과 React UI, 소켓 데이터 동기화를 위해 전역 상태 관리가 필요했는데, Jotai와 같은 Atomic 방식은 자칫 상태 변화 흐름 추적이 어려워질 수 있다는 것을 NASA 해커톤 때 몸소 느꼈다. 따라서 상태와 상태 변화 로직이 한 곳에 모여있는 Flux 방식의 Zustand를 선택했다.
그동안 계속 고민하던 디렉토리 구조도 나름 개선에 성공했다. components나 hooks 등의 폴더 내부에 feature 개념을 두는 식으로 구조화를 해보니 수정할 코드를 찾는 것이 이전보다 쉬워져 개발과 관리가 수월했다.
다만, 실 배포 후 약간의 문제가 발생했다. 쓰레기, 방해요소, 아이템 등을 생성하는 백엔드 로직을 setInterval로 구현했는데, 여러 사용자가 계속해서 소켓 이벤트를 emit하자 setInterval이 제 때 실행되지 않다가 한 번에 실행되는 것이다.
이는 이벤트루프의 마이크로태스크 큐가 비지 않아, 태스크 큐에 setInterval의 콜백이 실행되지 못하고 쌓여 발생한 문제였다.
하지만, 시간상 근본적으로 해결하기는 어려웠어서 결국 교수님이 제공해주신 라즈베리파이 서버 대신 내 홈 서버에 백엔드를 띄우는 방식으로 우회해 해결했다(...)
지금 생각해보면 프론트에서 소켓 이벤트를 emit할 때, 약간의 throttle을 걸어줬다면 훨씬 좋았을 것 같다.
1학기때 친한 선배들의 캡스톤 프로젝트에 프론트엔드 개발자로 참여해 개발하고 있었는데, 종강 이후 진행되지 않고 있었다. 그렇게 졸업 작품 전시회가 1달 남게 되었다.
급하게 회의를 한 뒤 코드를 살펴보았는데, 다시 만난 6개월 전 코드는 정말 처참했다(...)
일단 html을 너무 대충 짰고, Tailwind CSS 역시 순서 없이 마구잡이로 작성되어 있어 읽기 힘들었다.
또 컴포넌트 분리가 제대로 되어있지 않아 각 컴포넌트가 지나치게 비대했다. 컴포넌트를 적절히 나누는 것이 유지 보수 측면에서 얼마나 중요한지 직접 느낄 수 있었다.
비즈니스 로직 역시 내부에서 여러 조건문으로 분기된 형태라 가독성과 이해도가 많이 떨어졌다. 조건문이 코드 깊숙이 숨겨져 있으면 코드 가독성이 많이 떨어진다는 것을 깨달을 수 있었다.
결국 회의 때 나온 내용을 반영하기 전, 전체 리팩토링이라는 큰 결심을 하게 되었다.
기억상 오후 10시부터 다음날 새벽 5시까지 논스톱으로 진행한 것 같다. (어캐했지?)

그 다음, 핵심 요구사항 중 하나였던 "책 넘기는 애니메이션"을 구현하기 위한 삽질을 시작했다. react-pageflip이라는 라이브러리를 사용했는데, 우리 환경에 맞게 사용하기 위해 여러 번 몸을 비틀어 구현하는데 성공했다.
지금 생각해보니, 이 때 라이브러리를 사용하지 않고 직접 만들어봤다면 더 값진 경험이 되었을 것 같다.
이 때 이후로 개발 블로그를 시작했다. 내 개발 경험을 정리해두고 싶었기 때문이다. pageflip 시리즈
블로그의 시작이 개발 회고다 보니, 이후 작성한 포스트들도 주로 개발하면서 있었던 일을 다시 돌아보며 추가 학습을 하는 느낌인 것 같다. ㅎㅎ
사실 해당 프로젝트의 백엔드가 목업이라 별 기대를 하지 않았는데, 장려상(!)을 수상해서 선배들과 고기를 먹었다.
(아쉽게도 깍두기 포지션이라 상장 같은 건 받지 못했다.. ㅠㅠ)

친한 형이 교내 모바일프로그래밍 수업에서 진행하던 "멜리사"라는 팀 프로젝트가 있었다.
호기심이 생겨 프로젝트에 대해 물어봤더니, GPT와의 대화를 바탕으로 자동으로 일기를 작성해주는 앱이라고 설명해줬다. 그리고 이 프로젝트를 종강 후 고도화시킬 계획이라고도 했다.
주제가 너무 흥미로워 앱 개발 파트로 참여하고 싶다고 말씀드렸고, 나는 25년 1월부터 본격적으로 프로젝트에 합류하게 되었다.
지금까지는 혼자 개발하거나 백엔드 개발자와만 프로젝트를 진행했는데, 멜리사 프로젝트를 통해 PM, 마케팅, 디자인, AI 등 다양한 분야의 팀원들과 협업을 경험할 수 있었다.
매주 화요일 저녁에 디스코드로 회의를 진행하며 진행상황을 공유하고, 의견을 주고받는 것이 되게 재미있었다.
기반을 튼튼하게 하기 위해 어떤 코어 라이브러리를 사용할지 고민했다.
react-native-cli를 사용한 bare 환경과, Expo 프레임워크를 사용하는 환경을 두고 고민했다.
예전 Expo는 네이티브 모듈과 일부 라이브러리 사용에 제약이 있었고, 이를 우회하려면 eject가 필요했기 때문이다.
하지만 최근에는 이런 제약들이 거의 사라졌고, React Native 공식 문서에서도 Expo 사용을 적극 권장하고 있어, 최종적으로 Expo를 선택하게 되었다.
패키지 매니저의 경우 pnpm을 사용했는데, 지금 생각해보면 pnpm을 사용한 명확한 이유는 없었던 것 같다.
(남들 쓰니까 한 번 써보고 싶었던 것 같다...)
굳이 pnpm을 사용해서 편했던 점은 아래 두 가지 정도다. (그냥 npm 썼어도 충분했다)
Zustand 같은 전역 상태 관리 라이브러리 대신, 서버 상태(비동기 상태) 관리 라이브러리인 TanStack-Query를 사용하기로 결정했다.
TanStack-Query 사용 계기는 예전 "바다 이야기" 프로젝트의 상점 비즈니스 로직을 구현하며 느낀 불편함과 문제점 때문이다. 서버 상태를 Zustand로 관리하면 로딩과 에러 등을 직접 관리해야 하므로 개발자 경험(DX)이 좋지 않다. 또 구매, 판매, 환전 API 호출 시마다 화면이 깜빡였고, 상점을 열 때마다 중복 요청이 발생하는 문제가 있었다.
TanStack-Query를 사용하면, in-flight deduplication(query-key 기반 Promise 공유)을 통해 중복된 query 요청을 하나로 합쳐 처리하고, 데이터가 업데이트되기 전까지 기존 데이터를 보여줄 수 있으며, 낙관적 업데이트를 활용해 로딩 없이 즉각적인 UI 반영도 가능하다.
추가로, staleTime을 조절하면 불필요한 네트워크 요청을 줄일 수도 있다. 이를 활용해 자주 변경되지 않는 사용자 설정 데이터나 일기 데이터는 staleTime을 길게 설정해 캐싱 효과를 극대화하고, 데이터 변화를 일으키는 mutation의 onSuccess 시점에 invalidateQueries를 호출해 관련 데이터를 최신 상태로 유지할 수 있도록 설계했다.
개발 속도가 빠른 Tailwind의 RN 버전인 Nativewind를 사용할지, 유지 보수 및 가독성이 뛰어난 styled-components를 사용할지 고민했다.
하지만 팀에 디자이너가 있어 css 구상을 먼저 하고 개발할 수 있었기 때문에, Nativewind를 사용하지 않아도 충분히 빠르게 개발할 수 있다고 판단했다.
그래서 유지 보수 및 가독성이 뛰어난 styled-components를 선택했다.
앞으로도 앱을 지속적으로 배포할 계획이었기 때문에, 빌드와 스토어 제출을 매번 수동으로 하는 건 비효율적이라고 생각했다.
그래서 이를 자동화하는 방법에 대해 조사하던 중, Expo에서 제공하는 EAS Build/Submit에 대해 알게 되었다.
적용 과정도 어렵지 않았기 때문에 main 브랜치로 PR이 열리면 build & submit을 수행하도록 트리거를 걸어 간단히 배포 자동화를 처리할 수 있었다.
다만, RN 특성상 잔버그가 많아 자주 빌드가 실패하고 있다... 😭
첫 개발을 React-Native로 해서 그런지 나는 RN을 많이 좋아하는 편이다. (잔버그가 많긴 하지만...)
앱을 빌드할 때 수많은 네이티브 모듈이 컴파일되는 과정을 보고 있으면, React 하나로 서로 다른 OS의 앱을 만들 수 있도록 해주는 React-Native가 경이롭게 느껴지기까지 한다.
하지만 React에 비해 React-Native와 관련된 정보는 생각보다 찾기 어렵다.
프로젝트를 진행하며 실질적으로 문제를 해결하는데 도움을 받은 경로는 대부분 라이브러리 및 Expo의 github issue였다. 블로그 포스트나 stack-overflow에는 도움 되는 정보가 생각보다 많지 않다.
최근 XCode 버전 업데이트(비표준 C++ 기능을 제거)로 발생한 IOS 빌드 실패 이슈 역시 Expo 및 Sentry의 github issue를 통해 인지하고 해결할 수 있었다. (참고)
그래서 나는 멜리사를 개발하면서 겪은 이슈나 구현 과정을 블로그에 정리하고 있다. 혹시라도 나와 비슷한 상황이라면, 삽질을 덜 할 수 있게 돕고 싶기 때문이다.
현재 MVP 구현 및 IOS 배포를 마쳤고, AOS 배포를 위한 CBT(비공개 테스트)를 진행하고 있다.
동시에 몇몇 버그와 사용성이 떨어지는 부분들을 개선하는 작업을 진행 중이다.
최근에는 일기 팝업이 열린 채로 뒤로가기를 누르면 앱이 종료되는 문제와, 채팅 SSE에서 띄어쓰기가 사라지는 문제 등을 해결했다.
그 외에도 나는 산업기능요원 신분상 개발 외에는 참여하지 않고 있지만, 팀원들은 교내 창업동아리나 지원 사업 등을 적극적으로 신청하며 장기적으로 진행하는 프로젝트로 키워나가려고 하고 있는 상황이다.
소규모 인원이서 진행하는 단발성 프로젝트가 아닌, 다양한 분야의 팀원들과 진행하는 장기적인 프로젝트를 통해 협업에 더욱 익숙해질 수 있었다.
정보처리 관련 학과를 재학 중이고 특정 조건을 만족하면, IT 회사에서 일을 하면서 병역도 해결할 수 있는 산업기능요원 제도가 있다.
나는 신체 검사 4급을 받아, 원래 공익 근무를 하면서 공부와 프로젝트를 병행할 생각이었다. 그러던 중 산업기능요원 제도를 알게 되었고, 프론트엔드 실무를 경험하면서 병역까지 해결할 수 있다면 정말 좋겠다고 생각했다. 공부를 아무리 열심히 해도 직접 실무 경험을 쌓는 것과는 비교가 되지 않는다고 생각했고, 무엇보다 실무 경험을 조금이라도 더 빨리 시작하고 싶었다.
그래서 정보처리 분야 산업기능요원으로 편입하기 위한 자격 요건과 채용 방식 등을 조사했고, 3학년 2학기 종강 후 이력서와 포트폴리오를 준비하기 시작했다.
하지만 어떻게 준비해야 할지 잘 모르겠어서 우선 Notion을 활용해 내가 해온 프로젝트들과 잘할 수 있는 것들을 최대한 작성했다. (이력서 쓰는게 정말 어려웠다... 😢)
이력서와 포트폴리오가 어느정도 완성된 뒤, 보충역 신규 편입이 가능한 회사를 원티드와 사람인에서 조사하며 지원 요건에 맞는 회사는 전부 지원했다.
하지만 대부분의 회사에서 서류 탈락했다. 사실 요즘 취업 한파에 비하면 내가 지원한 회사 수는 적은 편이지만, 계속해서 탈락하니 자신감이 조금씩 떨어졌다(...)

멜리사 프로젝트 팀장 형에게 요즘 계속 서류 탈락을 하고 있다는 얘기를 드렸더니, 형이 이력서를 한 번 봐주시겠다고 했다. 처음으로 지인에게 이력서를 보여주는 거라 뭔가 부끄러웠다(...)
피드백을 받아보니 내 이력서는 너무 뻔하고 당연한 이야기 투성이었다. "학습과 개발을 즐기는 개발자"라던가, 25줄이 넘어가는 introduction, 할 줄 아는 건 다 나열한 기술 스택, 프로젝트 파트에서 드러나지 않는 배운 점과 문제 해결 과정 등... 고칠 점 투성이인 이력서였다.
형은 "1년동안 9개의 프로젝트를 성공시킨 개발자"와 같이 자신을 입체적으로 표현해보라고 조언해줬다. 또 캡스톤 장려상과 성능 개선 사례를 강조하고, 포트폴리오에 이미지나 gif도 함께 넣어보라고 말해줬다.
새벽에 삘 받아 밤새 수정해서 다시 탄생한 이력서는 기존보다 훨씬 맘에 들었다.
이력서와 포트폴리오를 개선하던 도중, 한 회사에서 서류 전형을 합격해 코딩 테스트를 진행하게 되었다. 그런데 당시 왜 그랬는지는 모르겠지만, 평소 실력으로 보겠다는 마음으로 따로 코테를 준비하지는 않았다(...)
다행히도 알고리즘보다는 구현 위주의 문제가 나왔고, 5문제중 3.5솔 할 수 있었다. (한 문제도 접근은 맞았는데, 잘못된 변수를 콘솔로 찍으며 헛돌고 있었다.. 😢)
사실 코딩 테스트를 보고 나서 망했다고 생각했다. 그래서 경험을 쌓았다고 생각하고 다른 회사를 지원하려고 했는데... 산업기능요원 프론트 공고가 많지 않았다.
서류 난사도 하려면 공고가 있어야 하는데, 지원할 수 있는 공고가 거의 없어 조급해지기 시작했다.
"아.. 나 이렇게 1년 날리고 공익으로 가야 되는건가...?"
정말 다행히 코딩 테스트 결과는 합격이었다... 🙏
다음 전형은 면접이었다.
나는 면접을 준비하면서 별도의 스터디나 정리는 하지 않았다. 암기한 느낌이 나거나 딱딱하게 답변하기 싫었기 때문이다.
대신 4개월 전부터 하루에 한 번씩 메일로 면접 질문을 받는 서비스를 활용해 꾸준히 공부를 해왔고, 그 덕에 기본적인 기술 질문에는 어느정도 자신 있었다.
그래서 자기소개와 지원동기만 준비하고 실수하지 않도록 여러 번 읽으며 흐름을 익힌 뒤, 면접을 봤다. 처음에는 분위기도 좋았고, 나도 잘 대답했다.
그런데 점점 어려운 기술 질문들이 쏟아지기 시작했다... 😢
아래는 어려웠던 기술 질문들을 복습하기 위해 기억을 되짚어가며 정리했던 내용이다.
나는 React를 쓰면서 Vue/Angular와의 차별점도 몰랐으며, 선언적으로 코딩하고 있었지만 개념을 몰랐고, 항상 써오던 JS의 이벤트 루프와 React의 diffing에 대해서도 겉핥기 정도로만 알고 있었다.
즉, 그동안 내가 사용하고 있는 기술들이 어떻게 문제를 해결했는지에 대해 고민하지 않았던 것이다.
면접관님께 너무 부끄러웠고, 아직 한 없이 부족하다는 것을 느꼈던 것 같다.
정말 고통스러운 면접이었지만, 면접 질문들을 통해 부족한 점을 직접 인지하고 개선할 수 있어 너무 좋았다.
면접이라는 값진 경험을 놓치고 싶지 않아 노션에 면접 질문과 관련된 내용을 정리하며 공부하긴 했지만, 사실상 떨어졌다고 생각했다. 기술 질문의 절반은 제대로 대답하지 못 했기 때문이다.
그런데 면접 다음주에 합격 연락이 왔다(!!) 너무 신나 목소리도 막 떨렸던 것 같다...
이렇게 정말 다행히 산업기능요원으로 대체복무를 할 수 있게 되었다. (물론 수습 기간을 잘 보내야 한다... 🔥)
정말 운이 좋게 합격할 수 있었던 것 같다.
이제 이 기회를 바탕으로 폭발적으로 성장하는 것이 목표다.
한 달이 정말 눈 깜짝할 사이에 지나간 것 같다. 초반에는 긴장감 때문에 시간이 빨리 갔고, 최근에는 업무가 생기면서 시간이 빨리 가고 있다! 😃
입사 첫 날에는 개발 환경 설정이 메인이었다. 개인 노트북과 최대한 유사한 환경을 구축하고, 사내 Github와 Slack 등에 가입한 뒤 레포지토리들을 클론해 로컬에서 실행시키는 작업을 했다.
첫 날 점심은 CTO님과 함께 먹었다. 앞으로 어떤 일을 하게 될지, Slack으로 소통하는 것이 왜 중요한지, 뛰어난 개발자는 어떤 개발자인지와 같은 이야기를 해주셨다.
이후 2주 동안은 사내 코드와 협업 문화에 적응하는 시간이었다. 사내 코드를 보며 구조와 패턴을 학습하고, Slack을 활용해 의사소통하는 것에 적응하고, 스크럼에 참여하고, 동료분들과 천천히 친해지는 시간을 가졌다.
이 때 가장 힘들었던 것은 내가 아직 팀에 도움이 되지 못한다는 것이었다. 나만 빼고 모두가 바쁜 와중에 사수님께 질문만 하는 느낌이라 약간 죄송하기도 했다.
그래도 동료분들이 잘 챙겨주셔서 차근차근 적응해나가며 작은 업무들을 맡기 시작했다.
처음 진행한 업무들은 리팩토링, 버그 수정, 사소한 기획 수정 반영 등이었다.
리팩토링 작업과 함께 디렉토리 구조 개선 작업도 진행했는데, 이 때 좋은 디렉토리 구조에 대해 고민하고 리뷰받으며 공부를 많이 할 수 있었다.
처음 PR을 올렸을 때 사수님이 축하한다고 해주셨던 것이 기억에 남는다. 비록 사소한 변경 사항이었지만, 상용 서비스 코드에 손을 댔다는 느낌이 짜릿했다.
나보다 뛰어난 프론트엔드 동료분들께 배울 수 있어서 너무 좋다.
그동안 프론트를 혼자 담당했다보니, 누군가에게 물어보거나 평가받지 못해 좋은 코드에 대한 확신을 갖기 어려웠다.
하지만 입사 후 사내 코드를 살펴봄과 동시에 코드 리뷰를 받으면서 다양한 React 디자인 패턴과 네이밍 규칙, 디렉토리 구조 등을 익힐 수 있었다.
동료분들의 의견을 듣고, 내 생각도 말하면서 배우는 것들이 많아 너무 좋다.
그동안 내 컴포넌트 단위가 큰 편이었다는 사실도 깨달을 수 있었다(...)
대형 사고를 한 번 치기도 했다... 😭
레거시 코드가 많은 페이지의 url을 개선하는 작업을 배포한 뒤, 서비스 장애가 발생한 것이다. 문제가 발견되자마자 고쳐서 핫픽스를 나갔지만, 토이 프로젝트들과 차원이 다른 무게감을 느낄 수 있었다...
핫픽스를 마친 뒤 죄송한 마음에 한동안 풀 죽어 있었는데, 사수님이 예전 스레드를 공유해주시면서 "이런 일도 있었다"며 너무 자책하지 말라고 위로해주셨다. CTO님도 "조금 더 신경 써주세요"라고만 말씀하시고, 탓하거나 화내지 않으셨다.
이후 항상 더블 체크를 하고, 단순 리팩토링 작업 외에는 테스트를 요청해 비슷한 실수를 하지 않도록 조심하고 있다.
2주 전, 신규 기능 기획이 나와 개발 준비 작업을 진행하고 있다.
글을 쓰는 환경을 구현해야 해서 TinyMCE나 ProseMirror 등의 WYSIWYG 라이브러리를 비교/테스트하고, 동료분들과 상의 끝에 Tiptap을 사용하기로 결정한 뒤, 필요한 기능들을 연결하거나 구현했다.
Tiptap은 Headless라서 UI 커스텀 자유도가 뛰어나며, 기저에 존재하는 ProseMirror API에 접근해 완전한 커스텀 기능 추가도 가능하다는 장점이 있다.
그 외에도 레거시 코드들의 문제점을 개선하기 위해, 전체적으로 사용할 공용 모듈/컴포넌트들을 리뷰받으며 직접 구현하고 있다. 라이브러리를 사용하지 않고 만드는 과정에서 많이 배우고 있다.
useModal()toast()신규 기능 배포까지 파이팅해보자!! 💪
2025년의 가장 큰 목표는 프론트엔드 개발자로서 기술적 성장을 이루는 것이다.
우선 회사에 잘 적응하며 다양한 실무 경험을 통해 빠르게 성장하고, 현재 진행중인 멜리사 프로젝트를 계속 이어가는 것을 최우선 과제로 삼고 있다.
이외에도 Next.js 딥다이브, TDD 학습, 개인 프로젝트 진행을 목표로 설정했다.

나는 지금까지 Vite 기반의 CSR(Client-side rendering)만 사용해왔고, Next.js App router를 잠깐 사용해본 경험은 있지만 'use client'에 대한 오해 때문에 잘 사용하지 않았다.
Next.js App router를 처음 사용하면, 'use client'라는 지시문과 서버/클라이언트 컴포넌트라는 이름 때문에 클라이언트 컴포넌트는 SSR이 되지 않는다고 오해하기 쉬운 것 같다.
이 부분이 헷갈린다면, 아래 아티클을 한 번 살펴보는 것도 괜찮을 것 같다 👀
Making Sense of React Server Components
Confusion about "use client" and SSR in Next.js
사내 프론트엔드 프레임워크가 Next.js이기 때문에, 공식문서/아티클 등을 통해 공부해보았다. 이 과정에서 서버 컴포넌트와 클라이언트 컴포넌트 모두 서버에서 사전 렌더링 된다는 것을 깨달았다.
서버 컴포넌트는 서버에서만 렌더링되고 클라이언트로는 HTML만 전달되기 때문에 이벤트 핸들러나 hooks 등을 사용할 수 없는 대신, 서버 자원에 직접 접근해 데이터를 가져올 수 있다.
// 서버 컴포넌트에서는 async-await이 가능하다.
export default async function ServerComp() {
const data = await axios.get('...');
return (
<div>
{data.result.map((e, i) => (
<p key={i}>{e}</p>
))}
</div>
)
}
서버 컴포넌트는 데이터를 직접 fetch해온 뒤, 완성된 HTML을 클라이언트로 전달할 수 있다. JS 번들이 포함되지 않기 때문에 클라이언트 번들 사이즈에 영향을 주지 않는다는 장점이 있다.
반면 클라이언트 컴포넌트는 서버에서 사전 렌더링된 후, 클라이언트에서 Hydration되기 때문에 이벤트 핸들러나 hooks 등을 사용할 수 있다. (Next.js Page router의 SSR 방식과 유사)
// 클라이언트 컴포넌트는 사실 우리가 잘 아는 기존 컴포넌트를 의미한다.
'use client'
export default function ClientComp() {
const [state, setState] = useState<string>('hello');
const handleClick = () => setState('world');
return (
<div>
<p>{state}</p>
<button onClick={handleClick}>버튼</button>
</div>
)
}
클라이언트 컴포넌트는 이벤트 핸들러나 hooks 등을 사용할 수 있다. 그렇다고 해서 클라이언트 컴포넌트가 서버에서 사전 렌더링 되지 않고, 빈 HTML과 JS 번들을 전달하는건 아니다.
위 예시 컴포넌트는 p태그와 버튼이 존재하는 형태 그대로 사전 렌더링된다.
아직 나는 Next.js 프레임워크의 모든 기능을 잘 알고 있는 것은 아니다. 파일 기반 라우팅과, 렌더링 방식에 대해서만 익숙한 상태다.
동작 원리와 구조를 명확히 이해해야 제대로 활용할 수 있다고 생각하기 때문에, Next.js가 제공하는 기능들에 대해 계속해서 딥다이브할 예정이다.
이론만큼 실습도 중요하기 때문에, Next.js의 다양한 기능들을 최대한 활용해볼 수 있는 사이드 프로젝트도 하나 준비 중이다. (이번에도 씹덕 게임 API를 활용하며 재미있게 학습하려고 생각 중이다.. 😄)
예전부터 TDD(Test Driven Development)에 관심은 있었지만, 테스트 라이브러리를 학습해야 한다는 부담과, 테스트 코드의 필요성을 크게 느끼지 못해 직접 작성해본 경험은 없었다.
하지만 최근 Date 객체와 관련된 비즈니스 로직을 구현하거나, Axios interceptor를 활용한 토큰 재발급 로직을 점검하는 과정에서 테스트 코드 없이는 테스트가 불가능한 상황을 경험했고, GPT의 도움을 받아 테스트 코드를 작성해보며 강력함을 직접 체감할 수 있었다.
또한 사내에서 리팩토링을 진행하며 느꼈던 불안감과, 실제 서비스 장애로 이어졌던 URL 개선 작업 등을 떠올리면, 테스트 코드는 개인적으로 선택이 아닌 필수라고 생각하게 되었다.
테스트 코드를 작성하는 과정은 비즈니스 요구사항을 정리하는 데 도움이 되고, 리팩토링 과정에서 발생할 수 있는 문제를 조기에 발견할 수 있다는 큰 장점이 있다.
다만 현재는 GPT의 도움 없이는 테스트 코드를 능숙하게 작성하긴 어렵기 때문에, 사이드 프로젝트에서 컴포넌트 단위 테스트에 Storybook을 도입하고, 비즈니스 로직 테스트에 Vitest나 Jest를 도입해 테스트 작성에 익숙해지려 한다.

(위 레퍼런스는 2025 네이버 공채 페이지입니다)
요즘 포트폴리오 웹앱이나 채용 공고 페이지에서 자주 보이는 스크롤 기반 애니메이션에 개인적으로 흥미가 있어, 노션으로 작성된 기존 포트폴리오 대신, 다양한 애니메이션과 최적화 기법이 적용된 포트폴리오 웹앱을 직접 하나 만들어보려고 생각하고 있다.
단순히 마크업으로 포트폴리오를 구현하는 대신, DB를 통해 내용을 저장해두고 백오피스 페이지를 통해 포트폴리오 수정이 가능하도록 구현할 계획이다. 이 과정에서 Next.js의 미들웨어를 활용한 인증도 함께 공부해보려고 한다.
한 해동안 정말 많은 일이 있었다보니, 한 번쯤 정리해두지 않으면 금방 잊혀질 것 같아 회고글 작성을 시작하게 되었다.
3주에 걸쳐 작성한 긴 글이지만, 나중에 다시 읽어보면 내가 24년을 어떻게 보냈는지 쉽게 떠오릴 수 있을 것 같고, 중간중간 정리한 개념들도 좋은 복습 자료가 되어줄 것 같다.
비록 회고글을 작성하는 데 시간이 많이 들긴 하지만, 매년마다 한 번쯤은 꼭 글을 쓰는 습관을 들여보려고 한다.
가치 있는 프론트엔드 개발자가 되기 위해서는 사용하는 기술들에 대한 깊은 이해와, 동료들과 잘 협업할 수 있는 커뮤니케이션 능력이 중요하다고 생각한다.
그렇기 때문에 앞으로도 열심히 배우고, 코딩하며 가치있는 프론트엔드 개발자가 되기 위해 노력하려고 한다.
저도 보충역으로 프론트엔드 준비 중인데, 진주같은 글을 보게 되어서 정말 반가웠습니다 😊
앞으로도 좋은 일만 있길 바래요 ㅎㅎ