결과물
14일간의 개인 프로젝트가 드디어 끝이 났습니다.
어쩌다 보니 갑자기 계획에 없던 프로젝트를 진행하게 되었습니다.
하고 싶은 게 생기면 바로 행동부터 하는 편인데 이번엔 그게 이 앱을 만들어내는 것이었습니다.
퇴사 이후 이사 일정이 있었기에 신경 쓸 것이 많아 그전까지는 사부작사부작 진득하니 하고 싶던 공부를 해야겠다 하며 무직 백수의 기간을 보내고 있었습니다.
날이 좀 풀린 어느 날 커피를 사러 가기 위해 대충 후드집업을 입고 나왔는데 세상에 너무 추운 겁니다.
'아.. 엄청 춥네.. 몇 도야..'
평소에도 귀찮아서 날씨를 안 보고 다니다 보니 봉변당하는 일이 잦았는데, 문득 비 오기 전에 우산 챙기라고 알려주고, 기온에 맞는 옷차림도 위젯으로 볼 수 있으면 좋겠다는 생각이 들었습니다.
사실 과거 팀 프로젝트를 진행할 때에도 같은 이유로 기온 별 옷차림을 추천해 주는 웹 사이트를 만든 적이 있습니다.
그때 당시에도 팀원들한테 "위젯 기능이 있으면 진짜 좋을텐데 언젠가 만들어 보고 싶어요"라는 말을 했었는데 문득 이 말이 스쳐 지나가면서 이제는 만들 수 있지 않을까? 하는 생각이 들었던 것 같습니다.
과거에는 웹 사이트에서 geolocation WEB API를 사용해 기온에 맞는 옷차림 정보만을 제공하는 게 끝이었지만, 이번에는 푸시 알림과 위젯을 지원하는 앱을 만들고자 했고 집에 돌아오자마자 바로 기획을 시작하게 되었습니다.
별로 어렵지 않았다며 너스레를 떨기엔 개인적으로 굉장히 도전적인.. 프로젝트였기 때문에 오늘은 개인적인 감상을 넣어 주절주절 프로젝트 회고를 한 번 작성해 볼까 합니다.
기획
만들고자 했던 기능
- 위젯을 통해 유저의 현재 위치 정보를 기반으로 날씨와 옷차림 정보를 제공한다.
- 비가 오기 전에 대비할 수 있도록 푸시 알람을 보내준다.
기획 단계에서는 해당 기능들을 구현하기 위해 어떤 작업이 필요한지, 어떤 기술 스택을 사용해야 할지 미리 파악 해야 했습니다.
무식한 자가 용감하다고 하죠. 기획 단계에서 제일 어려웠던 것은 프로젝트의 사이즈를 가늠하는 것이었습니다.
또한 이사 일정이 잡혀있었기에..가용 기간은 14일이 최대
무조건 14일 내에 구현을 완료하는 것을 목표로 잡고 기획을 세우기 시작했습니다.
고민) 하이브리드 앱 ( React-Native, Flutter ) vs IOS 네이티브 vs PWA
PWA의 경우 푸시 알림도 지원하고, 일부 제한된 환경에서(특정 브라우저) 위젯 기능도 제공한다는 글을 보긴 했는데 기능 명세 상 백그라운드에서 유저의 위치 정보를 받아와야만 했고 동적으로 스케줄러를 통해 유저 설문과 백그라운드 위치 정보를 기반으로 시간을 계산하여 푸시 알람을 전송해야 했기에 PWA로 이를 구현하려면 예상치 못한 이슈 + 정보를 찾기 쉽지 않을 것 같기도 했고 앱스토어에서 앱을 띄우고 싶었기에 PWA는 제외했습니다.
React-Native 경험이 있었기에 전체 프로젝트를 RN으로 구성할까 고민을 했었습니다.
다만 위젯 기능을 위해서는 하이브리드 앱으로 구현하더라도 필수 불가결로 네이티브 코드를 따로 만져야 했습니다.
하이브리드 앱의 존재 자체가 IOS와 안드로이드를 모두 커버하기 위해 탄생한 것인데, 사실 이 앱은 제가 쓰고 싶어서 만드는 것이었기에 최소 충족 조건은 IOS였습니다.
우선 RN을 사용하고 안드로이드 위젯 지원은 추후에 도전해보는 것으로 한 뒤 IOS만 핸들링해볼까도 고민했습니다.
다만 1인 프로젝트인지라 서버도 개발해야 하는 상황이었기 때문에 서버 기능들을 고려해 봤을 때 경험이 적은 서버 쪽에서 예상치 못한 병목이 분명 발생할 것이라 확신했습니다.
(가슴에 손을 얹고 정말 나중에 안드로이드 지원을 할 것인지 생각해보면 그렇지 않을 것 같았습니다)
이전에 웹뷰 앱 서비스의 개발을 담당하며, 네이티브 <-> JS 통신을 위한 브릿지를 구현해 본 적이 있었기에 (포스팅 참고) IOS 스위프트를 통해 네이티브 기능을 처리하고, 웹뷰로 브릿지를 구현하는 게 가장 효율적이겠다는 생각이 들었습니다.
고민 끝에 IOS 웹뷰 앱으로 결정하고, 웹뷰를 위한 클라이언트 환경은 SSR 경험이 없었기에 공부할 겸 Next로 구성하였습니다.
서버는 옛날에 노드 환경에서 NestJS + Mysql 조합을 사용하여 서버를 구현했던 적이 있고, Nest 경험이 좋았던지라 조금 더 익숙할 것이라 생각하여 다시 한번 사용해 보기로 결정하였습니다.
디자인
앱은 초기 부팅 화면, 최초 진입 유저를 위한 온보딩 화면, 알람 설문 화면, 기온과 옷차림을 확인할 수 있는 컨텐츠 페이지, 권한 설정 페이지로 구분하여 피그마로 설계를 진행했습니다.
서당 개도 3년이면 풍월을 읊는다더니 프론트엔드 3년이기도 하고, 같이 일했던 디자이너 분 덕분인지 예전에 직접 디자인해서 만들었던 프로젝트랑 비교해 봤을 때 디자인 실력이 많이 늘어난 것 같아 만들고 나서 기분이 매우 좋았습니다.
개인적으로 인터렉션을 좋아하는 편입니다. 귀염 뽀짝하고 부드럽고, 하지만 실무에서는 아무래도 기능 구현에 초점이 맞추어져 있다 보니 인터렉션을 구현할 기회가 별로 없어서 이번 프로젝트에서는 인터렉션을 적용하고자 했습니다.
인터렉션 결과물
동영상을 gif로 배속을 설정하여 변환하다 보니 버튼 인터렉션이 보이지 않는 부분이 있어 추가로 첨부합니다.
RippleButton
(컴포넌트는 스토리북 링크를 통해 확인 가능합니다)
프로젝트에 존재하는 모든 버튼 요소에서는 ripple 트랜지션이 동작합니다.
React-Native에 Touchableopacity 라는 버튼 컴포넌트가 존재하는데 그와 비슷하게 동작하는 애니메이션을 웹에 존재하는 버튼들에도 구현하고자 했고, RippleButton 컴포넌트를 만들어 모든 버튼 컴포넌트에 적용시켰습니다.
Ripple 컴포넌트 구현
useRipple.tsx
// useRipple hook
const useRipple = <T extends HTMLElement>(ref: React.RefObject<T>) => {
const [ripples, setRipples] = useState<React.CSSProperties[]>([]);
useEffect(() => {
if (ref.current) {
const $elem = ref.current;
const clickHandler = (e: MouseEvent) => {
const { left, top } = $elem.getBoundingClientRect();
const _left = e.clientX - left;
const _top = e.clientY - top;
const height = $elem.clientHeight;
const width = $elem.clientWidth;
const diameter = Math.max(width, height);
setRipples([
...ripples,
{
top: _top - diameter / 2,
left: _left - diameter / 2,
height: Math.max(width, height),
width: Math.max(width, height)
}
]);
};
$elem.addEventListener("click", clickHandler);
return () => {
$elem.removeEventListener("click", clickHandler);
};
}
}, [ref, ripples]);
const _debounced = useDebounce(ripples, 1000);
useEffect(() => {
if (_debounced.length) {
setRipples([]);
}
}, [_debounced.length]);
return ripples?.map((style, i) => {
return (
<span
key={i}
style={{
...style,
position: "absolute",
backgroundColor: "#FFFFFF",
opacity: "25%",
transform: "scale(0)",
animation: `ripple ${RIPPLE_DURATION}ms linear`,
borderRadius: "50%"
}}
/>
);
});
};
export default useRipple;
클릭 이벤트를 기반으로 ripple을 위한 hook을 구현했습니다.
RippleButton.tsx
export interface RippleButtonProps
extends ButtonHTMLAttributes<HTMLButtonElement> {
children: ReactNode;
onClick?: () => void;
delay?: number;
onRippleEndClick?: () => void;
}
const RippleButton = ({
// ripple duration default is 600ms
delay = RIPPLE_DURATION,
onClick,
onRippleEndClick,
children,
...props
}: RippleButtonProps) => {
const buttonRef = useRef<HTMLButtonElement>(null);
const ripples = useRipple(buttonRef);
return (
<button
ref={buttonRef}
{...props}
style={{ position: "relative", overflow: "hidden" }}
onClick={() => {
onClick && onClick();
if (onRippleEndClick) {
setTimeout(() => {
onRippleEndClick();
}, delay);
}
}}
>
{children}
{ripples}
</button>
);
};
export default RippleButton;
Ripple 동작을 모든 버튼 컴포넌트에 적용하고 싶었기 때문에 기본 button 요소의 어트리뷰트를 포함하고, RippleButton을 위한 인터페이스를 별도로 선언하였습니다.
onClick은 클릭 즉시 실행, onRippleEndClick은 기본 값으로 useRipple 훅에서 설정한 duration이 지난 후에 실행되도록 타이머 함수를 추가하였습니다.
클릭 메소드를 두 개로 나눈 것은 목적에 따라 다르게 동작해야 하는 경우가 있었기 때문입니다.
설문의 상단에는 progressBar가 존재합니다. 이는 답변 여부에 따라 transition이 발생하도록 구현되어 있는데 onClick으로만 페이지를 이동할 경우 페이지가 변경되면서 progress bar의 트랜지션이 동작하지 않는 문제가 있었기 때문에, onClick을 통해 답변 여부 상태를 업데이트하고, onRippleEndClick을 통해 페이지 전환이 진행되도록 구현하였습니다.
이렇게 구현된 RippleButton 컴포넌트를 통해 모든 버튼 컴포넌트 요소에 Ripple을 적용할 수 있었습니다.
LargeButton.tsx
import RippleButton, { RippleButtonProps } from "./RippleButton";
const LargeButton = ({ children, ...rest }: RippleButtonProps) => {
return (
<RippleButton
{...rest}
className="w-full px-20pxr rounded-10pxr bg-dark-primary text-dark-on-primary text-center button-b-18 py-15pxr"
>
{children}
</RippleButton>
);
};
export default LargeButton;
클라이언트 개발
클라이언트는 Next, TailwindCSS, Storybook, MSW로 구성하였습니다.
이 중 Next와 MSW는 처음 사용해 보는 것이었는데, Next는 개인적인 SSR 환경 경험을 위해, MSW는 서버 API가 만들어지기 전 API 연결을 진행하기 위해 사용했습니다.
Next도 난관이 여러 번 있었습니다.
프로젝트를 마친 지금도 Next를 제대로 사용했다고는 생각이 들지 않는데 이는 앞으로도 조금씩 공부를 해서 채워 나가야 할 것 같습니다.
빌드 환경
Next 프로젝트를 구성할 때 Turbopack을 사용해 볼까도 고민했는데, 아직 베타 버전이기도 하고, 프로드에 올리기에는 안정성 부분에서 우려되는 것들이 많아 webpack을 유지하고, 컴파일러는 SWC를 선택해 조금이라도 빌드 속도를 개선해보고자 했습니다.
CRN 웹팩으로 구성되어있던 서비스를 Vite으로 마이그레이션 하며 개발 경험이 상당히 상승하여 참 좋았는데.. 역체감이라고 하죠.
다시 웹팩 환경으로 돌아오니 웹팩 HMR이 너무 답답하게 느껴져서 새삼 esbuild 성능에 감탄했던 것 같습니다.
(Turbopack 개발 과정에서 Vite과 협력 중이라는 이야기를 보긴 했지만 아직은 Next가 Vite을 추천하고 있는 것 같진 않아 추후 고려해 보는 것으로 결정했습니다. (https://nextjs.org/docs/app/building-your-application/upgrading/from-vite))
MSW
서버 API 작업이 완료되기 전 Next app router에 MSW를 적용하는 것도 환경에 따라 분기처리를 해주어야 하는 부분이 있어 골치를 조금 먹었던 것 같습니다.
런타임환경에 따라 워커를 분기처리하여 실행될 수 있도록 적용했고, 서버 런타임에서도 동작할 수 있도록 별도의 express 서버를 같이 띄워 express로 전달되는 요청을 가로채 응답할 수 있도록 구현했습니다.
Storybook
그리고 이전에 npm 오픈소스를 하나 만들면서 컴포넌트의 확장성을 보여주기 위해 스토리북을 적용해 다양한 사용 예제를 보여준 적이 있었는데, 좀 더 실무 환경에서 사용해보고 싶어 스토리북을 적용해 보았습니다.
스토리북 애드온을 추가하고 크로마틱에 연동해서 UI테스팅도 진행할 수 있도록 구현해 보았는데, 협업 도구로서 굉장히 좋은 인상을 받았습니다.
다만 새로운 기술을 처음 도입하려고 할 때에는 항상 추가적인 리소스가 들어가게 되는데, 그러다 보니 사실 컴포넌트마다 스토리를 새로 만드는 게 생각보다 번거롭게 느껴진 부분이 있었습니다.
기존 디자인 시스템이 어느 정도 구축되어 있는 상태라면 어느정도 스토리에 대한 컨벤션도 존재할 것이기 때문에 이런 번거로움은 프로젝트 설계 초반의 과도기일 것이라 생각하긴 했지만, 스토리가 작은 컴포넌트를 벗어나 하나씩 조합으로 사용되기 시작하면 이것 또한 어떤 기준으로 어떤 목적을 가지고 작성해야 하는지 동료들과의 충분한 협의가 필요할 것 같다 생각했습니다.
그런 과도기만 지난다면 스토리북의 이점을 잘 활용할 수 있을 것 같아서 실무에서 어떤 식으로 사용되고 있는지도 개인적으로 조금 궁금해져서 기회가 된다면 실무에서 스토리북을 사용해보고 싶다는 생각을 했습니다.
브릿지 통신 ( bridge swift <-> JS )
그다음으로 머리가 아팠던 부분은 브릿지 통신이었는데요.....
브릿지는 크게 4가지 기능이 필요했습니다.
- 유저 FCM 토큰 전달
- 유저 권한 요청 (권한 설정 페이지를 통해 별도 수정 가능)
- 권한 여부 변경 시 구독 상태 관리
- 유저 위치 정보 전달
IOS에 javascript의 코드를 실행시킬 수 있는 evaluateJavaScript 메서드가 존재하는데, 이 메서드를 통해 window 객체에 데이터를 저장하거나, window 객체에 정의되어 있는 메소드를 호출하여 클라이언트의 상태를 갱신하는 방식으로 구현할 수 있었습니다.
문제는 웹뷰 환경이다 보니 웹뷰 로드가 되기 전에 evaluateJavaScript를 호출하는 경우 에러가 발생하고 값이 전달되지 않는 등의 문제가 있어 권한 요청 함수의 실행 시점 등을 알맞게 변경해 주는 작업을 진행했습니다.
클라이언트에서는 이 값을 어떻게 관리하는 것이 효율적일까 생각을 했는데, 브릿지 통신을 위한 훅을 별도로 빼고 페이지를 Webview 컴포넌트로 감싸 그 안에서 바로 값이 갱신될 수 있도록 구현했습니다.
처음에는 ContextAPI를 통해 구현할까 생각했지만, 유저의 위 경도 좌표는 작은 움직임에도 정보가 변경되는 부분이 있어 ContextAPI를 사용하면 불필요한 리렌더링이 상당 부분 발생할 것이라 생각해 Jotai 상태관리 라이브러리를 사용하여 atom store에 저장해 놓은 값을 효율적으로 사용할 수 있도록 구현했습니다.
여러 난항 끝에 생각보다 깔끔하게 브릿지를 구현할 수 있었습니다.
사실 기술적으로 어려웠다기 보단 Swift, 클라이언트, 서버 프로젝트를 모두 실행시킨 뒤 분리된 환경에서 로그를 보며 디버깅을 진행하고.... 3개의 프로젝트를 왔다 갔다 하며 수정했어야 했는데, 편집기 딜레이가 기본 4초씩 걸리는 현상을 감내하며 구현하다 보니 엄청 험난하게 느껴졌던 것 같습니다.
19년형 인텔 맥북의 한계를 절실히 느끼는 바람에 하반기에는 새로운 맥북을 사야겠다며 혼자 다짐했습니다.
IOS (Swift) 개발
IOS에선 크게 웹뷰, Firebase, 위젯, 권한 처리, 브릿지 코드를 구현했습니다.
Swift를 이해하고 구현을 시작한 게 아니다 보니 특히 Swift에서 유독 많은 난항이 있던 것 같습니다.
하지만 필요하다면 얼른 만들어야죠, IOS 작업은 우리의 든든한 파트너, GPT4와 함께 했습니다.
새로운 기술을 습득할 때 GPT가 좋은 이유는 종합적인 인사이트를 빠르게 얻을 수 있어서라고 생각합니다.
우선적으로 특정 기능을 구현하는 것이 기술적으로 가능한 것인지, 어떤 기술들을 사용할 수 있는지 플랜 A ~ N까지 인사이트를 얻고 적당한 방안을 찾아 나가는 식으로 구현을 했습니다.
문법이나 환경을 이해하고 있는 것이 아니기 때문에 무조건 적인 코드 요청 보단 빠르게 동작을 이해할 수 있도록 하는 하나의 매개체로서 사용하는 게 여러 기술을 직접 떠올리며 응용할 수 있다 보니 디버깅이 아주 막막해지는 일은 적어 개인적으로 좀 더 긍정적인 결과가 나왔던 것 같습니다.
IOS 개발 중 가장 기억에 남는 것은 위젯이었습니다.
위젯에서도 웹뷰를 혹시 사용할 수 있는 건지 기대했지만 불가능했고, Xcode에서 별도의 Widget Extension을 추가해 새로운 타깃을 만들고, 직접 UI와 http 통신을 진행해야 했습니다.
위젯이 비로소 동작했을 때의 감동은 이루 말할 수 없었습니다.
사실 그동안 IOS와 싸우며..... 서버와 싸우며..... 아니 이거.... 만들 수 있는 건가..? 무식하면 용감하다더니 정말 너무 용감했던 것 같다는 생각을 하고 있었는데 위젯이 제대로 동작하는 걸 확인한 순간 도파민 대폭발
그래 역시 하면 된다니까~~~~ 하는 생각이 들면서 정말 기분이 너무 좋았던 것 같습니다.
이 성취감을 느끼려고 매번 용감하게 덤비고 보는 것 아닐까 생각이 드네요.
그리고 특이했던 것은 IOS에서 서버로 http 요청을 보낼 때 CORS 설정을 위해 Origin 헤더를 알아보던 중 IOS 앱은 Origin 헤더가 null로 설정되어 있거나 전송되지 않는다는 특징이 있다는 것이었습니다.
별도로 애플 쪽에서 제공하는 Origin이 있지 않을까 생각했는데 아예 존재하지 않는다고 하니, 하긴 이렇게 구현하는 서비스가 얼마나 있겠어라는 생각이 들었습니다.
그래도 이런 경우가 없지는 않을 것 같은데 그럴 때 서버에서 CORS를 어떻게 관리하고 있는지 개인적인 궁금증이 남아있습니다.
서버 개발
서버는 Node 환경에서 Nest와 Mysql을 사용해 구현했습니다.
Nest의 경우 공식 문서가 잘 작성되어 있는 편이라 다시 감을 잡는 데에는 어렵지 않았지만, 기능 중 알림 시간대를 설정한 유저에게 미리 알림 형태로 유저에게 푸시 알림을 전송하는 기능이 존재했는데, 설문 기반이다 보니 유저 별로 모두 발송 시간이 다르기 때문에 이걸 어떻게 구현할지에 대해 감이 오지 않는 부분이 있었습니다.
다행히 Nest 스케줄러에 있는 Cron 데코레이터를 사용해 알림 타입에 맞게 매 분마다, 매 시간마다 동작하는 두 개의 스케줄러를 만들어 해결할 수 있었습니다.
현재는 미리 데이터를 푸시 알람에 보여주는 것이 아닌 유저가 요청한 period만을 동적으로 변환해 푸시 알람을 발송하여 앱 방문 유도를 진행하고 있습니다.
원래 기획에서는 푸시 알람 시간대가 된 경우, IOS를 통해 유저의 위치 정보를 받아 데이터를 가공해서 보여주고자 했습니다.
IOS의 remote notification 기능에는 Silent Push 기능이 존재합니다.
유저에게 푸시 알림을 노출하지 않고 별도의 로직을 실행시키기 위해 사용되며, 주로 앱의 업데이트를 위해 사용됩니다.
IOS 쪽에서는 알림이 전송되면 didReceiveRemoteNotification이라는 메서드가 실행되는데 본래는 이 메소드 내부에서 http HEAD 메서드를 통해 푸시 알림용 서버 API를 호출하고 쿼리 파라미터를 통해 유저 정보를 전달, 서버는 이 요청이 들어오면 데이터를 포함한 푸시 알람을 보낼 수 있도록 설계를 진행했습니다.
문제는 fcm을 사용하고 있다 보니, firebase-admin을 통해 ios silent push를 실행시켜야 했는데 IOS에서 didReceiveRemoteNotification 메서드 실행이 되지 않아 씨름을 하다가 결국 구현하지 못하고 우선 기획을 변경하였습니다.
앱이 아닌 웹뷰 앱이다 보니 서버의 버전 하위 호환성 부분에서 상대적으로 자유로운 부분이 있다고 생각해, 우선 추후 피쳐에 수정하는 것으로 변경하고 서버 배포 작업을 진행하는 것으로 결정했습니다.
배포
1. 클라이언트 배포
클라이언트는 Vercel을 통해 CI/CD 배포 환경을 구성했습니다.
프론트엔드 배포 툴을 결정하기 전에 어떤 것을 사용할지 고민을 하다가 amplify를 사용해 본 경험이 있어 이것저것 찾아 보았는데 앰플리파이는 Next의 스트리밍 기능을 지원하지 않는다는 것을 알게 되었습니다
참고 링크 https://github.com/aws-amplify/amplify-hosting/issues/3843
Next의 streaming은 이미지처럼 HTML을 작게 나누어서 모든 데이터가 로드되기 전에 미리 상호작용을 할 수 있도록 하는 기술인데, Suspense 컴포넌트와 layout.js를 통해 사용할 수 있습니다.
다시 말해 앰플리파이로 Next를 배포할 경우 서스펜스 기능을 사용하지 못하는 것입니다.
Suspense가 가져다주는 이점이 크다고 생각해 프론트는 Next에 최적화된 Vercel을 사용하기로 결정했습니다.
2. 서버 배포
서버는 https통신을 위해 가비아에서 도메인을 구입하고, 지갑 보호를 위해 AWS의 프리티어 계정으로 EC2, RDS, loadbalance, Route53, ACM을 통해 배포 환경을 구성했습니다.
직접 배포해 본 경험이 없다 보니 언젠가 한 번 배포를 해보고 싶었는데 이번에 그것을 이룬 것 같아 매우 재밌었습니다.
물론 고통스럽기도 했습니다.
초반에 EC2, RDS를 통해 서버 배포를 진행해서 우선 http로 EC2 IP에 직접 접근해 사용했었는데, 프론트는 https, 서버는 http 프로토콜을 사용하고 있어 mixed contents 에러가 발생했고, 이를 해결하기 위해 DNS 호스팅을 구성하는 과정에서 갑자기 ssh가 먹통이 되는 문제가 발생했었습니다.
스크립트를 실행해도 실행 로그만 찍히고 먹통이 되는 바람에 무엇이 문제인지 파악이 안 되어 이것저것 시도해 보다가 결국 ec2 인스턴스, rds, 호스팅을 처음부터 다시 설정했는데도 해결되지 않는 걸 보고 속으로 눈물을 조금 흘렸습니다.
알고 보니 프리티어용 인스턴스를 사용하다 보니 ec2 인스턴스의 CPU 메모리가 금방 부족해지는 바람에 먹통이 되는 것이었고, swap 메모리를 설정해 하드 디스크의 디스크 공간을 메모리처럼 사용할 수 있도록 변경한 뒤 해결할 수 있었습니다.
앱 심사
14일 차에 앱 심사를 요청할 수 있었습니다.
그리고 앱이 리젝 되어 재심사를 위한 인내의 시간을 보낸 뒤 드디어..
앱 배포
배포에 성공했습니다 🥰
날씨 데이터는 OpenWeather API를 사용하고 있는데 하루 1,000 호출까지 무료이기 때문에 사실 딱히 유명해지길 바라지는 않고 있습니다.
주변에 조금 자랑했는데 생각보다 반응이 좋아서 뿌듯하면서도 오히려 약간 당황스러운 상태네요
앱스토어 올라가면 알려 드린다 하고 있는데 갑자기 부담감에 어깨가 무거워지는 바람에 우선 혼자 쓰다가 주변에는 푸시 알람에 데이터를 보여주는 것까지 구현하고 알릴까 싶습니다.
이론상 위젯 설치 + 하루 두 번 앱에 접속한다고 가정했을 때 30명 내외가 유효 유저 범위이기 때문에 갑자기 API 응답이 없고 먹통이 되는 상황이 올 수도 있기때문에 약간 조마조마하네요
처음 진행해 본 개인 프로젝트였는데, 개인적으로는 상당히 도전적인 프로젝트였기에 감회가 남다른 것 같습니다.
일을 다니며 공부할 땐 내가 쓰고 있는 걸 더 잘 쓰기 위한 학습에 초점을 맞추었는데 그 범위를 벗어난 부분을 경험하는 게 시야를 확장시키는 데에 참 중요한 것 같다는 생각을 요즘 자주 하고 있습니다.
결과물이 바로바로 나오는 것을 좋아하다 보니 가장 재밌던 프론트엔드의 길을 선택해 지금도 걷고 있지만, 사람의 취향은 바뀌기 마련이고, 계속 다듬어가다 보면 자신감이 생기고 그러다 또 재미가 생기는 부분이 있다 보니 앞으로는 좀 더 넓은 범위로 공부하며 개발 생태계 전체를 연결해서 볼 수 있는 시야를 갖고자 하는 소망이 있습니다.
아직도 모르는 것이 많지만, 그래서 더욱 개발이 재밌는 것 같습니다.
다음에는 더 멋진 프로젝트를 만들어 찾아오겠습니다.
감사합니다!
'Project' 카테고리의 다른 글
[퍼스트 프로젝트 / 리팩토링] weSeason v2.0.0 배포 완료, 리팩토링 회고록 (0) | 2021.02.26 |
---|---|
[typescript / redux / react] 클라이언트 사이즈 확인, 실시간 리사이징 상태 변경에 대한 삽질 기록 (0) | 2021.02.14 |
[4주 프로젝트] soundWave / 백색 소음과 음원을 조합, 플레이 리스트 만들기 / 프로젝트 회고록 (0) | 2021.02.04 |
[퍼스트 프로젝트] 떨리고 설렜던 주니어 프론트엔드 개발자의 첫번째 프로젝트 회고 (0) | 2021.02.04 |