problem_solving/MarketRadar

3. 프론트엔드 고도화

점심뭐먹지 2026. 4. 1. 03:32

0. 리액트로 이사가기

화면배치를 바꾸다보니 슬슬 리액트로 넘어가는 게 맞을 거 같아서 작업을 진행중.

 

1. 대시보드면 정적 페이지여도 충분할텐데?

크게 3가지 문제가 있었다.

 

  • 시인성의 한계: 정보가 위아래로 너무 길게 나열되어 한 눈에 들어오지 않음.
  • 정보의 파편화: 뉴스 데이터가 분석 프롬프트용으로만 소비되고 추가 활용이 안되고 있었음.
  • 긴 스크롤: 핵심인 AI 분석 결과를 보려면 화면 맨 아래까지 내려가야 하는 불편함.

 

대시보드는 한 눈에 들어오는 시인성이 중요한데, 지금은 위 아래로 길게 정보를 뿌려주고 있었다.

 

AI 분석도 맨 밑에 있어서, 스크롤을 내려서 봐야하는 사소한 불편함이 있지 않나 싶어졌다.

 

지금 분석은 뉴스도 수집하고 있는데, 프롬프트 용으로 수집하는 게 아니라 '최근 뉴스'정도로 해서 주요 뉴스들을 뉴스 탭으로 따로 빼서 보게 하는 것도 괜찮을 것 같았다.

 

대시보드, AI분석, 최근 뉴스 정도로 탭을 늘여놓다 보니 이정도 탭 분량이면 화면 전환도 될 테니 리액트로 넘어가도 될 것 같아졌다.

 

2. 그러면 뭘 어떻게 바꾸는 거지?

  • 3개 탭의 구성: 대시보드(Overview), 뉴스(News), 심층분석(Analysis).
  • Tailwind CSS + Radix UI: 직접 스타일을 짜던 시간은 줄이고, 디자인의 완성도와 접근성(Accessibility)은 높이고.
  • 상태 관리 전략:
    • 서버 상태(React Query): 비동기 데이터 관리의 복잡함을 줄이기 위해 도입. useState가 복잡해지기 시작해서 캐싱과 로딩 상태를 더 효율적으로 처리하도록 도입.
    • 클라이언트 상태: 현재는 탭이나 필터 정도의 가벼운 상태만 필요해서 Zustand 도입은 보류.

 

탭은 3개, UI도 생짜 css로 짜던 걸 리액트 + 테일윈드로 바꾸고, 리액트 쿼리를 도입해보기로 했다.

주스탠드를 보류한 이유는 여러 컴포넌트들에서 상태를 공유해야 할 때 쓰는 게 맞을 텐데, 당장은 그렇게까지 할 상황이 아니기 때문.

 

useState로 각 컴포넌트마다 데이터를 관리하면서

  • 로딩 / 에러 상태 처리 코드가 반복되고
  • 탭 이동 시 데이터를 다시 불러오는 문제가 있었다

이를 해결하기 위해 React Query를 도입했다.

 

리액트 쿼리를 도입한 이유도 이 서비스는 어쨌든 서버에서 가져온 것들을 활용하는데, useState만으로는 관리해야할 게 많아지는 것 같아서 채택했다.

 

3. 탭을 3개로 늘렸다

3-1. 대시보드 탭

탭을 3개로 늘림

 

한 화면에서 길게 보는걸 아예 대시보드만 첫 화면에 남겨두고,

나머지 AI 분석이나 뉴스 관련 기능은 따로 탭을 팠다.

 

3-2. 심층분석 탭

AI 분석을 위한 탭을 따로 놓았다. 침고용 지표도 추가.

 

 

AI 기능만 있으면 또 애매하니 차트를 좀 더 넣었고

 

3-3. 뉴스 탭

RSS로 긁어오는 뉴스들도 한 눈에 볼 수 있게

 

 

RSS로 긁어오는 뉴스도 따로 참고할 수 있게 해뒀다.

받아오는 뉴스를 다양하게 하려고 NHK RSS도 챙겨봤는데 경제쪽이랑은 거리가 먼 뉴스여서 대체할 곳을 고민중이다.

 

3-4. footer

그리고 footer도 넣어뒀다.

누가 만들었고, 만약 의견을 주고 싶다면 연락할 곳을 만들어두는 게 좋으니까.

 

