1. Yahoo Finance를 브라우저에서 직접 부를 수 없는 이유
시장 데이터 대시보드를 만들며 가장 먼저 마주한 벽은 CORS(Cross-Origin Resource Sharing) 정책이었다. Yahoo Finance API를 브라우저에서 직접 호출하자마자 무심한 에러 메시지가 출력됐다.
시장 데이터 대시보드를 만들면서 가장 먼저 부딪힌 문제였다.
Yahoo Finance API를 브라우저에서 직접 호출하면 이런 에러가 난다.
Access to fetch at 'https://query1.finance.yahoo.com/...'
from origin 'https://...' has been blocked by CORS policy
CORS는 보안을 위해 브라우저가 실행 중인 도메인과 다른 곳의 리소스를 함부로 가져오지 못하게 막는 방패다. Yahoo Finance 서버가 브라우저의 직접적인 요청을 허용하지 않도록 설정되어 있기 때문에 발생하는 문제다.
처음엔 allOrigins 같은 무료 프록시 서비스를 고려했지만, 외부 서비스의 불안정성에 내 프로젝트를 맡기고 싶지 않았다. 결국 Vercel Serverless Functions를 활용해 직접 프록시 서버를 구축했다. "Vercel을 한 번 써볼까?" 하는 호기심에 확신을 준 계기가 되었다.
2. 요청 문제들
2-1. 제미나이가 자꾸 404로 응답한다 - 서버 대 서버로 요청하게 해보자
// api/yahoo.js
export default async function handler(req, res) {
const { ticker, range, interval } = req.query;
const url = `https://query1.finance.yahoo.com/v8/finance/chart/${ticker}?interval=${interval}&range=${range}`;
const upstream = await fetch(url, {
headers: { 'User-Agent': 'Mozilla/5.0' }
});
const data = await upstream.json();
res.setHeader('Cache-Control', 's-maxage=300, stale-while-revalidate=600');
return res.status(200).json(data);
}
```
브라우저 → Vercel 서버 → Yahoo Finance 순서로 요청이 흐르면 CORS 문제가 사라진다. 서버끼리의 통신은 CORS 제한을 받지 않기 때문이다.
덤으로 `Cache-Control` 헤더를 붙여서 Vercel CDN이 5분간 응답을 캐싱하게 했다. 여러 사용자가 같은 데이터를 요청해도 Yahoo Finance에는 한 번만 호출된다.
2-2. 제미나이가 자꾸 404로 응답한다 - Gemini API 모델명이 바뀌었다
AI 분석 기능을 붙이고 나서 며칠 뒤 갑자기 이런 에러가 났다.
```
This model models/gemini-2.0-flash is no longer available to new users.
처음엔 API 키 문제인 줄 알았다. 키를 새로 발급받고 다시 시도해도 똑같은 에러가 발생했다.
알고 보니 Google이 gemini-2.0-flash 모델을 신규 사용자에게 지원 중단한 것이었다.
공식 문서를 보면 모델이 주기적으로 deprecated되고 새 버전으로 교체되곤 한다.
// 에러 나는 코드
const modelId = 'gemini-2.0-flash';
// gemini-3-flash 시도 → 또 에러
// gemini-2.5-flash 시도 → 또 에러
// gemini-2.5-flash-lite → 동작!
const modelId = 'gemini-2.5-flash-lite';
Google AI Studio에서 현재 사용 가능한 모델 목록을 직접 확인하는 게 가장 빠른 방법이었다.
무료 티어에서 RPD(일일 요청 수)가 Unlimited인 모델을 고르면 된다.
const MODEL_ID = 'gemini-2.5-flash-lite'; // 여기만 바꾸면 됨
const url = `https://generativelanguage.googleapis.com/v1beta/models/${MODEL_ID}:generateContent?key=${apiKey}`;
그리고 모델명을 하드코딩하지 않고 변수로 빼두면 나중에 바꾸기 쉽다.
3. 60일 이동평균치가 왜 안 나올까 - 참고할 데이터가 부족해서!
기술적 지표를 구현하고 나서 60일 이동평균 대비 수치가 계속 —%로 나왔다.
코드 로직은 맞았다. 문제는 데이터였다.
function calcSMA(closes, n) {
if (closes.length < n) return null; // 여기서 null 반환
return closes.slice(-n).reduce((a, b) => a + b, 0) / n;
}
Yahoo Finance에서 1년치 데이터를 주간봉(1wk)으로 받으면 약 52개 포인트가 온다. 60일 SMA를 계산하려면 최소 60개가 필요한데 52개밖에 없으니 계산이 안 되는 것이었다.
해결책은 간단했다. 기술적 지표 계산용으로 2년치 데이터를 별도로 받으면 된다.
// 기존: 1년치로 지표 계산
indicators: calcIndicators(hist['1y'], q.curr),
// 수정: 2년치로 지표 계산 (104개 포인트)
indicators: calcIndicators(hist['2y'], q.curr),
차트에 표시되는 기간은 그대로 유지하면서 지표 계산만 더 긴 데이터를 쓰면 된다. API 호출이 1번 더 늘어나지만 지표 정확도를 위해 감수할 만하다.
4. JS 변수명 하나가 서버를 조용히 망가뜨린다
RSS 뉴스 파싱 API를 만들다가 이상한 버그를 발견했다. 코드는 에러 없이 실행되는데 뉴스가 하나도 안 왔다.
export default async function handler(req, res) {
// ...
await Promise.allSettled(RSS_FEEDS.map(async feed => {
const res = await fetch(feed.url); // 문제의 줄
if (!res.ok) return; // 여기서 핸들러의 res를 덮어씀
const xml = await res.text();
// ...
}));
return res.status(200).json({ news }); // res가 fetch Response로 덮여있음
}
핸들러 함수의 파라미터 res(Vercel 응답 객체)와 fetch 결과를 담는 const res가 같은 이름이었다. 안쪽 스코프의 res가 바깥 res를 가려버려서(shadowing) res.status(200)을 호출할 수 없게 된 것이다.
JavaScript는 이런 변수 shadowing을 에러로 처리하지 않는다. 그래서 런타임에서 조용히 실패했다.
// 수정: 변수명 구분
const feedRes = await fetch(feed.url);
if (!feedRes.ok) return;
const xml = await feedRes.text();
변수명 하나 바꿨을 뿐인데 뉴스가 정상적으로 왔다. .
'problem_solving > MarketRadar' 카테고리의 다른 글
| 6. 아무튼 내 잘못은 아닌 거 같은데 (0) | 2026.04.19 |
|---|---|
| 5. 기획이 엉성하면 몸이 고생한다. (7) | 2026.04.12 |
| 4. RSS를 깎자 (0) | 2026.04.02 |
| 3. 프론트엔드 고도화 (0) | 2026.04.01 |
| 2. 배포 이후 수행했던 것들 (0) | 2026.03.30 |