From 040866292ea92dc8b1311cc7d546d7adc8d956f4 Mon Sep 17 00:00:00 2001 From: gahusb Date: Fri, 20 Mar 2026 01:55:48 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20=EB=A1=9C=EB=98=90=20API=20504=20?= =?UTF-8?q?=ED=83=80=EC=9E=84=EC=95=84=EC=9B=83=20=EB=B0=8F=20Application?= =?UTF-8?q?=20error=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - _nas.ts: AbortSignal timeout 10s → 25s (NAS 무거운 연산 대응) - stats/performance, report/latest, report/history: maxDuration = 60 추가 (Vercel 함수 타임아웃 연장) - ReportTab: 에러 응답({error:"NAS_TIMEOUT"}) 받을 시 렌더 전 차단, confidence_factors null guard 추가 - PurchaseTab: API 에러 응답 감지 후 조용히 빈 상태 유지 - PatternTab: 에러 응답 감지 후 에러 메시지 표시 Co-Authored-By: Claude Sonnet 4.6 --- app/api/lotto/_nas.ts | 10 +++++----- app/api/lotto/report/history/route.ts | 2 ++ app/api/lotto/report/latest/route.ts | 2 ++ app/api/lotto/stats/performance/route.ts | 2 ++ app/services/lotto/recommend/PatternTab.tsx | 5 ++++- app/services/lotto/recommend/PurchaseTab.tsx | 3 ++- app/services/lotto/recommend/ReportTab.tsx | 10 ++++++++-- 7 files changed, 25 insertions(+), 9 deletions(-) diff --git a/app/api/lotto/_nas.ts b/app/api/lotto/_nas.ts index c9ce43c..e801efa 100644 --- a/app/api/lotto/_nas.ts +++ b/app/api/lotto/_nas.ts @@ -15,9 +15,9 @@ function nasBase() { return base; } -export async function nasGet(path: string): Promise { +export async function nasGet(path: string, timeoutMs = 25000): Promise { const res = await fetch(`${nasBase()}${path}`, { - headers: nasHeaders(), signal: AbortSignal.timeout(10000), + headers: nasHeaders(), signal: AbortSignal.timeout(timeoutMs), }); if (!res.ok) throw new Error(`NAS_${res.status}`); return res.json(); @@ -28,7 +28,7 @@ export async function nasPost(path: string, body: unknown): Promise { method: 'POST', headers: { ...nasHeaders(), 'Content-Type': 'application/json' }, body: JSON.stringify(body), - signal: AbortSignal.timeout(10000), + signal: AbortSignal.timeout(25000), }); if (!res.ok) throw new Error(`NAS_${res.status}`); return res.json(); @@ -39,7 +39,7 @@ export async function nasPut(path: string, body: unknown): Promise { method: 'PUT', headers: { ...nasHeaders(), 'Content-Type': 'application/json' }, body: JSON.stringify(body), - signal: AbortSignal.timeout(10000), + signal: AbortSignal.timeout(25000), }); if (!res.ok) throw new Error(`NAS_${res.status}`); return res.json(); @@ -47,7 +47,7 @@ export async function nasPut(path: string, body: unknown): Promise { export async function nasDelete(path: string): Promise { const res = await fetch(`${nasBase()}${path}`, { - method: 'DELETE', headers: nasHeaders(), signal: AbortSignal.timeout(10000), + method: 'DELETE', headers: nasHeaders(), signal: AbortSignal.timeout(25000), }); if (!res.ok) throw new Error(`NAS_${res.status}`); return res.json(); diff --git a/app/api/lotto/report/history/route.ts b/app/api/lotto/report/history/route.ts index cf3823a..bc641b6 100644 --- a/app/api/lotto/report/history/route.ts +++ b/app/api/lotto/report/history/route.ts @@ -1,6 +1,8 @@ import { NextResponse } from 'next/server'; import { nasGet, requireSubscription, handleNasError } from '../../_nas'; +export const maxDuration = 60; + export async function GET(request: Request) { try { const auth = await requireSubscription(); diff --git a/app/api/lotto/report/latest/route.ts b/app/api/lotto/report/latest/route.ts index 8b3c3ed..88758d9 100644 --- a/app/api/lotto/report/latest/route.ts +++ b/app/api/lotto/report/latest/route.ts @@ -1,6 +1,8 @@ import { NextResponse } from 'next/server'; import { nasGet, requireSubscription, handleNasError } from '../../_nas'; +export const maxDuration = 60; + export async function GET() { try { const auth = await requireSubscription(); diff --git a/app/api/lotto/stats/performance/route.ts b/app/api/lotto/stats/performance/route.ts index 20696d4..7d856f7 100644 --- a/app/api/lotto/stats/performance/route.ts +++ b/app/api/lotto/stats/performance/route.ts @@ -2,6 +2,8 @@ import { NextResponse } from 'next/server'; import { createClient } from '@/lib/supabase/server'; import { nasGet, handleNasError } from '../../_nas'; +export const maxDuration = 60; + export async function GET() { try { const supabase = await createClient(); diff --git a/app/services/lotto/recommend/PatternTab.tsx b/app/services/lotto/recommend/PatternTab.tsx index fc3a329..97c5864 100644 --- a/app/services/lotto/recommend/PatternTab.tsx +++ b/app/services/lotto/recommend/PatternTab.tsx @@ -65,7 +65,10 @@ export default function PatternTab() { useEffect(() => { fetch('/api/lotto/analysis/personal').then(r => r.json()) - .then(setData) + .then(d => { + if (d?.error) { setError(d.error === 'NAS_TIMEOUT' ? 'NAS 서버 응답 시간 초과.' : '패턴 분석을 불러오지 못했습니다.'); return; } + setData(d); + }) .catch(() => setError('패턴 분석을 불러오지 못했습니다.')) .finally(() => setLoading(false)); }, []); diff --git a/app/services/lotto/recommend/PurchaseTab.tsx b/app/services/lotto/recommend/PurchaseTab.tsx index 3af6b37..7589fb8 100644 --- a/app/services/lotto/recommend/PurchaseTab.tsx +++ b/app/services/lotto/recommend/PurchaseTab.tsx @@ -49,9 +49,10 @@ export default function PurchaseTab() { fetch('/api/lotto/purchase').then(r => r.json()), fetch('/api/lotto/purchase/stats').then(r => r.json()), ]); + if (recRes?.error || statRes?.error) throw new Error(recRes?.error ?? statRes?.error); setRecords(recRes.records ?? []); setStats(statRes); - } finally { setLoading(false); } + } catch { /* 에러 시 빈 상태 유지 */ } finally { setLoading(false); } }; useEffect(() => { load(); }, []); diff --git a/app/services/lotto/recommend/ReportTab.tsx b/app/services/lotto/recommend/ReportTab.tsx index 23e639e..18cabd7 100644 --- a/app/services/lotto/recommend/ReportTab.tsx +++ b/app/services/lotto/recommend/ReportTab.tsx @@ -78,8 +78,14 @@ export default function ReportTab() { fetch('/api/lotto/report/latest').then(r => r.json()), fetch('/api/lotto/report/history?limit=10').then(r => r.json()), ]).then(([rep, hist]) => { + if (rep?.error) { + setError(rep.error === 'NAS_TIMEOUT' + ? 'NAS 서버 응답 시간 초과. 잠시 후 다시 시도해주세요.' + : '리포트를 불러오지 못했습니다. (' + rep.error + ')'); + return; + } setReport(rep); - setHistory(hist.reports ?? []); + setHistory(hist?.reports ?? []); }).catch(() => setError('리포트를 불러오지 못했습니다.')) .finally(() => setLoading(false)); }, []); @@ -101,7 +107,7 @@ export default function ReportTab() {
{error}
); - if (!report) return null; + if (!report || !report.confidence_factors || !report.recommended_sets) return null; const strategyColors = ['#fbbf24', '#60a5fa', '#a78bfa'];