4. 그럼 다른 거는?

 

4-1. Yahoo api 요청 최적화

  • Before: 브라우저 → 야후 API (58회 요청)
  • After: 브라우저 → Vercel 서버 (1회 배치 요청) → 야후 API (58회 병렬 처리)

 

Yahoo 쪽으로 58개를 매번 요청할 때마다 보내게 되니 API 요청이 너무 잦다는 생각이 들었다.

사용자 수에 따라 IP 차단의 우려도 있을 수 있고, 매번 날리니 성능저하의 문제도 있었다.

 

그래서 Vercel 서버 api 함수를 추가로 제작.

return Promise.all(items.map(([ticker, range, interval]) => {
      const url = `https://query1.finance.yahoo.com/v8/finance/chart/${encodeURIComponent(ticker)}?interval=${interval}&range=${range}`;
      return fetch(url, { headers: { 'User-Agent': 'Mozilla/5.0' } })
        .then(upstream => {
          if (!upstream.ok) return { ticker, range, error: upstream.status };
          return upstream.json().then(json => {
            const result = json.chart.result[0];
            return {
              ticker,
              range,
              data: {
                quote: extractQuote(result),
                history: extractHistory(result),
              }
            };
          });

 

Vercel 서버에서 60여개의 요청을 병렬로 날리고, 리액트 쪽으로 브라우저가 정보를 줄 때는 묶어서 1회로 데이터를 받게 하도록 만들었다. 브라우저에서 보내는 건 최소화 하고, vercel 서버에서 대신 처리하도록 했다.

 

결과:

  • 브라우저 요청: 58회 → 1회
  • 초기 로딩 속도 체감 개선
  • API 호출 실패율 감소

 

4.2 TypeScript & Tailwind CSS

  • TypeScript: 프론트와 백엔드 사이의 데이터 규격을 명확히 하여, 향후 자바 서버로 전환할 때를 대비해 도입.
    무엇보다도 요새 리액트 하면 타입스크립트 못쓰면 안된다는 이야길 들었었다.
  • Tailwind CSS: 유틸리티 우선(Utility-first) 방식으로 스타일링 생산성을 최대한으로. 덕분에 디자인에 신경을 덜 써도 되서 편했다.

4.3 Fail-Open 전략

속도 제한(Rate Limiting)을 담당하는 Vercel KV(Redis)에 장애가 발생할 수 있을 가능성이 있으니, 만약 VercelKV에 장애가 발생하더라도 전체 서비스를 차단하는 대신, 에러를 로그로 남기고 요청을 허용하도록 변경했다.

 

악의적인 요청을 방지하고자 만든 자물쇠인데, 이 자물쇠가 망가져서 서비스를 이용을 못하게 되는 건 말이 안된다고 생각했었다.

 

4.4 트라이 해본 것

  •  응답이 10초 제한에 걸리며 504 발생

원인:

  • 프롬프트 토큰 과다
  • 모델 응답 시간

해결:

  • prompt 경량화 (키 축약, 뉴스 길이 제한)
  • gemini-2.5-flash → gemini-2.5-flash Lite로 롤백
  • 서버 timeout 전략 조정

결과:

  • 응답 속도 개선 (10초+ → 약 6초)
  • Vercel 함수 제한(10초) 내 안정적 처리
  • 504 Gateway Timeout 및 분석 실패 문제 해결

Gemini 2.5 flash로 모델을 바꿔봤더니 응답 대기 시간이 너무 길어졌다.

1년치 지수들의 지표를 전부 프롬프트에 넣어서 보내다보니 계산하는 게 길어지기도 해서, Gemini 2.5 Flash Lite로 다시 롤백할 수 밖에 없었다.

 

빠르게 답변주는 간단한 모델이 필요했던 건 속도 문제도 있었지만, Vercel의 무료 요금제에서는 요청 응답 시간을 10초로 끊는다. 상황에 맞춰서 모델을 취사선택할 수밖에 없어서 Gemini 2.5 flash lite로 다시 롤백할 수밖에 없었다.

 

5. 이후의 계획

Vercel은 너무 편한 대신, 조금만 더 파고들려면 과금이 추가로 든다.

따라서 스프링부트로 서버를 직접 짜서 Vercel을 점차 대체하려 한다.