# 날씨 데이터 조회하기

[데이터 페칭](https://book.hajoeun.dev/friendly-next-js/next.js-1/undefined-4)에서 다뤘던 fetch 함수를 이용해서 API를 호출해보겠습니다.

### 데이터 페칭

데이터 페칭에 필요한 API 경로는 앞 장에서 API Explorer에서 확인할 수 있습니다. 그대로 복사해서 가져옵니다. 현재 날씨를 조회하는 함수(`getCurrentWeather`)는 아래와 같은 형태로 만들 수 있습니다. 응답 값에 대한 정의는 앞 장에서 정의한 타입 `CurrentWeatherResponse`를 사용합니다.

```typescript
export const getCurrentWeather = async (): Promise<CurrentWeatherResponse> => {
  const apiPath = `http://api.weatherapi.com/v1/current.json?key={API_KEY}`
  const res = await fetch(apiPath)
  
  if (!res.ok) throw new Error('날씨 정보를 가져올 수 없습니다.')

  return res.json()
}
```

같은 방식으로 예보 데이터를 조회하는 함수도 만들어보세요. 함수 이름은 `getForecast` 정도로 하면 되겠네요. 마찬가지로 앞 장에서 정의했던 `ForecastResponse`를 사용하시면 됩니다.

### 환경 변수 다루기

위의 코드는 일종의 의사코드이기 때문에 그대로 붙여넣으면 동작하지 않습니다. 특히 `API_KEY`라고 적어둔 부분은 직접 채워넣지 않으면 응답 값을 받아낼 수 없습니다. `API_KEY`는 Weather API의 대시보드에 있던 API Key를 가져와 채워넣으면 됩니다.

만약 `{API_KEY}` 자리에 그대로 API Key 값을 옮겨넣는다면 코드를 공개하면 모두가 여러분의 API Key를 사용할 수 있게 되겠죠. 그럼 금방 제한된 호출 횟수를 잃어버릴겁니다. 이 같은 상황을 막기 위해 환경 변수로 API Key와 같은 값을 주입 받도록 만들 수 있습니다.

프로젝트 최상단에 `.env.local` 이라는 파일을 만들고 `NEXT_PUBLIC_`으로 시작하는 변수를 정의합니다. 저희는 `NEXT_PUBLIC_API_KEY` 라고 정의하면 되겠네요. 이때 `NEXT_PUBLIC_` 로 시작하지 않는 변수는 브라우저에서 찾질 못한다는 점에 유의하세요. ([참고](https://nextjs.org/docs/app/building-your-application/configuring/environment-variables#bundling-environment-variables-for-the-browser))

```bash
NEXT_PUBLIC_API_KEY=3d9284ea92f14f078ca75a17232817
```

`.env.local` 파일에 정의한 변수는 `process.env`라는 객체를 통해 조회할 수 있습니다. 덕분에 코드에는 직접적으로 API Key가 드러나지 않을 수 있죠.

```tsx
const API_KEY = process.env.NEXT_PUBLIC_API_KEY
const apiPath = `http://api.weatherapi.com/v1/current.json?key=${API_KEY}`
const res = await fetch(apiPath)
```

### 에러와 로딩 대응하기

API 호출을 해보면 에러가 발생하는 경우가 있을겁니다. 네트워크 상황에 따라 페이지가 늦게 뜨기도 하죠. 이런 상황에는 에러, 로딩 뷰를 준비해주면 좋습니다. 앞서 [라우팅](https://book.hajoeun.dev/friendly-next-js/next.js-1/undefined-1#undefined)에서 배웠던 것처럼 상황에 맞게 파일 이름을 정해서 두 가지 경우에 대응할 수 있습니다. 여기서는 `error.tsx`, `loading.tsx` 파일이 필요하겠네요.

{% code title="app/error.tsx" %}

```tsx
'use client' // 에러 컴포넌트는 클라이언트 컴포넌트로 정의되어야 합니다.

import { useEffect } from 'react'

export default function Error({
  error,
  reset,
}: {
  error: Error
  reset: () => void
}) {
  useEffect(() => {
    // 에러 로그를 남기면 디버깅에 도움이 되겠죠.
    console.error(error)
  }, [error])

  return (
    <div>
      <h2></h2>
      <button onClick={() => reset()}>
        재시도
      </button>
    </div>
  )
}
```

{% endcode %}

로딩 페이지는 이렇게 만듭니다.

{% code title="app/loading.tsx" %}

```tsx
export default function Loading() {
  return <h2>로딩 중</h2>
}
```

{% endcode %}

### 데이터 재검증하기

Next.js에서는 자동으로 데이터를 캐싱합니다. 캐싱된 데이터가 아닌 실제 데이터를 불러오도록 하기 위해 재검증을 해줘야 합니다. `revalidateTag`를 사용하는 방법을 살펴보겠습니다.&#x20;

`revalidateTag`는 서버 사이드에서 호출 가능합니다. 하지만 캐시를 풀어달라는 요청은 클라이언트(브라우저)에서 만들어집니다. 때문에 API 형태로 `revalidateTag`를 호출할 수 있도록 만들어야 합니다. Next.js에서의 API는 라우트 핸들러를 통해 만들 수 있습니다.

#### 라우트 핸들러

홈과 상세를 `page` 파일을 만들어 생성했던 것처럼 API는 `route` 파일을 만들어 생성할 수 있습니다. 아래와 같은 형식으로 재검증 API를 만들 수 있습니다.

{% code title="app/api/revalidate/route.ts" %}

```typescript
import { revalidateTag } from 'next/cache'
import { NextRequest, NextResponse } from 'next/server'

export async function POST(req: NextRequest) {
  const tag = req.nextUrl.searchParams.get('tag')
  if (!tag) throw new Error('태그는 필수 값입니다')

  revalidateTag(tag)
  return NextResponse.json({ message: '재검증에 성공했습니다', tag })
}
```

{% endcode %}

재검증 API는 다시 아래와 같은 함수를 만들어 사용할 수 있습니다.

```typescript
export const revalidateByTag = async (tag: string) => {
  const baseUrl = location.origin
  const res = await fetch(`${baseUrl}/api/revalidate?tag=${tag}`, {
    method: 'POST',
    cache: 'no-cache',
  })
  if (!res.ok) throw new Error('재검증에 실패했습니다')

  return res.json()
}
```

`revalidateByTag`는 캐시 비우기 버튼을 만들어 사용할 수 있겠네요.

```tsx
'use client'

import { revalidateByTag } from '@/api/revalidate-by-tag'

const RevalidateButton = () => {
  const handleClick = async () => {
    await revalidateByTag('tag')
  }

  return <button onClick={handleClick}>캐시 비우기</button>
}

export default RevalidateButton
```
