ํฐ์คํ ๋ฆฌ ๋ทฐ
[์ฌ๋๋ณ_Day9] ๊ณตํด์ผ ๋ฌ๋ ฅ์ ์ ์ฉํ๊ธฐ (feat.๊ณต๊ณต๋ฐ์ดํฐ)
์ฑ._. 2025. 4. 9. 23:12๋ฌ๋ ฅ์ด๋ผ๋ฉด ํ์ํ ๊ณตํด์ผ !
๊ณต๊ณต๋ฐ์ดํฐ API ๋ฐ๊ธ
๊ณตํด์ผ ์ ๋ณด๋ฅผ ์ป์ด์ค๊ธฐ ์ํด์ ๊ณต๊ณต๋ฐ์ดํฐ๋ฅผ ์ด์ฉํ์๋ค.

ํ์๊ฐ์ ๊ณผ ๋ก๊ทธ์ธ์ ํ๊ณ ํ์ฉ์ ์ฒญ์ ํด๋ฆญํ๋ค.
![]() |
![]() |
์ ์ฒญ์ด ์๋ฃ๋๋ฉด ์ผ๋ฐ ์ธ์ฆํค ๋ ๊ฐ๊ฐ ๋ฐ๊ธ๋๋๋ฐ, Encoding ํค๋ฅผ ์ด์ฉํ์๋ค.
API ์ ์ฉ
๋ผ์ฐํธ ํธ๋ค๋ฌ
.env.local ํ์ผ์ ํค ๊ฐ์ ๋ ๋ ํ ๊ฐ์ง ์ ์ํ ์ ์ด ์๋ค.
supabase์ ์ด๋ ผํค ๊ฐ์ ๊ฒฝ์ฐ๋ ๋ธ๋ผ์ฐ์ ์ ๋ ธ์ถ๋์ด๋ ํฌ๊ฒ ๋ฌธ์ ๊ฐ ์ ๋๊ธฐ ๋๋ฌธ์ 'NEXT_PUBLIC'์ ๋ถ์ฌ์ ์ฌ์ฉํ๋ค.
ํ์ง๋ง ๊ณต๊ณต๋ฐ์ดํฐ ๊ฐ์ ๊ฒฝ์ฐ๋ ํ์๊ฐ์ ํ ๋ฐ๊ธ๋ฐ์๊ธฐ ๋๋ฌธ์ ๋ธ๋ผ์ฐ์ ์ ๋ ธ์ถ์ด ์กฐ๊ธ ๊บผ๋ ค์ก๋ค.
๋ฐ๋ผ์ 'NEXT_PUBLIC'์ ์ฌ์ฉํ์ง ์์๋ค.
const ENV = {
SUPABASE_URL: process.env.NEXT_PUBLIC_SUPABASE_URL || '',
SUPABASE_ANON_KEY: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || '',
HOLIDAY_KEY: process.env.HOLIDAY_KEY || '',
};
export default ENV;
๊ทธ๋ ๋ค๋ฉด ์ด ํค๋ฅผ ์ฌ์ฉํด์ ์ด๋ป๊ฒ ๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฌ์์ผ ํ ๊น?
์ฒ์์๋ ๊ธฐ์กด ๋ฐฉ์์ ๋ฐ๋ผ service.ts ํ์ผ์์ fetch๋ฅผ ํตํด ๋ค์๊ณผ ๊ฐ์ด ๊ฐ์ ธ์์๋ค.
// ๊ณต๊ณต๋ฐ์ดํฐ ๊ณตํด์ผ API
export const getHolidays = async (year: string) => {
const BASE_URL = 'http://apis.data.go.kr/B090041/openapi/service/SpcdeInfoService';
const res = await fetch(
//ํ์ฌ ๋
๋ ๊ฐ๋ง
`${BASE_URL}/getRestDeInfo?ServiceKey=${ENV.HOLIDAY_KEY}&solYear=${year}&_type=json&numOfRows=100`
);
const data = await res.json();
return data;
};
ํ์ง๋ง 'NEXT_PUBLIC'๊ฐ ์๊ธฐ ๋๋ฌธ์ ๋ธ๋ผ์ฐ์ ์์์ ์ฌ์ฉ์ด ๋ถ๊ฐ๋ฅํ๋ค.
๋ฐ๋ผ์ ์๊ฐํ ๊ฒ์ด ๋ผ์ฐํธ ํธ๋ค๋ฌ!
์๋ฒ-์๋ฒ์ ํต์ ์ ํตํด ๋ฐ์ดํฐ์ ์๋ต ๊ฐ์ ๋ผ์ฐํธ ๊ฒฝ๋ก์ ์ฃผ๊ธฐ ๋๋ฌธ์
์๋ฒ์์ ํค ๊ฐ ์ฌ์ฉํ์ฌ ๋ฐ์ดํฐ ํ์นญ์ด ๊ฐ๋ฅํ๋ฉฐ ๊ทธ ๋ฐ์ดํฐ๋ฅผ ๋ธ๋ผ์ฐ์ ์์๋ ์ฌ์ฉ์ด ๊ฐ๋ฅํ๋ค.
๐๊ณตํด์ผ API ๋ผ์ฐํธ ํธ๋ค๋ฌ
// app > api > holiday > route.ts
import { NextResponse } from 'next/server';
// ๊ณตํด์ผ ๋ผ์ฐํธ ํธ๋ค๋ฌ
export async function GET(req: Request) {
const { searchParams } = new URL(req.url);
const year = searchParams.get('year') || new Date().getFullYear().toString();
const BASE_URL = 'http://apis.data.go.kr/B090041/openapi/service/SpcdeInfoService';
const HOLIDAY_KEY = process.env.HOLIDAY_KEY;
const url = `${BASE_URL}/getRestDeInfo?ServiceKey=${HOLIDAY_KEY}&solYear=${year}&_type=json&numOfRows=100`;
const res = await fetch(url);
const data = await res.json();
return NextResponse.json(data);
}
๋ฐ์ดํฐ ๊ฐ๊ณต
๋ผ์ฐํธํธ๋ค๋ฌ๋ฅผ ํตํด ๋ฐ์์จ ์๋ต๊ฐ์ fetch๋ก ๋ฐ์์จ๋ค.
export const useGetHolidays = (calendarYear: string) => {
// ๊ณตํด์ผ ๋ฐ์ดํฐ ์ ์ฒด ๊ฐ์ ธ์ค๊ธฐ
const fetchHolidays = async () => {
const res = await fetch(`/api/holidays?year=${calendarYear}`); // ๋
๋์ ๋ง๋ ๋ฐ์ดํฐ
const data = await res.json();
const items = data.response?.body?.items?.item || [];
...

์ด ๊ฐ์ ๋ฌ๋ ฅ์ ๋ฐ๋ก ์ ์ฉํ ์ ์์ผ๋ฏ๋ก ์๋ง๊ฒ ๊ฐ๊ณตํด์ผ ํ๋ค.
1. ํ์ํ ๊ฐ๋ง ์ถ์ถ
- ์ด๊ธฐ ๋ฐ์ดํฐ์์ ํ์ํ ๋ ์ง์ ๊ณตํด์ผ๋ช ์ ๋ฝ์๋ธ๋ค.
// ํ์ํ ๊ฐ๋ง ์ถ์ถ
const holidays = items.map((item: Item) => ({
date: item.locdate, // 20250408 ํ์
name: item.dateName,
}));

2. ๋ฌ๋ ฅ์ ์ ์ฉํ ์ ์๋๋ก ๊ฐ๊ณต
- .slice ๋ฉ์๋๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํด ์ซ์์๋ date๋ฅผ ๋ฌธ์์ด๋ก ๋ณํํ์ฌ dateStr ๋ณ์์ ๋ด์์ค๋ค.
- '20250101' ํํ์ date๋ฅผ ๋
, ์, ์ผ๋ก ๋๋์ด new Date ๊ฐ์ฒด๋ก ๋ณํํ๋ค.
- ์ด๋, ์์ 0๋ถํฐ ์์ํ๋ ์ธ๋ฑ์ค์ด๊ธฐ ๋๋ฌธ์ '-1' ์ฒ๋ฆฌ๋ฅผ ํด์ค๋ค.
// ์บ๋ฆฐ๋์ ์ ์ฉํ ์ ์๋๋ก ๊ฐ๊ณต
const formattedHolidays = holidays.map((item: Holidays) => {
const dateStr = item.date.toString(); // number → string
return {
title: item.name,
date: new Date(
parseInt(dateStr.slice(0, 4), 10), // year
parseInt(dateStr.slice(4, 6), 10) - 1, // month (0-indexed)
parseInt(dateStr.slice(6, 8), 10) // day
),
};
});
return formattedHolidays;
};

3. useQuery ํ ์ ๋ฐํ
- ์ด ๋ชจ๋ ๊ธฐ๋ฅ์ ํจ์๋ฅผ queryFn๋ก ๊ฐ๋ useQuery๋ฅผ ์์ฑํ๋ค.
- ๊ณตํด์ผ์ ์ ๋ณด๋ฅผ ๋ ๋ง๋ค ์บ์ฑํ๊ธฐ ์ํด ์ฟผ๋ฆฌ ํค๋ฅผ ํด๋น ๋ ๋์ ๋ฐ๋ผ ๋์ ์ผ๋ก ๋ณ๋ํ๋ค.
return useQuery({
queryKey: [QUERY_KEY.HOLIDAYS, calendarYear], // ๋
๋์ ๋ฐ๋ผ ์ฟผ๋ฆฌํค ๋ณ๋
queryFn: fetchHolidays,
staleTime: 1000 * 60 * 60 * 24, // 1์ผ
});
๐useQuery ์ปค์คํ ํ ์ ์ฒด ๋ก์ง
import { QUERY_KEY } from '@/constants/queryKey';
import { Holidays, Item } from '@/types/plans';
import { useQuery } from '@tanstack/react-query';
export const useGetHolidays = (calendarYear: string) => {
// ๊ณตํด์ผ ๋ฐ์ดํฐ ์ ์ฒด ๊ฐ์ ธ์ค๊ธฐ
const fetchHolidays = async () => {
const res = await fetch(`/api/holidays?year=${calendarYear}`); // ๋
๋์ ๋ง๋ ๋ฐ์ดํฐ
const data = await res.json();
const items = data.response?.body?.items?.item || [];
// ํ์ํ ๊ฐ๋ง ์ถ์ถ
const holidays = items.map((item: Item) => ({
date: item.locdate, // 20250408 ํ์
name: item.dateName,
}));
// ์บ๋ฆฐ๋์ ์ ์ฉํ ์ ์๋๋ก ๊ฐ๊ณต
const formattedHolidays = holidays.map((item: Holidays) => {
const dateStr = item.date.toString(); // number → string
return {
title: item.name,
date: new Date(
parseInt(dateStr.slice(0, 4), 10), // year
parseInt(dateStr.slice(4, 6), 10) - 1, // month (0-indexed)
parseInt(dateStr.slice(6, 8), 10) // day
),
};
});
console.log('formattedHolidays', formattedHolidays);
return formattedHolidays;
};
return useQuery({
queryKey: [QUERY_KEY.HOLIDAYS, calendarYear], // ๋
๋์ ๋ฐ๋ผ ์ฟผ๋ฆฌํค ๋ณ๋
queryFn: fetchHolidays,
staleTime: 1000 * 60 * 60 * 24, // 1์ผ
});
};
๋ฌ๋ ฅ์ ๊ณตํด์ผ ์ ์ฉ
์ด์ ๋ฌ๋ ฅ์ ์ ์ฉํ ์ ์๋๋ก ๊ฐ๊ณตํ ๋ฐ์ดํฐ๋ฅผ ์ ์ฉํด์ผ ํ๋ค.
์บ๋ฆฐ๋ ์ปดํฌ๋ํธ์์ ๋ค์๊ณผ ๊ฐ์ด ์ํ์ ๊ฐ์ ์ ์ํ๊ณ ์๋ค.
์ปค์คํ ํ ์ ํด๋น ๋ ๋๋ฅผ ๋ฌธ์์ด๋ก ๋ฃ์ด์ฃผ๊ณ ๊ทธ ๊ฐ์ 'holidays'๋ผ๋ ์ด๋ฆ์ผ๋ก ๋ฐ์์จ๋ค.
const [month, setMonth] = useState(new Date()); //ํด๋น ๋ฌ
const calendarYear = month.getFullYear(); // ํด๋น ๋ฌ์ ๋
๋
const { data: holidays } = useGetHolidays(String(calendarYear)); // useQuery ์ปค์คํ
ํ
๊ธฐ์กด์ ๋ํ๋ด์๋ ์ฝ์ ์ผ์ ๋ค๊ณผ ๊ณตํด์ผ์ ํ๋์ ๋ฐฐ์ด๋ก ํฉ์ณ์ ๋ฌ๋ ฅ์ ์ ๋ฌํด์ฃผ์ด์ผ ํ๋ค.
- ๊ธฐ์กด ์ฝ์์ธ event๋ฅผ ํผ์น๋ค.
- ๊ณตํด์ผ ๋ฐ์ดํฐ์ธ holidays์ ๊ธฐ์กด ์ฝ์๊ณผ ๊ฐ์ ์ด๋ฆ์ผ๋ก ๋งคํํ์ฌ ํผ์น๋ค.
- ๋์ ํ๋์ ๋ฐฐ์ด๋ก ๋ง๋ ๋ค.
// ์ฝ์ + ๊ณตํด์ผ
const combinedEvents: CalendarEventType[] = [
...(events || []),
...(holidays || []).map((holiday: Holidays, idx: number) => ({
id: `holiday-${idx}`,
title: holiday.title,
start: holiday.date,
end: holiday.date, // ๋จ์ผ ์ผ์
isHoliday: true, // ์คํ์ผ ๊ตฌ๋ถ์ฉ
})),
];
์ต์ข ์ ์ผ๋ก ์บ๋ฆฐ๋์ ์ ์ฉํ๋ฉด ์๋ฃ!
<DnDCalendar
localizer={localizer}
events={combinedEvents}
...
/>

'Project' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
| [์ฌ๋๋ณ_Day10] ์๋ณ๋ก ๋ฐ์ดํฐ ์บ์ฑํ๊ธฐ (0) | 2025.04.09 |
|---|---|
| [์ฌ๋๋ณ_Day8] RBC ์ชผ๊ฐ๊ธฐ 1 - CustomToolbar : ๋ ์ง ์ด๋ (0) | 2025.04.07 |
| [์ฌ๋๋ณ_Day6] ์บ๋ฆฐ๋์ ์ผ์ ๋ํ๋ด๊ธฐ - UTC (0) | 2025.04.04 |
| [์ฌ๋๋ณ_Day5] RBC ํํค์น๊ธฐ 4 - ๊ธฐ์กด ์คํ์ผ๊ณผ ์ปค์คํฐ๋ง์ด์ง (0) | 2025.04.03 |
| [์ฌ๋๋ณ_Day4] RBC ํํค์น๊ธฐ 3 - ์บ๋ฆฐ๋๋ ์ CSR? (0) | 2025.04.02 |

