7️날씨 데이터 조회하기

데이터 페칭에서 다뤘던 fetch 함수를 이용해서 API를 호출해보겠습니다.

데이터 페칭

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

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_ 로 시작하지 않는 변수는 브라우저에서 찾질 못한다는 점에 유의하세요. (참고)

NEXT_PUBLIC_API_KEY=3d9284ea92f14f078ca75a17232817

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

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 호출을 해보면 에러가 발생하는 경우가 있을겁니다. 네트워크 상황에 따라 페이지가 늦게 뜨기도 하죠. 이런 상황에는 에러, 로딩 뷰를 준비해주면 좋습니다. 앞서 라우팅에서 배웠던 것처럼 상황에 맞게 파일 이름을 정해서 두 가지 경우에 대응할 수 있습니다. 여기서는 error.tsx, loading.tsx 파일이 필요하겠네요.

app/error.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>
  )
}

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

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

데이터 재검증하기

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

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

라우트 핸들러

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

app/api/revalidate/route.ts
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 })
}

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

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는 캐시 비우기 버튼을 만들어 사용할 수 있겠네요.

'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

Last updated