외부 API와의 통합
외부 날씨 API를 서버 Route Handler로 감싸 비밀 API 키를 서버에서 관리하고 브라우저에 필요한 응답만 전달합니다.
대부분의 현대 웹 애플리케이션은 독립적으로 동작하기보다, 다양한 외부 API(Application Programming Interface)와 연동해 데이터를 가져오거나 기능을 활용합니다.
예를 들어 날씨 정보를 위해 기상청 API를 쓰거나, 결제를 위해 PG(Payment Gateway) API를 호출할 수 있습니다.
Next.js의 Route Handler는 이러한 외부 API와의 통합을 위한 서버 환경을 제공합니다.
클라이언트(브라우저)에서 직접 외부 API를 호출하는 대신, Next.js Route Handler를 경유하면 여러 가지 중요한 이점을 얻을 수 있습니다.
이 절에서는 Next.js Route Handler로 외부 API를 통합하는 흐름과, 이 방식의 이점 및 주의사항을 함께 정리합니다.
왜 Route Handler를 통해 외부 API를 호출하나요?
클라이언트(브라우저)에서 JavaScript를 사용하여 직접 외부 API를 호출할 수도 있지만, Next.js Route Handler를 경유하면 비밀값과 응답 계약을 서버에서 통제할 수 있습니다.
보안 (비밀 API 키 숨김): 이 실습의 OpenWeather 키처럼 서버에서 관리할 비밀 키는 클라이언트 코드에 노출하지 않습니다.
Route Handler에서 외부 API를 호출하면, API 키를 서버 환경 변수(.env.local)에 저장하고 서버 측에서만 접근할 수 있어 브라우저 번들 노출을 막을 수 있습니다.
CORS(Cross-Origin Resource Sharing) 문제 회피: 브라우저의 보안 정책(동일 출처 정책, Same-Origin Policy) 때문에 다른 도메인의 API를 직접 호출하는 데 제약이 있습니다.
Route Handler의 서버 측 외부 요청은 브라우저의 CORS 제약을 받지 않습니다.
Route Handler가 외부 API로부터 데이터를 가져온 후, 클라이언트는 동일 출처(Same-Origin)인 Next.js Route Handler에만 요청을 보내면 됩니다.
데이터 가공 및 전처리: 외부 API에서 반환되는 데이터 형식이 클라이언트에서 원하는 형식과 다를 수 있습니다.
Route Handler에서 데이터를 가져온 후 필요한 형태로 가공하거나 필터링하여 클라이언트에게 최적화된 데이터를 제공할 수 있습니다.
성능 최적화: 여러 외부 API를 호출하여 데이터를 조합해야 하는 경우, 클라이언트에서 각각 호출하는 것보다 Route Handler에서 한 번에 처리하여 클라이언트-서버 간의 네트워크 왕복(Round Trip) 횟수를 줄일 수 있습니다.
에러 처리 및 로깅: 외부 API 호출 시 발생할 수 있는 네트워크 오류나 API 에러를 Route Handler에서 중앙 집중적으로 처리하고 로깅할 수 있습니다.
속도 제한(Rate Limiting) 관리: 일부 외부 API는 호출 횟수에 제한을 둡니다.
Route Handler에서 호출을 관리하면 이러한 속도 제한을 더 효율적으로 제어하고 재시도 로직 등을 구현할 수 있습니다.
외부 API 호출을 위한 기본 도구: fetch API
Node.js 환경의 Next.js Route Handler에서는 웹 표준 fetch API 또는 axios와 같은 라이브러리를 사용하여 외부 API를 호출할 수 있습니다.
여기서는 기본 제공되는 fetch API를 사용한 예시를 살펴보겠습니다.
fetch API의 기본 사용법은 다음과 같습니다.
// fetch API 기본 사용법
async function fetchData(url: string) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json(); // JSON 응답 파싱
// const textData = await response.text(); // 텍스트 응답 파싱
return data;
} catch (error) {
console.error('Fetch error:', error);
throw error;
}
}실습: 날씨 정보를 가져오는 Route Handler 생성
OpenWeatherMap API를 사용하여 특정 도시의 현재 날씨 정보를 가져오는 Next.js Route Handler를 만들어 보겠습니다.
사전 준비- OpenWeatherMap 웹사이트(https://openweathermap.org/)에 가입합니다.
- 로그인 후 API keys 섹션에서 키를 생성하고, 해당 키의 Current Weather 사용 권한과 현재 요금제·호출 한도를 확인합니다.
- 발급받은 API 키를 복사해 둡니다.
환경 변수 설정:
프로젝트 루트에 .env.local 파일을 생성하고 발급받은 OpenWeatherMap API 키를 추가합니다.
# .env.local
OPENWEATHER_API_KEY=YOUR_OPENWEATHER_API_KEY_HERE주의: YOUR_OPENWEATHER_API_KEY_HERE 부분을 실제 발급받은 키로 대체하세요.
이 예제는 도시명 q를 받는 Current Weather 호출을 읽는 실습입니다. OpenWeather 공식 문서는 내장 도시명 조회를 아직 제공하지만 사용 중단 권고 기능으로 분류하고 업데이트하지 않는다고 설명합니다. 새로운 연동에서는 별도 Geocoding API로 좌표를 얻는 방식도 검토합니다.
src/app/api/weather/route.ts)
import { NextRequest, NextResponse } from 'next/server';
const OPENWEATHER_API_KEY = process.env.OPENWEATHER_API_KEY;
const BASE_URL = 'https://api.openweathermap.org/data/2.5/weather';
export async function GET(request: NextRequest) {
// 1. 쿼리 파라미터에서 도시 이름 가져오기
const searchParams = request.nextUrl.searchParams;
const city = searchParams.get('city')?.trim();
// 2. 필수 파라미터 검증
if (!city) {
return NextResponse.json(
{ message: '도시 이름(city) 쿼리 파라미터가 필요합니다.' },
{ status: 400 } // Bad Request
);
}
// 3. API 키 유효성 검사 (서버 환경 변수 확인)
if (!OPENWEATHER_API_KEY) {
console.error('OpenWeatherMap API Key is not set in environment variables.');
return NextResponse.json(
{ message: '서버 설정 오류: API 키가 누락되었습니다.' },
{ status: 500 } // Internal Server Error
);
}
try {
// 4. OpenWeatherMap 외부 API 호출 URL 생성 (단위: 섭씨, 언어: 한국어)
const apiUrl = new URL(BASE_URL);
apiUrl.search = new URLSearchParams({
q: city,
appid: OPENWEATHER_API_KEY,
units: 'metric',
lang: 'kr',
}).toString();
// 5. 외부 API 호출
const response = await fetch(apiUrl);
// 6. 외부 API 응답 처리
if (!response.ok) {
const notFound = response.status === 404;
console.error('OpenWeatherMap API Error:', { status: response.status });
// 제공자 인증·호출 한도 오류를 애플리케이션 사용자의 오류로 그대로 전달하지 않습니다.
return NextResponse.json(
{ message: notFound ? '도시를 찾을 수 없습니다.' : '날씨 제공 서비스의 응답을 처리할 수 없습니다.' },
{ status: notFound ? 404 : 502 }
);
}
const weatherData = await response.json();
// 7. 클라이언트에 필요한 형태로 데이터 가공 (선택 사항)
const processedData = {
city: weatherData.name,
country: weatherData.sys.country,
temperature: weatherData.main.temp,
feelsLike: weatherData.main.feels_like,
humidity: weatherData.main.humidity,
description: weatherData.weather[0].description,
icon: `https://openweathermap.org/img/wn/${weatherData.weather[0].icon}.png`,
};
// 8. 가공된 데이터 반환
return NextResponse.json(processedData, { status: 200 });
} catch (error) {
console.error('Route Handler에서 오류 발생:', {
name: error instanceof Error ? error.name : 'UnknownError',
});
return NextResponse.json(
{ message: '서버에서 날씨 정보를 가져오는 중 오류가 발생했습니다.' },
{ status: 500 }
);
}
}성공 응답은 제공자의 필드 구조를 가정해 필요한 값만 추립니다. 빈 도시명은 400, 키 누락은 500, 제공자의 도시 없음 응답은 404, 그 밖의 비정상 HTTP 응답은 502로 바꿉니다. 네트워크·파싱 예외는 현재 예제의 일반 오류 분기인 500으로 처리합니다.
예시
서버에 키가 없고 city도 공백뿐이면 첫 city 검사에서 400으로 끝나 외부 API를 호출하지 않습니다. city가 정상이어야 키 누락의 500까지 진행합니다. 키는 함수 내부가 아니라 모듈의 최상위 const에서 읽으므로 환경 값을 바꾼 뒤에는 서버 프로세스가 새 설정으로 모듈을 읽도록 다시 시작합니다. 이후 요청마다 process.env를 다시 읽는 코드로 생각하지 않습니다.
확인 문제
city와 키가 정상이라고 가정합니다. 제공자가 429를 반환하면 브라우저에 429가 그대로 전달되나요? 제공자의 HTTP 상태가 200이어도 JSON에 sys가 없다면 어떤 상태가 되나요? 정상 응답의 temperature 24를 화면에 표시할 때 단위는 무엇이고, 해당 날씨 JSON 경유와 공개 아이콘 이미지 경유는 같은가요?
제공자 상태와 앱 계약의 해설
429는 404가 아닌 비정상 응답이므로 이 예제는 502로 바꿉니다. HTTP 200 뒤 sys.country를 읽을 수 없으면 가공 중 예외가 발생하여 catch의 500으로 끝납니다. TypeScript WeatherData 표기만으로 필드 구조가 검증되는 것은 아닙니다. 요청의 units가 metric이므로 temperature는 섭씨이고 화면에는 24°C를 표시합니다. 날씨 JSON은 /api/weather를 거치지만 아이콘 img는 공개 제공자 URL을 브라우저가 직접 요청합니다. 모든 외부 요청을 서버가 대신하거나 현재 코드에 타임아웃·호출 제한이 구현되었다고 확대하지 않습니다.
날씨 조회의 서버 요청과 응답 경계
city와 서버 키가 정상인 원 GET의 공통 관찰 단계에서 fetch·HTTP·JSON·필드 조건별 종료를 비교합니다. 200·404·502·500을 같은 실행의 연속 응답으로 잇지 않고 서버 키와 공개 응답 경계를 유지합니다.
NEXT · 11.4 · 외부 API
날씨 API의 비밀 키는 서버 요청에만 더합니다
유효한 도시·서버 키·제공자 응답의 성공 경로입니다. 오른쪽 화살표는 요청, 반대쪽은 응답입니다. 서버가 city를 q로 전달하며 appid를 추가하고 JSON을 가공합니다.
빈 city는 400, 키 누락은 500입니다. 제공자 404는 404, 그 밖의 비정상 HTTP 응답은 502, 네트워크·파싱 예외는 500으로 처리합니다. 공개 아이콘 이미지는 브라우저가 제공자에서 직접 가져옵니다. 응답 스키마 검증·인증·속도 제한·재시도는 구현하지 않았습니다.
이 예의 조건
- 두 박스는 같은 실행의 진행 정보와 자료·결과를 관찰하는 고정 역할입니다. 박스의 좌우·위아래 위치를 별도 호출 순서로 해석하지 않습니다.
- 조건별 종료는 대안입니다. 실패 뒤 정상 후속 단계를 실행하거나 독립 비교 사례를 같은 요청으로 연결하지 않습니다.
- city와 서버 키가 정상인 원 GET의 try 블록이다. fetch 응답을 받으면 response.ok를 검사한다. HTTP 실패의 404는 404로, 그 밖은 502로 끝난다. HTTP 정상일 때만 JSON을 읽고 sys/main/weather 필드를 가공하여 200으로 응답한다. fetch/JSON 파싱/필드 접근 예외는 catch의 500으로 합류한다. HTTP 200이라고 유효한 스키마가 보장되는 것은 아니다. 아래 원 그림은 성공 요청/응답 방향과 서버 키 경계이며 이 제어 분기는 새로운 서버 보안 기능이나 재시도를 나타내지 않는다.
- 서버는 도시 이름에 서버의 appid를 더해 외부 API를 호출하고 성공 응답에서 필요한 필드를 골라 브라우저로 보낸다.
- 요청과 응답은 서로 다른 연결선으로 표시하며 비밀 키는 서버에서 외부 API로만 보낸다.
관계도의 보충 설명
| 순번 | 원문 표시 |
|---|---|
| 1 | 날씨 조회의 서버 요청과 응답 경계 |
| 6 | city와 서버 키가 정상인 원 GET의 try 블록이다. fetch 응답을 받으면 response.ok를 검사한다. HTTP 실패의 404는 404로, 그 밖은 502로 끝난다. HTTP 정상일 때만 JSON을 읽고 sys/main/weather 필드를 가공하여 200으로 응답한다. fetch/JSON 파싱/필드 접근 예외는 catch의 500으로 합류한다. HTTP 200이라고 유효한 스키마가 보장되는 것은 아니다. 아래 원 그림은 성공 요청/응답 방향과 서버 키 경계이며 이 제어 분기는 새로운 서버 보안 기능이나 재시도를 나타내지 않는다. |
| 장면 | 관찰 값 | 전환 이유 |
|---|---|---|
| provider 응답 대기 city와 모듈에서 읽은 서버 키가 정상인 GET이 q·appid·metric·kr로 외부 fetch를 실행합니다. | 공통 전제: city·서버 key 정상 · 서버 요청: q·appid·units metric·lang kr · 브라우저 키 공개: 없음 | 외부 Promise의 응답 또는 거부를 기다립니다. 빈 city 400과 키 누락 500은 이 선택 범위 이전의 독립 검사입니다. |
| HTTP 응답과 거부 판정 fetch 거부는 catch의 500으로 끝나고, 응답을 받으면 response.ok를 검사합니다. | fetch 거부: 500 종료 · HTTP 실패 404: 404 종료 · 다른 HTTP 실패: 502 종료 · HTTP 정상: JSON 읽기·필드 가공으로 진행 | 같은 fetch 결과 판정에서 실패 경로는 종료하고 정상 HTTP만 JSON 처리를 시작합니다. 종료한 실패가 다음 가공 명령을 실행하지 않습니다. |
| 정상 HTTP의 자료 판정 JSON을 읽고 sys·main·weather 필드에 접근할 수 있는지에 따라 공개 자료 또는 catch 오류가 정해집니다. | HTTP 정상·자료 정상: 공개 날씨 필드 준비 · JSON·필드 예외: 500 종료 · 앞선 실패: 404·502·fetch 거부 500은 이미 종료 · HTTP 200: 유효한 스키마 보장 아님 | 필드 가공까지 성공해야 processedData를 만들며 HTTP 성공만으로 200 공개 응답을 확정하지 않습니다. |
| 조건별 응답 완료 자료 가공 성공은 processedData의 200이며 각 실패는 해당 오류 응답으로 반환을 마칩니다. | 자료 가공 성공: 200·필요한 날씨 필드 · provider 도시 없음: 404 · 다른 provider HTTP 실패: 502 · fetch·JSON·필드 예외: 500 | 각 조건에서 하나의 응답으로 끝납니다. 성공 반환 뒤 예외 경로를 이어 실행하지 않으며 retry·스키마 검증은 원 예제에 없습니다. |
날씨 조회의 서버 요청과 응답 경계
city와 서버 키가 정상인 원 GET의 공통 관찰 단계에서 fetch·HTTP·JSON·필드 조건별 종료를 비교합니다. 200·404·502·500을 같은 실행의 연속 응답으로 잇지 않고 서버 키와 공개 응답 경계를 유지합니다.
NEXT · 11.4 · 외부 API
날씨 API의 비밀 키는 서버 요청에만 더합니다
유효한 도시·서버 키·제공자 응답의 성공 경로입니다. 오른쪽 화살표는 요청, 반대쪽은 응답입니다. 서버가 city를 q로 전달하며 appid를 추가하고 JSON을 가공합니다.
빈 city는 400, 키 누락은 500입니다. 제공자 404는 404, 그 밖의 비정상 HTTP 응답은 502, 네트워크·파싱 예외는 500으로 처리합니다. 공개 아이콘 이미지는 브라우저가 제공자에서 직접 가져옵니다. 응답 스키마 검증·인증·속도 제한·재시도는 구현하지 않았습니다.
이 예의 조건
- 두 박스는 같은 실행의 진행 정보와 자료·결과를 관찰하는 고정 역할입니다. 박스의 좌우·위아래 위치를 별도 호출 순서로 해석하지 않습니다.
- 조건별 종료는 대안입니다. 실패 뒤 정상 후속 단계를 실행하거나 독립 비교 사례를 같은 요청으로 연결하지 않습니다.
- city와 서버 키가 정상인 원 GET의 try 블록이다. fetch 응답을 받으면 response.ok를 검사한다. HTTP 실패의 404는 404로, 그 밖은 502로 끝난다. HTTP 정상일 때만 JSON을 읽고 sys/main/weather 필드를 가공하여 200으로 응답한다. fetch/JSON 파싱/필드 접근 예외는 catch의 500으로 합류한다. HTTP 200이라고 유효한 스키마가 보장되는 것은 아니다. 아래 원 그림은 성공 요청/응답 방향과 서버 키 경계이며 이 제어 분기는 새로운 서버 보안 기능이나 재시도를 나타내지 않는다.
- 서버는 도시 이름에 서버의 appid를 더해 외부 API를 호출하고 성공 응답에서 필요한 필드를 골라 브라우저로 보낸다.
- 요청과 응답은 서로 다른 연결선으로 표시하며 비밀 키는 서버에서 외부 API로만 보낸다.
관계도의 보충 설명
| 순번 | 원문 표시 |
|---|---|
| 1 | 날씨 조회의 서버 요청과 응답 경계 |
| 6 | city와 서버 키가 정상인 원 GET의 try 블록이다. fetch 응답을 받으면 response.ok를 검사한다. HTTP 실패의 404는 404로, 그 밖은 502로 끝난다. HTTP 정상일 때만 JSON을 읽고 sys/main/weather 필드를 가공하여 200으로 응답한다. fetch/JSON 파싱/필드 접근 예외는 catch의 500으로 합류한다. HTTP 200이라고 유효한 스키마가 보장되는 것은 아니다. 아래 원 그림은 성공 요청/응답 방향과 서버 키 경계이며 이 제어 분기는 새로운 서버 보안 기능이나 재시도를 나타내지 않는다. |
| 장면 | 관찰 값 | 전환 이유 |
|---|---|---|
| provider 응답 대기 city와 모듈에서 읽은 서버 키가 정상인 GET이 q·appid·metric·kr로 외부 fetch를 실행합니다. | 공통 전제: city·서버 key 정상 · 서버 요청: q·appid·units metric·lang kr · 브라우저 키 공개: 없음 | 외부 Promise의 응답 또는 거부를 기다립니다. 빈 city 400과 키 누락 500은 이 선택 범위 이전의 독립 검사입니다. |
| HTTP 응답과 거부 판정 fetch 거부는 catch의 500으로 끝나고, 응답을 받으면 response.ok를 검사합니다. | fetch 거부: 500 종료 · HTTP 실패 404: 404 종료 · 다른 HTTP 실패: 502 종료 · HTTP 정상: JSON 읽기·필드 가공으로 진행 | 같은 fetch 결과 판정에서 실패 경로는 종료하고 정상 HTTP만 JSON 처리를 시작합니다. 종료한 실패가 다음 가공 명령을 실행하지 않습니다. |
| 정상 HTTP의 자료 판정 JSON을 읽고 sys·main·weather 필드에 접근할 수 있는지에 따라 공개 자료 또는 catch 오류가 정해집니다. | HTTP 정상·자료 정상: 공개 날씨 필드 준비 · JSON·필드 예외: 500 종료 · 앞선 실패: 404·502·fetch 거부 500은 이미 종료 · HTTP 200: 유효한 스키마 보장 아님 | 필드 가공까지 성공해야 processedData를 만들며 HTTP 성공만으로 200 공개 응답을 확정하지 않습니다. |
| 조건별 응답 완료 자료 가공 성공은 processedData의 200이며 각 실패는 해당 오류 응답으로 반환을 마칩니다. | 자료 가공 성공: 200·필요한 날씨 필드 · provider 도시 없음: 404 · 다른 provider HTTP 실패: 502 · fetch·JSON·필드 예외: 500 | 각 조건에서 하나의 응답으로 끝납니다. 성공 반환 뒤 예외 경로를 이어 실행하지 않으며 retry·스키마 검증은 원 예제에 없습니다. |
테스트 방법: 아래는 유효한 키·제공자 응답을 전제로 확인할 분기이며, 이 문서에서 새로 측정한 결과가 아닙니다.
개발 서버(npm run dev)를 실행합니다.
웹 브라우저나 Postman/Insomnia에서 다음 URL로 접속하여 테스트합니다.
- 성공 케이스:
http://localhost:3000/api/weather?city=Seoul - 실패 케이스 (도시 이름 누락):
http://localhost:3000/api/weather - 실패 케이스 (유효하지 않은 도시 이름):
http://localhost:3000/api/weather?city=InvalidCityName123
클라이언트 컴포넌트에서 Route Handler 호출
이제 클라이언트 컴포넌트에서 위에서 만든 Route Handler를 호출하여 날씨 정보를 화면에 표시해 보겠습니다.
src/app/weather/page.tsx (클라이언트 컴포넌트 예시)
"use client";
import { useState } from 'react';
interface WeatherData {
city: string;
country: string;
temperature: number;
feelsLike: number;
humidity: number;
description: string;
icon: string;
}
export default function WeatherPage() {
const [city, setCity] = useState('');
const [weather, setWeather] = useState<WeatherData | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const fetchWeather = async () => {
if (!city.trim()) {
setError('도시 이름을 입력해주세요.');
return;
}
setLoading(true);
setError(null);
setWeather(null); // 이전 날씨 정보 초기화
try {
// Next.js Route Handler를 호출합니다.
const query = new URLSearchParams({ city: city.trim() });
const response = await fetch(`/api/weather?${query}`);
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.message || '날씨 정보를 가져오는 데 실패했습니다.');
}
const data: WeatherData = await response.json();
setWeather(data);
} catch (err) {
setError((err as Error).message);
console.error('클라이언트에서 날씨 API 호출 중 오류:', err);
} finally {
setLoading(false);
}
};
return (
<div style={{ padding: '20px', maxWidth: '600px', margin: '20px auto', border: '1px solid #007bff', borderRadius: '10px', boxShadow: '0 4px 8px rgba(0,0,0,0.1)', textAlign: 'center' }}>
<h1 style={{ color: '#007bff', marginBottom: '20px' }}>날씨 정보 가져오기</h1>
<div style={{ marginBottom: '20px' }}>
<input
type="text"
aria-label="도시 이름"
placeholder="도시 이름을 입력하세요 (예: Seoul)"
value={city}
onChange={(e) => setCity(e.target.value)}
style={{ padding: '10px', marginRight: '10px', border: '1px solid #ccc', borderRadius: '5px', width: '70%' }}
/>
<button
onClick={fetchWeather}
disabled={loading}
style={{ padding: '10px 15px', backgroundColor: '#28a745', color: 'white', border: 'none', borderRadius: '5px', cursor: 'pointer', opacity: loading ? 0.7 : 1 }}
>
{loading ? '로딩 중...' : '날씨 가져오기'}
</button>
</div>
{error && (
<p style={{ color: '#dc3545', fontWeight: 'bold', marginBottom: '15px' }}>오류: {error}</p>
)}
{weather && (
<div style={{ border: '1px solid #eee', borderRadius: '8px', padding: '20px', backgroundColor: '#f9f9f9' }}>
<h2 style={{ color: '#333', marginBottom: '10px' }}>{weather.city}, {weather.country}</h2>
<img src={weather.icon} alt={weather.description} style={{ width: '80px', height: '80px' }} />
<p style={{ fontSize: '1.2em', margin: '10px 0' }}>온도: {weather.temperature}°C (체감: {weather.feelsLike}°C)</p>
<p>습도: {weather.humidity}%</p>
<p>날씨: {weather.description}</p>
</div>
)}
</div>
);
}- 클라이언트에서는 Next.js Route Handler의 상대 경로(
/api/weather?city=Seoul)를 사용합니다. 이는 개발 환경(localhost)과 배포 환경에서 URL이 자동으로 맞춰지도록 합니다. - 에러 처리 및 로딩 상태 관리를 통해 사용자에게 더 나은 경험을 제공합니다.
WeatherData 타입 표기는 응답의 런타임 검증을 수행하지 않습니다. 서버와 클라이언트 모두 제공자 응답의 필드 구조를 가정하며, 실제 연동에서 필요한 스키마 검증은 별도로 정합니다. 날씨 JSON은 Route Handler를 거치지만 공개 날씨 아이콘 이미지는 <img>가 제공자에서 직접 가져옵니다.
고려 사항
- API 키 관리: 개발 환경(
.env.local)에서는process.env.VAR_NAME으로 접근하고, 배포 환경(Vercel, AWS 등)에서는 해당 플랫폼의 환경 변수 설정 기능을 사용하여 API 키를 안전하게 관리해야 합니다. - 보안: 외부 API 호출 시 민감한 데이터를 전송해야 한다면, HTTPS를 사용하는지, 추가적인 인증(OAuth 등)이 필요한지 확인해야 합니다.
- 속도 제한 및 캐싱: 자주 호출되는 외부 API의 경우, Route Handler 내에서 응답을 캐싱하여 외부 API 호출 횟수를 줄이고 성능을 향상시킬 수 있습니다.
- 오류 처리 및 재시도: 외부 API 호출이 실패할 경우를 대비하여 견고한 오류 처리 로직과 필요한 경우 재시도(Retry) 메커니즘을 구현해야 합니다.
- 환경별 설정: 개발, 스테이징, 프로덕션 환경마다 다른 외부 API 엔드포인트나 키를 사용해야 할 경우, 환경 변수를 통해 유연하게 관리해야 합니다.
현재 코드는 응답 가공과 기본 오류 처리를 보여 줍니다. 사용자 인증, 호출 횟수 제한, 명시적 캐시 정책, 타임아웃·재시도는 구현하지 않았으므로 Route Handler를 경유한다는 사실만으로 이러한 기능이 생기지는 않습니다.