<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>개발  </title>
    <link>https://coco910.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Fri, 24 Jul 2026 02:50:48 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>채._.</managingEditor>
    <image>
      <title>개발  </title>
      <url>https://tistory1.daumcdn.net/tistory/7570977/attach/d9a5fd1b489140fb82f84f6d6e2388b4</url>
      <link>https://coco910.tistory.com</link>
    </image>
    <item>
      <title>[사람별] Next.js 에러 핸들링 (error.tsx)</title>
      <link>https://coco910.tistory.com/129</link>
      <description>&lt;h3 style=&quot;padding: 0.4em 1em 0.1em 0.2em; margin: 0.5em 0em; color: #000; border-bottom: 2px #888 dotted; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;문제 상황&lt;/h3&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-end=&quot;540&quot; data-start=&quot;410&quot; data-ke-size=&quot;size16&quot;&gt;Next.js를 사용해 캘린더 페이지를 개발하는 중,&lt;br /&gt;Supabase 통신 중 발생하는 에러를&lt;span&gt; 페이지 라우터 세그먼트 내의 &lt;/span&gt;&lt;b&gt;error.tsx&lt;/b&gt;로 통합해서 처리하려 했다.&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-end=&quot;555&quot; data-start=&quot;482&quot; data-ke-size=&quot;size16&quot;&gt;하지만 에러를 던질 때&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;내가 지정한 메시지&lt;/b&gt;와 &lt;b&gt;런타임 중 발생하는 시스템 메시지&lt;/b&gt;가 섞여 나오는 문제가 있었다.&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-end=&quot;562&quot; data-start=&quot;557&quot; data-ke-size=&quot;size16&quot;&gt;또한,&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-end=&quot;562&quot; data-start=&quot;557&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;color: #000000;&quot; data-end=&quot;598&quot; data-start=&quot;563&quot;&gt;app/api/**/route.ts&lt;span&gt;&amp;nbsp;&lt;/span&gt;(라우트 핸들러)&lt;/li&gt;
&lt;li style=&quot;color: #000000;&quot; data-end=&quot;626&quot; data-start=&quot;599&quot;&gt;supabase 호출 함수(fetch)&lt;/li&gt;
&lt;li style=&quot;color: #000000;&quot; data-end=&quot;652&quot; data-start=&quot;627&quot;&gt;클라이언트 컴포넌트 (page.tsx)&lt;/li&gt;
&lt;li style=&quot;color: #000000;&quot; data-end=&quot;718&quot; data-start=&quot;653&quot;&gt;탠스택 쿼리(useQuery)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: left;&quot;&gt;이렇게 여러 레이어를 거치면서 에러 흐름을 깔끔하게 잡는 것도 고민이 필요했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: left;&quot;&gt;최종적인 에러 흐름은 아래와 같다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;라우트 핸들러&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;성공or에러를 객체 형태로 받아서 return   페이지에서 에러 객체를 잡아서 throw &lt;b&gt; &lt;/b&gt; error.tsx 잡음&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1745675969635&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import ENV from '@/constants/env.constant';
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();

  try {
    const url = `${ENV.HOLIDAY_URL}/getRestDeInfo?ServiceKey=${ENV.HOLIDAY_KEY}&amp;amp;solYear=${year}&amp;amp;_type=json&amp;amp;numOfRows=100`;
    const res = await fetch(url);

    if (!res.ok) {
      throw new Error(`공휴일 API 응답 오류: ${res.statusText}`);
    }

    const data = await res.json();
    // 데이터 구조 검증
    if (!data.response || !data.response.body) {
      console.error('유효하지 않은 API 응답 형식:', data);
      return NextResponse.json({ error: '공휴일 데이터 형식이 올바르지 않습니다' }, { status: 500 });
    }
    return NextResponse.json(data); //정상 응답 반환
  } catch (error) {
    console.error('공휴일 데이터 호출 중 오류 발생:', error);
    return NextResponse.json({ error: '공휴일을 불러올 수 없습니다' }, { status: 500 });
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;supabase 통신&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;각 통신 함수에서 console.error &amp;amp; throw new Error   페이지 내의 쿼리 훅에서 error 꺼내기 &amp;nbsp;  각 에러 처리 후 throw   error.tsx 잡음&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1745676082953&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;1. plans 데이터 불러오기
export const getPlans = async (planId: string): Promise&amp;lt;PlansType&amp;gt; =&amp;gt; {
  const { data, error } = await supabase.from(PLANS).select('*').eq('plan_id', planId).single();

  if (error) {
    console.error('약속을 불러오는 중 오류가 발생했습니다:', error);
    throw new Error('약속을 불러오는 중 문제가 발생했습니다. 다시 시도해주세요.');
  }

  return data;
};

2. 쿼리 훅에서 error 꺼내기
  const { data: holidays, error: holidaysError } = useGetHolidays(String(calendarYear)); //공휴일
  const { data: events, isPending, error: plansError } = useGetCalendarPlans(user, calendarYear, moment); //약속(readonly)
  const {
    data: selectedPlanData,
    error: selectPlanError,
    refetch: refetchSelectedPlan,
  } = useGetSelectPlan(selectedPlanId ?? '');
    const { mutate: updateEvent, error: updatePlanError } = useUpadateEventMutate();
    
    
3. 에러 모아서 throw
  if (holidaysError || plansError || selectPlanError || updatePlanError) {
    const error = new Error('캘린더 데이터를 불러오는 중 문제가 발생했습니다.') as Error &amp;amp; {
      originalError?: Error | null;
    };
    error.originalError = holidaysError ?? plansError ?? selectPlanError ?? updatePlanError;
    throw error;
  }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 style=&quot;padding: 0.4em 1em 0.1em 0.2em; margin: 0.5em 0em; color: #000; border-bottom: 2px #888 dotted; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;에러 흐름 전체 구조&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;에러 처리를 다음과 같은 규칙으로 정리했다.&lt;/span&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;border-radius: 5em; padding: 0.5em 1em; background: #F2F2F2; box-shadow: 0.2em 0.15em 0.3em #999; margin-left: 5px; font-weight: bold;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;1.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;라우트 핸들러(app/api/&lt;/b&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;/route.ts)**&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;서버 사이드에서 에러 발생 시 &amp;rarr;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;NextResponse.json({ error }, { status })&lt;/b&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;로 클라이언트에 에러 전달&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1745675207721&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 });&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;border-radius: 5em; padding: 0.5em 1em; background: #F2F2F2; box-shadow: 0.2em 0.15em 0.3em #999; margin-left: 5px; font-weight: bold;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;2.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;통신 로직 (Supabase 호출 함수 등)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;fetch 함수에서는 통신 실패 시&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;throw new Error('커스텀 메시지')&lt;/b&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;로 에러 던지기&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;  이때 통신 자체가 안 된다면 런타임 에러가 발생할 수 있으니 주의.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1745675253757&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (error) {
  throw new Error('약속을 불러오는 중 오류가 발생했습니다.');
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;border-radius: 5em; padding: 0.5em 1em; background: #F2F2F2; box-shadow: 0.2em 0.15em 0.3em #999; margin-left: 5px; font-weight: bold;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;3.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;페이지 컴포넌트 (ex. app/(routes)/calendar/page.tsx)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;직접적인 오류 발생 시&lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;throw new Error('메시지')&lt;/b&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;&amp;rarr;&lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;error.tsx&lt;/b&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;로 연결되도록 설정&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1745675317413&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (!data) {
  throw new Error('데이터를 불러올 수 없습니다.');
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;border-radius: 5em; padding: 0.5em 1em; background: #F2F2F2; box-shadow: 0.2em 0.15em 0.3em #999; margin-left: 5px; font-weight: bold;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;4.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;탠스택 쿼리 (useQuery, useMutation)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;쿼리 훅 내부에서는 Next.js의 error.tsx를 직접 건드리지 않고&lt;/span&gt;&lt;br /&gt;error&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;isError&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;플래그를 통해&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;클라이언트 컴포넌트 안에서 에러 처리&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1745675346419&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const { data, error, isError } = useGetCalendarPlans();

if (isError) {
  throw error;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;✅&lt;b&gt; 흐름 정리&lt;/b&gt;&lt;/p&gt;
&lt;div style=&quot;width: 500px; padding: 15px 20px; border-radius: 10px; border: 1px solid #d9d9d9; margin: 10px 10px; box-shadow: 2px 2px 2px #999;&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;color: #000000;&quot; data-end=&quot;1781&quot; data-start=&quot;1739&quot;&gt;라우트 핸들러에서 발생하는 에러는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;json 응답&lt;/b&gt;으로 내려옴&lt;/li&gt;
&lt;li style=&quot;color: #000000;&quot; data-end=&quot;1840&quot; data-start=&quot;1782&quot;&gt;통신(fetcher) 단에서 받은 에러는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;throw new Error&lt;/b&gt;로 명시적으로 던짐&lt;/li&gt;
&lt;li style=&quot;color: #000000;&quot; data-end=&quot;1892&quot; data-start=&quot;1841&quot;&gt;페이지 컴포넌트에서는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;통합적으로 에러를 받아&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;필요한 경우 다시&lt;span&gt;&amp;nbsp;&lt;/span&gt;throw&lt;/li&gt;
&lt;li style=&quot;color: #000000;&quot; data-end=&quot;1934&quot; data-start=&quot;1893&quot;&gt;최종적으로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;error.tsx&lt;/b&gt;가 모든 에러를 받아 처리하는 구조&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;padding: 0.4em 1em 0.1em 0.2em; margin: 0.5em 0em; color: #000; border-bottom: 2px #888 dotted; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;error.tsx&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;error.tsx에서는 이렇게 메인 에러와 서브 에러(originalError)를 나누어 출력했다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메인 에러: 캘린더 페이지에서 throw한 메세지 '캘린더 데이터를 불러오는 중 문제가 발생했습니다.'&lt;/li&gt;
&lt;li&gt;서브 에러: supabase 통신 로직에서 throw한 디테일 메세지 '약속을 불러오는 중 오류가 발생했습니다. 다시 시도해주세요'&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;에러를 콘솔에도 출력해서 디버깅하기 쉽게 설정했다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1745675463828&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;'use client';

import GenericError from '@/components/Error';

export default function ErrorPage({ error, reset }: { error: Error &amp;amp; { originalError?: Error }; reset: () =&amp;gt; void }) {
  console.error('메인 에러:', error);

  if (error.originalError) {
    console.error('원래 에러:', error.originalError);
  }

  return (
    &amp;lt;GenericError
      error={error}
      originalError={error.originalError ?? null}
      refetch={reset} // reset 함수는 Next.js가 제공하는 재시도 함수
    /&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;padding: 0.4em 1em 0.1em 0.2em; margin: 0.5em 0em; color: #000; border-bottom: 2px #888 dotted; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;GenericError 컴포넌트&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;렌더링 시, &lt;b&gt;error.message&lt;/b&gt;와 &lt;b&gt;&lt;span style=&quot;color: #000000; letter-spacing: 0px;&quot;&gt;originalError.message&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #000000; letter-spacing: 0px;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #000000; letter-spacing: 0px;&quot;&gt;를 각각 보여줄 수 있도록 했다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1745676223630&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { Warning } from '@phosphor-icons/react';

type GenericErrorProps = {
  error?: Error | null;
  originalError: Error | null;
  refetch?: () =&amp;gt; void;
};

export default function GenericError({ error, refetch, originalError }: GenericErrorProps) {
  return (
    &amp;lt;div className='flex min-h-screen flex-col items-center justify-center gap-8'&amp;gt;
      &amp;lt;Warning size={60} className='text-secondary-600' /&amp;gt;
      &amp;lt;h2 className='title text-[45px] font-bold text-primary-700'&amp;gt;Error!&amp;lt;/h2&amp;gt;
      {error?.message &amp;amp;&amp;amp; originalError?.message &amp;amp;&amp;amp; (
        &amp;lt;section className='flex flex-col items-center text-[23px] text-primary-500'&amp;gt;
          &amp;lt;p&amp;gt;{error?.message}&amp;lt;/p&amp;gt;
          &amp;lt;p&amp;gt;Detail: {originalError?.message}&amp;lt;/p&amp;gt;
        &amp;lt;/section&amp;gt;
      )}
      {refetch &amp;amp;&amp;amp; (
        &amp;lt;button
          onClick={refetch}
          className='rounded-md border-2 bg-primary-500 p-4 font-bold text-white hover:bg-primary-600'
        &amp;gt;
          다시 시도
        &amp;lt;/button&amp;gt;
      )}
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;padding: 0.4em 1em 0.1em 0.2em; margin: 0.5em 0em; color: #000; border-bottom: 2px #888 dotted; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;일부러 통신 에러를 만드는 방법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Supabase 요청 중 발생하는 에러는 잘 잡히지만,&lt;br /&gt;코드 자체에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;런타임 에러&lt;/b&gt;가 나면 Supabase에 도달조차 못 하고 죽는다.&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-start=&quot;2665&quot; data-end=&quot;2671&quot; data-ke-size=&quot;size16&quot;&gt;예를 들면 아래와 같이&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&amp;nbsp;Supabase 요청을 보내기도 전에 런타임 에러가 발생한다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;(따라서 originalError가 존재하지 않는다.)&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1745676416463&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const { error } = await supabase.from(PLAS).select('*'); // PLAS가 undefined&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 Supabase 통신 실패를 일부러 만들고 싶으면&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #000000; text-align: start;&quot; data-end=&quot;2936&quot; data-start=&quot;2894&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2907&quot; data-start=&quot;2894&quot;&gt;잘못된 테이블명 사용&lt;/li&gt;
&lt;li data-end=&quot;2919&quot; data-start=&quot;2908&quot;&gt;필수 컬럼을 누락&lt;/li&gt;
&lt;li data-end=&quot;2936&quot; data-start=&quot;2920&quot;&gt;존재하지 않는 필드를 쿼리&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-end=&quot;3011&quot; data-start=&quot;2938&quot; data-ke-size=&quot;size16&quot;&gt;등을 통해 에러를 발생시키고,&lt;br /&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;정상적으로 응답이 오게 한 뒤&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;거기서&lt;span&gt;&amp;nbsp;&lt;/span&gt;error&lt;span&gt;&amp;nbsp;&lt;/span&gt;체크 후 던지는 식으로 처리해야 한다.&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-end=&quot;3011&quot; data-start=&quot;2938&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-end=&quot;3011&quot; data-start=&quot;2938&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;padding: 0.4em 1em 0.1em 0.2em; margin: 0.5em 0em; color: #000; border-bottom: 2px #888 dotted; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;최종 결과 및 느낀점&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-04-26 오후 8.54.18.png&quot; data-origin-width=&quot;2940&quot; data-origin-height=&quot;1478&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qiEre/btsNAQJrG8E/1JNBcp6ZlId5oCFQD31Eik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qiEre/btsNAQJrG8E/1JNBcp6ZlId5oCFQD31Eik/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qiEre/btsNAQJrG8E/1JNBcp6ZlId5oCFQD31Eik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqiEre%2FbtsNAQJrG8E%2F1JNBcp6ZlId5oCFQD31Eik%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2940&quot; height=&quot;1478&quot; data-filename=&quot;스크린샷 2025-04-26 오후 8.54.18.png&quot; data-origin-width=&quot;2940&quot; data-origin-height=&quot;1478&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-04-26 오후 8.52.39.png&quot; data-origin-width=&quot;2940&quot; data-origin-height=&quot;1488&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pHRek/btsNAfv1B5o/0kLeiyVadowmjLeVURIYsK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pHRek/btsNAfv1B5o/0kLeiyVadowmjLeVURIYsK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pHRek/btsNAfv1B5o/0kLeiyVadowmjLeVURIYsK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpHRek%2FbtsNAfv1B5o%2F0kLeiyVadowmjLeVURIYsK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2940&quot; height=&quot;1488&quot; data-filename=&quot;스크린샷 2025-04-26 오후 8.52.39.png&quot; data-origin-width=&quot;2940&quot; data-origin-height=&quot;1488&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 작업을 통해 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;런타임 에러와 통신 에러를 명확히 구분해서 다뤄야 한다&lt;/b&gt;&lt;/span&gt;는 걸 다시 깨달았다.&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-end=&quot;3492&quot; data-start=&quot;3372&quot; data-ke-size=&quot;size16&quot;&gt;또한&lt;span&gt;&amp;nbsp;&lt;/span&gt;throw new Error를 쓰는 위치, 에러를 받는 위치(error.tsx), 표시하는 방법(GenericError 컴포넌트)까지 모두 설계해야&lt;br /&gt;&lt;b&gt;에러 핸들링이 완성&lt;/b&gt;된다는 것을 경험했다.&lt;/p&gt;</description>
      <category>Language/Next</category>
      <author>채._.</author>
      <guid isPermaLink="true">https://coco910.tistory.com/129</guid>
      <comments>https://coco910.tistory.com/129#entry129comment</comments>
      <pubDate>Sat, 26 Apr 2025 23:07:20 +0900</pubDate>
    </item>
    <item>
      <title>[사람별_Day10] 월별로 데이터 캐싱하기</title>
      <link>https://coco910.tistory.com/128</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;오전 스크럼 시간에 캘린더 구현 기능을 이야기 나누던 중, 튜터님의 한 마디&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;i&gt;&quot;약속 데이터는 한 번에 다 받아오나요? 월별로 넘어갈 때마다 데이터를 가져오는 건 어떨까요?&quot;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 단계인 지금은 임의의 값 두어 개만 넣어놓고 전체를 가져오기 때문에 크게 문제가 안 되겠지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 이 서비스가 활성화된다면..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 유저가 약속 일정을 추가하게 되고, 시간이 지나 아주 예전 데이터까지 다 불러오게 된다면...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안 되겠다. 매 달마다 데이터를 가져오고 캐싱해두어야겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;border-bottom: 2px solid #424242; text-align: left; color: #424242; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #fff; position: relative; padding: 10px 15px 10px 0px; bottom: -19px;&quot;&gt;월별 데이터 캐싱하기&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단히 설명하자면 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;캘린더 컴포넌트 - &lt;span style=&quot;text-align: start;&quot;&gt;useQuery 커스텀 훅 - &lt;b&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;supabase 데이터 페칭 함수&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&amp;nbsp;&lt;span style=&quot;color: #333333;&quot;&gt;세 로직의 관계를 수정하면 된다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;padding: 0.4em 1em 0.1em 0.2em; margin: 0.5em 0em; color: #000; border-bottom: 2px #888 dotted; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;캘린더 컴포넌트&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캘린더 컴포넌트의 역할은 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;해당 달의 상태 관리 - Date 객체 : Wed Apr 09 2025 23:37:00 GMT+0900 (한국 표준시)&lt;/li&gt;
&lt;li&gt;해당 달의 현재 년도 - number : 2025&lt;/li&gt;
&lt;li&gt;useQuery 커스텀 훅에 년도와 달의 값 넘겨주어 데이터 받아오기 : 약속 일정 데이터&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1744209333230&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  const [month, setMonth] = useState(new Date()); //해당 달 
  const calendarYear = month.getFullYear(); // 해당 달의 년도
  const { data: events, isPending, isError, error } = useGetCalendarPlans(calendarYear, month);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;padding: 0.4em 1em 0.1em 0.2em; margin: 0.5em 0em; color: #000; border-bottom: 2px #888 dotted; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;text-align: start;&quot;&gt;useQuery 커스텀 훅&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;useQuery 커스텀 훅&lt;/span&gt;의 역할은 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;supabase에 숫자로 전달해주기 위해 타입 전환&lt;/li&gt;
&lt;li&gt;해당 년도의 달마다 캐싱 (staleTime은 1일)&lt;/li&gt;
&lt;li&gt;supabase 페칭 로직에 년도와 달 넘겨주어 값 가져오기&lt;/li&gt;
&lt;li&gt;받아온 데이터를 캘린더에 적용하도록 가공하기&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1744209552733&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export const useGetCalendarPlans = (year: number, monthDate: Date) =&amp;gt; {
  const month = monthDate.getMonth() + 1; // Date 객체 -&amp;gt; 숫자 전환
  return useQuery&amp;lt;CalendarEventType[]&amp;gt;({
    queryKey: [QUERY_KEY.CALENDAR_PLANS, year, month], //달마다 캐싱
    queryFn: async () =&amp;gt; {
      const plans: PlansType[] = await getPlans(year, month); // supabase에서 plans 데이터 가져오기

      // 캘린더에 적용되도록 데이터 가공하기
      const events: CalendarEventType[] = plans.map((plan) =&amp;gt; ({
        id: plan.plan_id,
        title: plan.title,
        start: new Date(plan.start_date),
        end: new Date(plan.end_date),
      }));
      return events;
    },
     staleTime: 24 * 60 * 60 * 1000, // 1일
  });
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;padding: 0.4em 1em 0.1em 0.2em; margin: 0.5em 0em; color: #000; border-bottom: 2px #888 dotted; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;supabase 데이터 페칭 함수&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;supabase 데이터 페칭 함수의 역할은 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;매 달의 첫 날과 마지막 날 만들기&lt;/li&gt;
&lt;li&gt;약속 추가할 때 1시간 추가한 이유
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;DnD 시 단일 일정 늘어남 (end-start = 0 일 때 자동처리)&lt;/li&gt;
&lt;li&gt;연속 일정 시 마지막 날 달력에 적용시키기&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;월말~월초 이어지는 약속도 나타내기 위해 setHours 적용&lt;/li&gt;
&lt;li&gt;lte: 작거나 같다 / gte: 크거나 같다&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1744209824236&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export const getPlans = async (year: number, month: number): Promise&amp;lt;PlansType[]&amp;gt; =&amp;gt; {
  const startOfMonth = new Date(year, month - 1, 1); //첫 날
  const endOfMonth = new Date(year, month, 0); //마지막 날 (4/30 00:00:00)
  // 현재 start_date가 4/30 01:00 이기 때문에 연속 일정의 첫 날도 함께 포함시키기 위함
  endOfMonth.setHours(23, 59, 59, 999);

  const { data: plans, error } = await supabase
    .from(PLANS)
    .select('plan_id, user_id, contacts_id, title, detail, priority, start_date, end_date')
    //해당 달의 데이터만 가져오기(넘어가는 연속 일정 포함)
    .lte('start_date', endOfMonth.toISOString()) // 일정이 달의 마지막 날과 같거나 이전에 시작
    .gte('end_date', startOfMonth.toISOString()); // 일정이 달의 첫 날보다 같거나 이후에 끝
  if (error) {
    throw new Error(error.message);
  }
  return plans;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Project</category>
      <author>채._.</author>
      <guid isPermaLink="true">https://coco910.tistory.com/128</guid>
      <comments>https://coco910.tistory.com/128#entry128comment</comments>
      <pubDate>Wed, 9 Apr 2025 23:45:29 +0900</pubDate>
    </item>
    <item>
      <title>[사람별_Day9] 공휴일 달력에 적용하기 (feat.공공데이터)</title>
      <link>https://coco910.tistory.com/127</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;달력이라면 필요한 공휴일 !&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;border-bottom: 2px solid #424242; text-align: left; color: #424242; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #fff; position: relative; padding: 10px 15px 10px 0px; bottom: -19px;&quot;&gt;공공데이터 API 발급&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공휴일 정보를 얻어오기 위해서 &lt;a href=&quot;https://www.data.go.kr/iim/api/selectAPIAcountView.do&quot;&gt;공공데이터&lt;/a&gt;를 이용하였다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2534&quot; data-origin-height=&quot;1068&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kFti7/btsNf6j3Ytp/CpVrlKe4FWlJruhactnFoK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kFti7/btsNf6j3Ytp/CpVrlKe4FWlJruhactnFoK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kFti7/btsNf6j3Ytp/CpVrlKe4FWlJruhactnFoK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkFti7%2FbtsNf6j3Ytp%2FCpVrlKe4FWlJruhactnFoK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2534&quot; height=&quot;1068&quot; data-origin-width=&quot;2534&quot; data-origin-height=&quot;1068&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회원가입과 로그인을 하고 활용신청을 클릭한다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;figure class=&quot;imageblock undefined&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;806&quot; data-origin-height=&quot;332&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qURlY/btsNfRUYXXE/alWygBmk8ntpXNpdVc4i50/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qURlY/btsNfRUYXXE/alWygBmk8ntpXNpdVc4i50/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qURlY/btsNfRUYXXE/alWygBmk8ntpXNpdVc4i50/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqURlY%2FbtsNfRUYXXE%2FalWygBmk8ntpXNpdVc4i50%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;837&quot; height=&quot;143&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;806&quot; data-origin-height=&quot;332&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;figure class=&quot;imageblock undefined&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;380&quot; data-origin-height=&quot;252&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bVFEWI/btsNfYflovC/BwptdZQkCG0OrflJpwQDN0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bVFEWI/btsNfYflovC/BwptdZQkCG0OrflJpwQDN0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bVFEWI/btsNfYflovC/BwptdZQkCG0OrflJpwQDN0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbVFEWI%2FbtsNfYflovC%2FBwptdZQkCG0OrflJpwQDN0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;380&quot; height=&quot;252&quot; data-origin-width=&quot;380&quot; data-origin-height=&quot;252&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;신청이 완료되면 일반 인증키 두 개가 발급되는데, &lt;b&gt;Encoding&lt;/b&gt; 키를 이용하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;border-bottom: 2px solid #424242; text-align: left; color: #424242; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #fff; position: relative; padding: 10px 15px 10px 0px; bottom: -19px;&quot;&gt;API 적용&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;padding: 0.4em 1em 0.1em 0.2em; margin: 0.5em 0em; color: #000; border-bottom: 2px #888 dotted; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;라우트 핸들러&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;.env.local 파일에 키 값을 둘 때 한 가지 유의할 점이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;supabase의 어논키 같은 경우는 브라우저에 노출되어도 크게 문제가 안 되기 때문에 'NEXT_PUBLIC'을 붙여서 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 공공데이터 같은 경우는 &lt;b&gt;회원가입 후 발급&lt;/b&gt;받았기 때문에 브라우저에 노출이 조금 꺼려졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt; 'NEXT_PUBLIC'을 사용하지 않았다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1744205327350&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;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;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 이 키를 사용해서 어떻게 데이터를 불러와야 할까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 기존 방식을 따라 service.ts 파일에서 fetch를 통해 다음과 같이 가져왔었다.&lt;/p&gt;
&lt;pre id=&quot;code_1744205973017&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 공공데이터 공휴일 API
export const getHolidays = async (year: string) =&amp;gt; {
  const BASE_URL = 'http://apis.data.go.kr/B090041/openapi/service/SpcdeInfoService';
  const res = await fetch(
    //현재 년도 값만
    `${BASE_URL}/getRestDeInfo?ServiceKey=${ENV.HOLIDAY_KEY}&amp;amp;solYear=${year}&amp;amp;_type=json&amp;amp;numOfRows=100`
  );
  const data = await res.json();

  return data;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 'NEXT_PUBLIC'가 없기 때문에 브라우저에서의 사용이 불가능했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 생각한 것이 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;라우트 핸들러!&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;서버-서버의 통신을 통해 데이터의 응답 값을 라우트 경로에 주기 때문에&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;서버에서 키 값 사용하여 데이터 페칭이 가능하며 그 데이터를 브라우저에서도 사용이 가능하다.&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt; 공휴일 API 라우트 핸들러&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1744206168633&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// app &amp;gt; api &amp;gt; holiday &amp;gt; 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}&amp;amp;solYear=${year}&amp;amp;_type=json&amp;amp;numOfRows=100`;

  const res = await fetch(url);
  const data = await res.json();

  return NextResponse.json(data);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;padding: 0.4em 1em 0.1em 0.2em; margin: 0.5em 0em; color: #000; border-bottom: 2px #888 dotted; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;데이터 가공&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라우트핸들러를 통해 받아온 응답값을 fetch로 받아온다.&lt;/p&gt;
&lt;pre id=&quot;code_1744206374370&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export const useGetHolidays = (calendarYear: string) =&amp;gt; {
  // 공휴일 데이터 전체 가져오기
  const fetchHolidays = async () =&amp;gt; {
    const res = await fetch(`/api/holidays?year=${calendarYear}`); // 년도에 맞는 데이터
    const data = await res.json();
    const items = data.response?.body?.items?.item || [];
    
    ...&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;856&quot; data-origin-height=&quot;354&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b4v9JK/btsNd9JHdcE/9ExAJBrnrWRfVQUuw390u0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b4v9JK/btsNd9JHdcE/9ExAJBrnrWRfVQUuw390u0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b4v9JK/btsNd9JHdcE/9ExAJBrnrWRfVQUuw390u0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb4v9JK%2FbtsNd9JHdcE%2F9ExAJBrnrWRfVQUuw390u0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;619&quot; height=&quot;256&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;856&quot; data-origin-height=&quot;354&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 값은 달력에 바로 적용할 수 없으므로 알맞게 가공해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1. 필요한 값만 추출&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;초기 데이터에서 필요한 날짜와 공휴일명을 뽑아낸다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1744206700776&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 필요한 값만 추출
    const holidays = items.map((item: Item) =&amp;gt; ({
      date: item.locdate, // 20250408 형식
      name: item.dateName,
    }));&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;880&quot; data-origin-height=&quot;212&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cFGWVx/btsNfJir0Wz/LFX3vUMNpij8wQB1PTjcl0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cFGWVx/btsNfJir0Wz/LFX3vUMNpij8wQB1PTjcl0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cFGWVx/btsNfJir0Wz/LFX3vUMNpij8wQB1PTjcl0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcFGWVx%2FbtsNfJir0Wz%2FLFX3vUMNpij8wQB1PTjcl0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;652&quot; height=&quot;157&quot; data-origin-width=&quot;880&quot; data-origin-height=&quot;212&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2. 달력에 적용할 수 있도록 가공&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;&lt;b&gt;.slice 메서드&lt;/b&gt;를 사용하기 위해 숫자였던 date를 문자열로 변환하여 dateStr 변수에 담아준다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;'20250101' 형태의 date를 년, 월, 일로 나누어 new Date 객체로 변환한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이때, &lt;b&gt;월은 0부터 시작하는 인덱스&lt;/b&gt;이기 때문에 '-1' 처리를 해준다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1744206797582&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; // 캘린더에 적용할 수 있도록 가공
    const formattedHolidays = holidays.map((item: Holidays) =&amp;gt; {
      const dateStr = item.date.toString(); // number &amp;rarr; 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;
  };&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1010&quot; data-origin-height=&quot;218&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HptdY/btsNfoS71Ng/jIzhFwpkckrv5LOXbNNGvk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HptdY/btsNfoS71Ng/jIzhFwpkckrv5LOXbNNGvk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HptdY/btsNfoS71Ng/jIzhFwpkckrv5LOXbNNGvk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHptdY%2FbtsNfoS71Ng%2FjIzhFwpkckrv5LOXbNNGvk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;652&quot; height=&quot;141&quot; data-origin-width=&quot;1010&quot; data-origin-height=&quot;218&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3. useQuery 훅을 반환&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 모든 기능의 함수를 queryFn로 갖는 useQuery를 생성한다.&lt;/li&gt;
&lt;li&gt;공휴일의 정보를 &lt;b&gt;년마다 캐싱&lt;/b&gt;하기 위해 쿼리 키를 해당 년도에 따라 동적으로 변동한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1744207253317&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; return useQuery({
    queryKey: [QUERY_KEY.HOLIDAYS, calendarYear], // 년도에 따라 쿼리키 변동
    queryFn: fetchHolidays,
    staleTime: 1000 * 60 * 60 * 24, // 1일
  });&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt; &lt;b&gt;useQuery &lt;/b&gt;커스텀 훅 전체 로직&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1744207374368&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { QUERY_KEY } from '@/constants/queryKey';
import { Holidays, Item } from '@/types/plans';
import { useQuery } from '@tanstack/react-query';

export const useGetHolidays = (calendarYear: string) =&amp;gt; {
  // 공휴일 데이터 전체 가져오기
  const fetchHolidays = async () =&amp;gt; {
    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) =&amp;gt; ({
      date: item.locdate, // 20250408 형식
      name: item.dateName,
    }));

    // 캘린더에 적용할 수 있도록 가공
    const formattedHolidays = holidays.map((item: Holidays) =&amp;gt; {
      const dateStr = item.date.toString(); // number &amp;rarr; 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일
  });
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;border-bottom: 2px solid #424242; text-align: left; color: #424242; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #fff; position: relative; padding: 10px 15px 10px 0px; bottom: -19px;&quot;&gt;달력에 공휴일 적용&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 달력에 적용할 수 있도록 가공한 데이터를 적용해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캘린더 컴포넌트에서 다음과 같이 상태와 값을 정의하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커스텀 훅에 해당 년도를 문자열로 넣어주고 그 값을 'holidays'라는 이름으로 받아온다.&lt;/p&gt;
&lt;pre id=&quot;code_1744207496680&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  const [month, setMonth] = useState(new Date()); //해당 달
  const calendarYear = month.getFullYear(); // 해당 달의 년도
  const { data: holidays } = useGetHolidays(String(calendarYear)); // useQuery 커스텀 훅&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에 나타내었던 &lt;b&gt;약속 일정들과 공휴일을 하나의 배열로 합&lt;/b&gt;쳐서 달력에 전달해주어야 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기존 약속인 event를 펼친다.&lt;/li&gt;
&lt;li&gt;공휴일 데이터인 holidays을 기존 약속과 같은 이름으로 &amp;nbsp;매핑하여 펼친다.&lt;/li&gt;
&lt;li&gt;둘을 하나의 배열로 만든다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1744207619248&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  // 약속 + 공휴일
  const combinedEvents: CalendarEventType[] = [
    ...(events || []),
    ...(holidays || []).map((holiday: Holidays, idx: number) =&amp;gt; ({
      id: `holiday-${idx}`,
      title: holiday.title,
      start: holiday.date,
      end: holiday.date, // 단일 일정
      isHoliday: true, // 스타일 구분용
    })),
  ];&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최종적으로 캘린더에 적용하면 완료!&lt;/p&gt;
&lt;pre id=&quot;code_1744207818895&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;   &amp;lt;DnDCalendar
        localizer={localizer}
        events={combinedEvents}
        ...
    /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;무제.gif&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;608&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QXKLJ/btsNdSai53R/EM3pGFGHYQPTWoJFbkMB7K/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QXKLJ/btsNdSai53R/EM3pGFGHYQPTWoJFbkMB7K/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QXKLJ/btsNdSai53R/EM3pGFGHYQPTWoJFbkMB7K/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/QXKLJ/btsNdSai53R/EM3pGFGHYQPTWoJFbkMB7K/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;608&quot; data-filename=&quot;무제.gif&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;608&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Project</category>
      <author>채._.</author>
      <guid isPermaLink="true">https://coco910.tistory.com/127</guid>
      <comments>https://coco910.tistory.com/127#entry127comment</comments>
      <pubDate>Wed, 9 Apr 2025 23:12:35 +0900</pubDate>
    </item>
    <item>
      <title>[사람별_Day8] RBC 쪼개기 1 - CustomToolbar : 날짜 이동</title>
      <link>https://coco910.tistory.com/126</link>
      <description>&lt;h3 style=&quot;border-bottom: 2px solid #424242; text-align: left; color: #424242; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #fff; position: relative; padding: 10px 15px 10px 0px; bottom: -19px;&quot;&gt;시작 요일, 월간만 보여주기&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;padding: 0.4em 1em 0.1em 0.2em; margin: 0.5em 0em; color: #000; border-bottom: 2px #888 dotted; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;월요일부터 시작&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2502&quot; data-origin-height=&quot;1016&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cHGo3c/btsNannGml2/YWoRnq2s4BmsENhXYt2pUK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cHGo3c/btsNannGml2/YWoRnq2s4BmsENhXYt2pUK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cHGo3c/btsNannGml2/YWoRnq2s4BmsENhXYt2pUK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcHGo3c%2FbtsNannGml2%2FYWoRnq2s4BmsENhXYt2pUK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2502&quot; height=&quot;1016&quot; data-origin-width=&quot;2502&quot; data-origin-height=&quot;1016&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1743998297868&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const localizer = dateFnsLocalizer({
  format,
  parse,
  startOfWeek: () =&amp;gt; startOfWeek(new Date(), { weekStartsOn: 1 }), // 월요일 시작
  getDay,
  locales,
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;padding: 0.4em 1em 0.1em 0.2em; margin: 0.5em 0em; color: #000; border-bottom: 2px #888 dotted; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;월간만 보여주기&lt;/h3&gt;
&lt;pre id=&quot;code_1743998257779&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;   &amp;lt;Calendar
        localizer={localizer}
        events={events} // 여기에 이벤트 추가 가능
        startAccessor='start'
        endAccessor='end'
        defaultView='month' // 기본 보여주기 : 월별
        views={['month']} // 월간 달력만 보여주기
        style={{ height: 500 }}
      /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2512&quot; data-origin-height=&quot;1014&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/baiDdC/btsNbpEF5ci/tqawpMMtYU1kZTMikDvUx1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/baiDdC/btsNbpEF5ci/tqawpMMtYU1kZTMikDvUx1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/baiDdC/btsNbpEF5ci/tqawpMMtYU1kZTMikDvUx1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbaiDdC%2FbtsNbpEF5ci%2FtqawpMMtYU1kZTMikDvUx1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2512&quot; height=&quot;1014&quot; data-origin-width=&quot;2512&quot; data-origin-height=&quot;1014&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;border-bottom: 2px solid #424242; text-align: left; color: #424242; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #fff; position: relative; padding: 10px 15px 10px 0px; bottom: -19px;&quot;&gt;RBC 주요 컴포넌트&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좌측 상단에 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;날짜 넘기기 / 오늘 날짜 버튼&lt;/span&gt;을 두어 달력의 이동을 구현하고 싶었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사례를 찾다보니 RBC의 요소들을 여러 컴포넌트로 쪼개서 사용할 수 있다는 것을 알게되었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;쪼갤 수 있는 &lt;b&gt;주요 컴포넌트&lt;/b&gt;는 다음과 같다.&lt;/p&gt;
&lt;div style=&quot;width: 500px; padding: 15px 20px; border-radius: 10px; border: 1px solid #d9d9d9; margin: 10px 10px; box-shadow: 2px 2px 2px #999;&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;components={{&lt;br /&gt;&amp;nbsp;&amp;nbsp;toolbar,&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;상단&amp;nbsp;툴바&lt;br /&gt;&amp;nbsp;&amp;nbsp;event,&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;이벤트&amp;nbsp;셀&lt;br /&gt;&amp;nbsp;&amp;nbsp;agenda:&amp;nbsp;{&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;event,&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;agenda&amp;nbsp;view의&amp;nbsp;이벤트&lt;br /&gt;&amp;nbsp;&amp;nbsp;},&lt;br /&gt;&amp;nbsp;&amp;nbsp;timeSlotWrapper,&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;시간&amp;nbsp;슬롯&amp;nbsp;(시간&amp;nbsp;칸)&lt;br /&gt;&amp;nbsp;&amp;nbsp;dateCellWrapper,&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;날짜&amp;nbsp;셀&amp;nbsp;(월간&amp;nbsp;뷰&amp;nbsp;셀)&lt;br /&gt;&amp;nbsp;&amp;nbsp;day,&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;일간&amp;nbsp;뷰&lt;br /&gt;&amp;nbsp;&amp;nbsp;week,&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;주간&amp;nbsp;뷰&lt;br /&gt;&amp;nbsp;&amp;nbsp;month,&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;월간&amp;nbsp;뷰&lt;br /&gt;}}&lt;/p&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;padding: 0.4em 1em 0.1em 0.2em; margin: 0.5em 0em; color: #000; border-bottom: 2px #888 dotted; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;Toolbar의 props&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 중 툴바에 해당하는 요소를 자세히 살펴보겠다.&lt;/p&gt;
&lt;div style=&quot;width: 500px; padding: 15px 20px; border-radius: 10px; border: 1px solid #d9d9d9; margin: 10px 10px; box-shadow: 2px 2px 2px #999;&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;label, onNavigate, localizer, onView, date, view, views&lt;/p&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;border-radius: 5em; padding: 0.5em 1em; background: #F2F2F2; box-shadow: 0.2em 0.15em 0.3em #999; margin-left: 5px; font-weight: bold;&quot;&gt;label&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;color: #000000;&quot; data-end=&quot;468&quot; data-start=&quot;416&quot;&gt;툴바 중앙에 보통 보이는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;현재 날짜 범위&lt;/b&gt;의 텍스트 (예: &quot;April 2025&quot;)&lt;/li&gt;
&lt;li style=&quot;color: #000000;&quot; data-end=&quot;507&quot; data-start=&quot;469&quot;&gt;RBC 내부에서 자동으로 만들어줌&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1744003728812&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;span&amp;gt;{label}&amp;lt;/span&amp;gt;  // &quot;April 2025&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;border-radius: 5em; padding: 0.5em 1em; background: #F2F2F2; box-shadow: 0.2em 0.15em 0.3em #999; margin-left: 5px; font-weight: bold;&quot;&gt;onNavigate(action)&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc; color: #000000; text-align: start;&quot; data-end=&quot;681&quot; data-start=&quot;591&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;618&quot; data-start=&quot;591&quot;&gt;이전/다음/오늘 버튼 클릭 시 사용하는 함수&lt;/li&gt;
&lt;li data-end=&quot;681&quot; data-start=&quot;619&quot;&gt;action&lt;span&gt;&amp;nbsp;&lt;/span&gt;값은 다음 중 하나:&lt;span&gt;&amp;nbsp;&lt;/span&gt;'PREV',&lt;span&gt;&amp;nbsp;&lt;/span&gt;'NEXT',&lt;span&gt;&amp;nbsp;&lt;/span&gt;'TODAY',&lt;span&gt;&amp;nbsp;&lt;/span&gt;'DATE'&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;pre id=&quot;code_1744003784138&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;button onClick={() =&amp;gt; onNavigate('TODAY')}&amp;gt;Today&amp;lt;/button&amp;gt;
&amp;lt;button onClick={() =&amp;gt; onNavigate('PREV')}&amp;gt;{'&amp;lt;'}&amp;lt;/button&amp;gt;
&amp;lt;button onClick={() =&amp;gt; onNavigate('NEXT')}&amp;gt;{'&amp;gt;'}&amp;lt;/button&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;border-radius: 5em; padding: 0.5em 1em; background: #F2F2F2; box-shadow: 0.2em 0.15em 0.3em #999; margin-left: 5px; font-weight: bold;&quot;&gt;localizer&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;달력에서 사용하는 날짜/시간 관련 기능들을 지역(locale)에 맞게 처리&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;color: #000000;&quot; data-end=&quot;224&quot; data-start=&quot;197&quot;&gt;날짜를 &quot;2025년 4월 7일&quot;처럼 포맷&lt;/li&gt;
&lt;li style=&quot;color: #000000;&quot; data-end=&quot;264&quot; data-start=&quot;225&quot;&gt;요일을 &quot;Mon&quot;, &quot;Tue&quot;가 아니라 &quot;월&quot;, &quot;화&quot;처럼 변경&lt;/li&gt;
&lt;li style=&quot;color: #000000;&quot; data-end=&quot;287&quot; data-start=&quot;265&quot;&gt;주의 시작 요일을 월요일로 설정&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;border-radius: 5em; padding: 0.5em 1em; background: #F2F2F2; box-shadow: 0.2em 0.15em 0.3em #999; margin-left: 5px; font-weight: bold;&quot;&gt;onView(viewName)&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;뷰(예: month, week, day, agenda)를 전환하고 싶을 때 사용하는 함수&lt;/li&gt;
&lt;li&gt;views props에 해당 뷰가 등록되어있어야 작동한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1744003900627&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;button onClick={() =&amp;gt; onView('month')}&amp;gt;Month&amp;lt;/button&amp;gt;
&amp;lt;button onClick={() =&amp;gt; onView('week')}&amp;gt;Week&amp;lt;/button&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;border-radius: 5em; padding: 0.5em 1em; background: #F2F2F2; box-shadow: 0.2em 0.15em 0.3em #999; margin-left: 5px; font-weight: bold;&quot;&gt;date&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;color: #000000;&quot; data-end=&quot;1182&quot; data-start=&quot;1132&quot;&gt;현재 보여지고 있는 캘린더 날짜 기준의&lt;span&gt;&amp;nbsp;&lt;/span&gt;Date&lt;span&gt;&amp;nbsp;&lt;/span&gt;객체 (예: 2025-04-01)&lt;/li&gt;
&lt;li style=&quot;color: #000000;&quot; data-end=&quot;1204&quot; data-start=&quot;1183&quot;&gt;직접 날짜 조작하고 싶을 때 유용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;border-radius: 5em; padding: 0.5em 1em; background: #F2F2F2; box-shadow: 0.2em 0.15em 0.3em #999; margin-left: 5px; font-weight: bold;&quot;&gt;view&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;현재 보고 있는 뷰의 이름 ('month',&amp;nbsp;'week',&amp;nbsp;'day',&amp;nbsp;'agenda')&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1744003979725&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (view === 'month') {
  // 월간 뷰 전용 UI 보여주기
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;border-radius: 5em; padding: 0.5em 1em; background: #F2F2F2; box-shadow: 0.2em 0.15em 0.3em #999; margin-left: 5px; font-weight: bold;&quot;&gt;views&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;color: #000000;&quot; data-end=&quot;1410&quot; data-start=&quot;1363&quot;&gt;사용 가능한 뷰들의 배열 (예:&lt;span&gt;&amp;nbsp;&lt;/span&gt;['month', 'week', 'day'])&lt;/li&gt;
&lt;li style=&quot;color: #000000;&quot; data-end=&quot;1410&quot; data-start=&quot;1363&quot;&gt;&lt;span style=&quot;color: #333333; letter-spacing: 0px;&quot;&gt;이걸로 view 전환 버튼을 동적으로 생성할 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;padding: 0.4em 1em 0.1em 0.2em; margin: 0.5em 0em; color: #000; border-bottom: 2px #888 dotted; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;이전, 다음 달 넘기기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;onNavigate()와 date를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://jquense.github.io/react-big-calendar/examples/index.html?path=/docs/props--on-navigate&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;공식 문서&lt;/a&gt;에서는 &lt;b&gt;onNavigate&lt;/b&gt;와 &lt;b&gt;date&lt;/b&gt;를 다음과 같이 설명하고 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1134&quot; data-origin-height=&quot;362&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mOoUG/btsNcp5pHqW/yKgxoRXj2K1K54X7CvNnA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mOoUG/btsNcp5pHqW/yKgxoRXj2K1K54X7CvNnA0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mOoUG/btsNcp5pHqW/yKgxoRXj2K1K54X7CvNnA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmOoUG%2FbtsNcp5pHqW%2FyKgxoRXj2K1K54X7CvNnA0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;572&quot; height=&quot;183&quot; data-origin-width=&quot;1134&quot; data-origin-height=&quot;362&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1382&quot; data-origin-height=&quot;512&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/B7BJj/btsNce31PW6/MQWKLsBKTnywsyTEZ8nT3k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/B7BJj/btsNce31PW6/MQWKLsBKTnywsyTEZ8nT3k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/B7BJj/btsNce31PW6/MQWKLsBKTnywsyTEZ8nT3k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FB7BJj%2FbtsNce31PW6%2FMQWKLsBKTnywsyTEZ8nT3k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;620&quot; height=&quot;230&quot; data-origin-width=&quot;1382&quot; data-origin-height=&quot;512&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;둘은 함께 사용되며 onNavigate 함수를 통해 date를 제어하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동적으로 변하는 날짜를 state로 두고 onNavigate 함수로 이를 제어했다.&lt;/p&gt;
&lt;pre id=&quot;code_1744007881881&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// MainCalendar.tsx

const MainCalendar = () =&amp;gt; {
  const [month, setMonth] = useState(new Date());
  
  return (
    &amp;lt;div&amp;gt;
   	 &amp;lt;Calendar
        localizer={localizer}
        events={events}
        date={month} // 현재 날짜(달)
        onNavigate={(newDate) =&amp;gt; {
          setMonth(newDate); // 날짜(달) 변동
        }}
        startAccessor='start'
        endAccessor='end'
        defaultView='month'
        views={['month']}
        components={{
          toolbar: CustomToolbar, // 상단 툴바 분리
        }}
        style={{ height: 500 }}
      /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1744007926792&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// CustomToolbar.tsx

import React from 'react';
import { NavigateAction } from 'react-big-calendar';

interface Props {
  label: string;
  onNavigate: (action: NavigateAction) =&amp;gt; void;
}

const CustomToolbar = ({ label, onNavigate }: Props) =&amp;gt; {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; onNavigate('PREV')}&amp;gt;⬅️&amp;lt;/button&amp;gt;
      &amp;lt;span&amp;gt;{label}&amp;lt;/span&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; onNavigate('NEXT')}&amp;gt;➡️&amp;lt;/button&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; onNavigate('TODAY')}&amp;gt;Today&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

export default CustomToolbar;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;  버튼은 &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;CustomToolbar 안에 있는데 외부에 date랑 onNavigate를 두어도 작동되는 이유가 뭘까?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캘린더 컴포넌트가 툴바에게 &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;props를 넘겨주기 때문에&lt;/b&gt;&lt;/span&gt; 둘이 연결될 수 있는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좀 더 자세히 살펴보자면,&lt;/p&gt;
&lt;pre id=&quot;code_1744008075382&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;Calendar
  date={month}                          //   현재 날짜 state
  onNavigate={(newDate) =&amp;gt; setMonth(newDate)} //   날짜 변경 함수
  components={{ toolbar: CustomToolbar }}      //   커스텀 툴바 적용
/&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;사용자가 버튼을 클릭한다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;CustomToolbar&lt;/span&gt;에서 onNavigate('PREV')&lt;/li&gt;
&lt;li&gt;RBC 자체적으로 PREV에 맞는 newDate 계산&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;Calendar&lt;/span&gt;의&amp;nbsp;onNavigate(newDate)&amp;nbsp;호출&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;Calendar&lt;/span&gt;의 setMonth(newDate) &amp;rarr; state 업데이트&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;Calendar&lt;/span&gt;&amp;nbsp;다시&amp;nbsp;렌더링&amp;nbsp;+&amp;nbsp;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;CustomToolbar&lt;/span&gt;도&amp;nbsp;리렌더링&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;padding: 0.4em 1em 0.1em 0.2em; margin: 0.5em 0em; color: #000; border-bottom: 2px #888 dotted; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;localizer 설정 : 2025 4월&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 최상단에 정리한 것처럼 툴바가 아닌 캘린더에서 월요일 시작을 구현했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 캘린더에서와 툴바에서의 localizer 역할 차이도 궁금했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;캘린더&lt;/span&gt;에서의 localizer&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;전체 캘린더의 날짜 처리 기준&lt;/b&gt;을 정한다.&lt;/p&gt;
&lt;pre id=&quot;code_1744013270926&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;Calendar localizer={localizer} ... /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;color: #000000;&quot; data-end=&quot;320&quot; data-start=&quot;249&quot;&gt;&lt;b&gt;기본 날짜 포맷팅&lt;/b&gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;시작 요일&lt;/b&gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;언어/로케일&lt;/b&gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;날짜 계산 로직&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;등을&lt;span&gt;&amp;nbsp;&lt;/span&gt;Calendar에 전달&lt;/li&gt;
&lt;li style=&quot;color: #000000;&quot; data-end=&quot;397&quot; data-start=&quot;321&quot;&gt;내부적으로&lt;span&gt;&amp;nbsp;&lt;/span&gt;localizer.format(...),&lt;span&gt;&amp;nbsp;&lt;/span&gt;localizer.startOfWeek(...)&lt;span&gt;&amp;nbsp;&lt;/span&gt;등을 사용해서 날짜 처리&lt;/li&gt;
&lt;li style=&quot;color: #000000;&quot; data-end=&quot;506&quot; data-start=&quot;398&quot;&gt;시작 요일을 월요일:&lt;span&gt;&amp;nbsp;&lt;/span&gt;startOfWeek: () =&amp;gt; startOfWeek(new Date(), { weekStartsOn: 1 })&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt; &lt;span style=&quot;background-color: #9feec3;&quot;&gt;툴바&lt;/span&gt;에서의 localizer&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;b&gt;툴바&lt;/b&gt;에 표시되는 &lt;b&gt;날짜 텍스트 포맷&lt;/b&gt;한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1744013444028&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const CustomToolbar = ({ localizer }: ToolbarProps&amp;lt;Event&amp;gt;) =&amp;gt; {
  const label = localizer.format(date, 'yyyy MMMM', 'ko-KR');
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;color: #000000;&quot; data-end=&quot;777&quot; data-start=&quot;733&quot;&gt;Calendar에서 넘겨준&lt;span&gt;&amp;nbsp;&lt;/span&gt;localizer&lt;span&gt;&amp;nbsp;&lt;/span&gt;객체가 툴바에도 전달&lt;/li&gt;
&lt;li style=&quot;color: #000000;&quot; data-end=&quot;887&quot; data-start=&quot;778&quot;&gt;툴바는 보통&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;현재 날짜에 맞는 문자열(label)&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;을 표시하기 때문에, &lt;br /&gt;localizer.format(...)&lt;span&gt;&amp;nbsp;&lt;/span&gt;을 직접 호출해서 사용자가 보기 쉬운 텍스트를 만든다.&lt;/li&gt;
&lt;li style=&quot;color: #000000;&quot; data-end=&quot;948&quot; data-start=&quot;888&quot;&gt;&quot;2025년 4월&quot; 한글로 표시하고 싶을 때 : 직접 format을 호출해서 처리&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금 필요한 건 날짜를 &quot;2025년 4월&quot;처럼 포맷 하는 기능이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;color: #000000;&quot; data-start=&quot;265&quot; data-end=&quot;287&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;localizer.format(date, formatString, culture)&lt;/span&gt;을 사용하여 원하는 형태로 날짜 출력 가능하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;866&quot; data-origin-height=&quot;426&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cSrjNz/btsNaNGCeRf/Wgni4cxUAHpK5bKKyAqibk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cSrjNz/btsNaNGCeRf/Wgni4cxUAHpK5bKKyAqibk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cSrjNz/btsNaNGCeRf/Wgni4cxUAHpK5bKKyAqibk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcSrjNz%2FbtsNaNGCeRf%2FWgni4cxUAHpK5bKKyAqibk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;464&quot; height=&quot;228&quot; data-origin-width=&quot;866&quot; data-origin-height=&quot;426&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;하지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캘린더에 &lt;i&gt;&lt;b&gt;&lt;span style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot;&gt;const localizer = dateFnsLocalizer()&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;/i&gt;&lt;span style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;명시도 하고, 툴바에도 맞게 적용했지만 계속해서 'message' 오류가 발생했다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;원인은 &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;dateFnsLocalizer()로 생성한&lt;span&gt;&amp;nbsp;&lt;/span&gt;localizer와&lt;span&gt;&amp;nbsp;&lt;/span&gt;Calendar의 generic 타입이 서로 안 맞아서 생기는 문제&lt;/b&gt;이다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;사실 그 내용을 아주 자세히 파악하지 못했지만 대략 다음과 같이 이해했다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;캘린더에서는 id가 포함된 supabase 데이터를 사용하기 때문에 CalendarEventType(id 포함) 타입을 사용하지만,&lt;br /&gt;툴바에는 id를 사용하지 않는다. 따라서 둘의 타입이 맞지 않아서 생기는 문제&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1804&quot; data-origin-height=&quot;718&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0zlA8/btsNbbgmpUm/f6gUWzewXHTOeUnfX6kWH1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0zlA8/btsNbbgmpUm/f6gUWzewXHTOeUnfX6kWH1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0zlA8/btsNbbgmpUm/f6gUWzewXHTOeUnfX6kWH1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0zlA8%2FbtsNbbgmpUm%2Ff6gUWzewXHTOeUnfX6kWH1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1804&quot; height=&quot;718&quot; data-origin-width=&quot;1804&quot; data-origin-height=&quot;718&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 문제를 다음과 같이 해결하였다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;툴바의 타입을 캘린더와 같게 명시해주기 =&amp;gt; 캘린더의 components &amp;gt; toolbar 에서 타입 오류 사라짐&lt;/li&gt;
&lt;li&gt;date-fns에서 한글 날짜값 가져오기&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1744014611140&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { CalendarEventType } from '@/types/plans';
import { format } from 'date-fns';
import { ko } from 'date-fns/locale';
import { ToolbarProps } from 'react-big-calendar';

//⭐️캘린더와 타입 맞춤(id 유실로 인한 오류)⭐️ 
const CustomToolbar = ({ date, onNavigate }: ToolbarProps&amp;lt;CalendarEventType&amp;gt;) =&amp;gt; {
  const customLabel = format(date, 'yyyy MMMM', { locale: ko });
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; onNavigate('PREV')}&amp;gt;⬅️&amp;lt;/button&amp;gt;
      &amp;lt;span&amp;gt;{customLabel}&amp;lt;/span&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; onNavigate('NEXT')}&amp;gt;➡️&amp;lt;/button&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; onNavigate('TODAY')}&amp;gt;Today&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

export default CustomToolbar;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어차피 커스터마이징 하는 것이기 때문에 꼭 localizer에 얽메이지 않고 date-fns에서 한글로 맞게 번역하는 방법을 택했다.&lt;/p&gt;</description>
      <category>Project</category>
      <author>채._.</author>
      <guid isPermaLink="true">https://coco910.tistory.com/126</guid>
      <comments>https://coco910.tistory.com/126#entry126comment</comments>
      <pubDate>Mon, 7 Apr 2025 17:31:46 +0900</pubDate>
    </item>
    <item>
      <title>[사람별_Day8] Tanstack Query 초기 로딩 처리 - initialData</title>
      <link>https://coco910.tistory.com/125</link>
      <description>&lt;h3 style=&quot;padding: 0.4em 1em 0.1em 0.2em; margin: 0.5em 0em; color: #000; border-bottom: 2px #888 dotted; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;이전 상황 : Tanstack Query 적용 중 undefined 방지&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useEffect로 처리했던 이전 로직을 tanstack으로 관리하기 위해 커스텀 훅으로 뺐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 로직은 formatted 로직을 제외한, supabase에서 데이터를 받아오는 부분만 커스텀으로 뺐을 때의 로직이다.&lt;/p&gt;
&lt;pre id=&quot;code_1743949434420&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;'use client';
import { Calendar, dateFnsLocalizer } from 'react-big-calendar';
import { format, parse, startOfWeek, getDay } from 'date-fns';
import { enUS } from 'date-fns/locale/en-US';
import { ko } from 'date-fns/locale/ko';
import { useState } from 'react';
import { CalendarEventType, PlansType } from '@/types/plans';
import { useGetCalendarPlans } from '@/hooks/useGetCalendarPlans';

// 로케일(지역화) 설정
const locales = {
  'en-US': enUS, //미국-영어 : 미국식 날짜
  'ko-KR': ko, //한국-한글 : 한국식 날짜
};

const localizer = dateFnsLocalizer({
  format,
  parse,
  startOfWeek,
  getDay,
  locales,
});

const MainCalendar = () =&amp;gt; {
  const [events, setEvents] = useState&amp;lt;CalendarEventType[]&amp;gt;([]);

  const { data: plans, isPending, isError, error } = useGetCalendarPlans();

  const fetchEvents = async () =&amp;gt; {
    const formatted = plans.map(
      (plan: PlansType): CalendarEventType =&amp;gt; ({
        id: plan.plan_id,
        title: plan.title,
        start: new Date(plan.start_date),
        end: new Date(plan.end_date),
      })
    );
    setEvents(formatted);
  };

  if (isPending) {
    return &amp;lt;div&amp;gt;로딩 중입니다...&amp;lt;/div&amp;gt;;
  }

  if (isError) {
    return &amp;lt;div&amp;gt;캘린더 에러 발생 : {error.message}&amp;lt;/div&amp;gt;;
  }

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;Calendar
        localizer={localizer}
        events={events} // 여기에 이벤트 추가 가능
        startAccessor='start'
        endAccessor='end'
        style={{ height: 500 }}
      /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

export default MainCalendar;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때, plans의 값이 undefined가 될 수도 있다는 eslint의 감시가 있었다..!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 처리하기 위해서 &lt;b&gt;useQuery에 타입을 명시함&lt;/b&gt;과 함께 &lt;b&gt;initialData&lt;/b&gt;을 추가하여 초기값을 빈 배열로 주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;이렇게 하면 훅에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;data&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;는 항상&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;PlansType[]&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;이고,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;undefined&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;가 되지 않는다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1743949666173&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;useQuery&amp;lt;PlansType[]&amp;gt;({
  queryKey: ['calendarPlans'],
  queryFn: fetchPlans,
  initialData: [], // ✅ plans는 항상 [] 이상의 배열이 됨
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;padding: 0.4em 1em 0.1em 0.2em; margin: 0.5em 0em; color: #000; border-bottom: 2px #888 dotted; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;문제 상황 : 캘린더에 일정이 나타나지 않음&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 문제가 발생했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캘린더에 일정이 나타나지 않는 다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어디서부터 문제가 있는 것인지 콘솔을 모두 찍어보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;supabase에서 데이터를 가져오는 로직, useQuery에서 적용하는 부분, 캘린더 컴포넌트&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;콘솔 찍은 부분들&lt;/p&gt;
&lt;pre id=&quot;code_1743949867744&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// plans 데이터 가져오기 - calendar 사용
export const getPlans = async (): Promise&amp;lt;PlansType[]&amp;gt; =&amp;gt; {
  const { data: plans, error } = await supabase
    .from('plans')
    .select('plan_id, user_id, contacts_id, title, detail, priority, start_date, end_date');
  console.log('getPlans: plans', plans); ⭐️
  if (error) {
    throw new Error(error.message);
  }
  return plans;
};&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1743949904867&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export const useGetCalendarPlans = () =&amp;gt; {
  return useQuery&amp;lt;CalendarEventType[]&amp;gt;({
    queryKey: [QUERY_KEY.CALENDAR_PLANS],
    queryFn: async () =&amp;gt; {
      const plans: PlansType[] = await getPlans(); // supabase에서 plans 데이터 가져오기
      console.log('useQuery: plans', plans); ⭐️

      // 캘린더에 적용되도록 데이터 가공하기
      const events: CalendarEventType[] = plans.map((plan) =&amp;gt; ({
        id: plan.plan_id,
        title: plan.title,
        start: new Date(plan.start_date),
        end: new Date(plan.end_date),
      }));
      console.log('useQuery: events', events); ⭐️
      return events;
    },
    initialData: [], // undefined 아님 명시
    staleTime: 60 * 1000, // 1분
  });
};&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1743949936325&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const MainCalendar = () =&amp;gt; {
  const { data: events, isPending, isError, error } = useGetCalendarPlans();
  console.log('MainCalendar: events', events); ⭐️&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2812&quot; data-origin-height=&quot;1022&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/n3d33/btsNaFBn0xs/hPz8jk7d9tsFwz3kqItXA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/n3d33/btsNaFBn0xs/hPz8jk7d9tsFwz3kqItXA0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/n3d33/btsNaFBn0xs/hPz8jk7d9tsFwz3kqItXA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fn3d33%2FbtsNaFBn0xs%2FhPz8jk7d9tsFwz3kqItXA0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2812&quot; height=&quot;1022&quot; data-origin-width=&quot;2812&quot; data-origin-height=&quot;1022&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 빈 배열로 찍히면서 캘린더에도 일정이 나타나지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 원인을 찾으면서 &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;시간이 좀 지나자&lt;/b&gt;&lt;/span&gt;, 갑자기 일정과 콘솔이 다시 나타났다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2806&quot; data-origin-height=&quot;1010&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bE0xSd/btsNbNSx23H/i8QYzjHejGoOgn9Q1tkcM0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bE0xSd/btsNbNSx23H/i8QYzjHejGoOgn9Q1tkcM0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bE0xSd/btsNbNSx23H/i8QYzjHejGoOgn9Q1tkcM0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbE0xSd%2FbtsNbNSx23H%2Fi8QYzjHejGoOgn9Q1tkcM0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2806&quot; height=&quot;1010&quot; data-origin-width=&quot;2806&quot; data-origin-height=&quot;1010&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뭘까.. 뭘까...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뭔가 내가 설정한 1분의 staleTime과 관련이 있을 것 같았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;padding: 0.4em 1em 0.1em 0.2em; margin: 0.5em 0em; color: #000; border-bottom: 2px #888 dotted; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;오류 원인 및 &lt;b&gt;initialData 용도&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 새로고침 시 콘솔이 찍히는 모습은 아래와 같이 빈 값(undefined)이 들어왔다가 값이 들어온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원인은 바로 이것이었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;934&quot; data-origin-height=&quot;417&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4lvQr/btsNa2CTu2q/IAe278VU3x36ODAfq0KVvk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4lvQr/btsNa2CTu2q/IAe278VU3x36ODAfq0KVvk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4lvQr/btsNa2CTu2q/IAe278VU3x36ODAfq0KVvk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4lvQr%2FbtsNa2CTu2q%2FIAe278VU3x36ODAfq0KVvk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;934&quot; height=&quot;417&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;934&quot; data-origin-height=&quot;417&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;initialData 를 빈 배열로 설정해두었으니, 처음 빈 배열인 값을 캐싱해두는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 후 staleTime(1분)이 지나면 새롭게 값을 페칭하기 때문에 데이터가 들어오는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;initialData의 용도&lt;/b&gt;&lt;/span&gt;는 &lt;span style=&quot;color: #ee2323;&quot;&gt;새로운 값을 받아오기 전에 빠르게 값을 보여주기 위한 것&lt;/span&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;서버 사이드 렌더링 데이터 있거나, &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;부모 컴포넌트에서 데이터 내려주는 등 &lt;b&gt;진짜 초기값&lt;/b&gt;을 위해 쓰이게 되는 것이다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;</description>
      <category>Forntend/Trouble Shooting</category>
      <author>채._.</author>
      <guid isPermaLink="true">https://coco910.tistory.com/125</guid>
      <comments>https://coco910.tistory.com/125#entry125comment</comments>
      <pubDate>Sun, 6 Apr 2025 23:43:56 +0900</pubDate>
    </item>
    <item>
      <title>[사람별_Day6] 캘린더에 일정 나타내기 - UTC</title>
      <link>https://coco910.tistory.com/124</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;RBC에 대한 기본적인 기능 및 설정은 얼추 마무리되고, 실제 데이터와 연동하고자 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;supabase 테이블에 적용된 데이터를 가져와서 캘린더에 출력해보았다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;supabase 테이블 연결 설정은 간략히 아래와 같이 진행했다.&lt;/p&gt;
&lt;div style=&quot;width: 500px; padding: 15px 20px; border-radius: 10px; border: 1px solid #d9d9d9; margin: 10px 10px; box-shadow: 2px 2px 2px #999;&quot;&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;페이지, 컴포넌트 생성 및 연결&lt;/li&gt;
&lt;li&gt;supabaseClient 생성 (supabaseClient.ts)&lt;/li&gt;
&lt;li&gt;plans 테이블 값 불러오기 로직 (supabaseApi.ts)&lt;/li&gt;
&lt;li&gt;plans 타입 지정&lt;/li&gt;
&lt;li&gt;컴포넌트에서 호출 : useState, useEffect&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;border-bottom: 2px solid #424242; text-align: left; color: #424242; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #fff; position: relative; padding: 10px 15px 10px 0px; bottom: -19px;&quot;&gt;new Date()&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기값을 빈 배열로 두어 이벤트 상태를 관리하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 state의 타입을 &amp;lt;plans[]&amp;gt;로 지정하였을 때 에러가 발생했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에러는 다음과 같이 나타났다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;이 호출과 일치하는 오버로드가 없습니다. '&quot;start&quot;' 형식은~ , '&quot;end&quot;' 형식은~&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;에러 원인은 supabase의 데이터 값과&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;RBC가 &lt;b&gt;필요한 값이 다르기 때문&lt;/b&gt;이다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;supabase : start_date와 end_date 모두 &lt;b&gt;string&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;RBC : start와 end 모두 &lt;b&gt;Date&lt;/b&gt; 필요&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 해결하기 위한 방법은 supabase의 값을 알맞게 매핑하기.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, Date 객체로 변환하기 위해 new Date()를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리하자면&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;new Date() 사용 이유&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;number&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;와&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;string&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;형식은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;Date&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;형식에 할당할 수 없다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;즉, RBC에서는 &quot;&lt;/span&gt;start&quot;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;와&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&quot;end&quot;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;필드가&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;Date&lt;span&gt;&amp;nbsp;&lt;/span&gt;객체여야 한다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;Supabase에서 받아오는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;start_date&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;와&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;end_date의 type은&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&amp;nbsp;&lt;/span&gt;string.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;이 둘을 직접&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;Date&lt;span&gt;&amp;nbsp;&lt;/span&gt;객체로 변환 : new Date()&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;state 타입은 &amp;lt;CalendarEvent[]&amp;gt;로 변경한다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt; Type과 최종 로직&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1743756539549&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export interface PlansType {
  plan_id: string;
  created_at: number;
  user_id: string;
  contacts_id: string;
  start_date: string;
  end_date: string;
  title: string | null;
  detail: string | null;
  priority: string | null;
}

export interface CalendarEventType {
  id: string;
  title: string | null;
  start: Date;
  end: Date;
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1743756551307&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// src &amp;gt; components &amp;gt; commons &amp;gt; calendar.tsx

'use client';
import { Calendar, dateFnsLocalizer } from 'react-big-calendar';
import { format, parse, startOfWeek, getDay } from 'date-fns';
import { enUS } from 'date-fns/locale/en-US';
import { ko } from 'date-fns/locale/ko';
import { getPlans } from '@/lib/utils/supabaseApi';
import { useEffect, useState } from 'react';
import { CalendarEventType } from '@/types/plans';

// 로케일(지역화) 설정
const locales = {
  'en-US': enUS, //미국-영어 : 미국식 날짜
  'ko-KR': ko, //한국-한글 : 한국식 날짜
};

const localizer = dateFnsLocalizer({
  format,
  parse,
  startOfWeek,
  getDay,
  locales,
});

const MainCalendar = () =&amp;gt; {
  const [events, setEvents] = useState&amp;lt;CalendarEventType[]&amp;gt;([]);

  useEffect(() =&amp;gt; {
    const fetchEvents = async () =&amp;gt; {
      try {
        const plans = await getPlans();

        const formatted = plans.map(
          (plan): CalendarEventType =&amp;gt; ({
            id: plan.plan_id,
            title: plan.title,
            start: new Date(plan.start_date),
            end: new Date(plan.end_date),
          })
        );
        setEvents(formatted);
      } catch (error) {
        console.error('일정 목록 가져오기 에러', error);
      }
    };
    fetchEvents();
  }, []);

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;Calendar
        localizer={localizer}
        events={events} // 여기에 이벤트 추가 가능
        startAccessor='start'
        endAccessor='end'
        style={{ height: 500 }}
      /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

export default MainCalendar;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;border-bottom: 2px solid #424242; text-align: left; color: #424242; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #fff; position: relative; padding: 10px 15px 10px 0px; bottom: -19px;&quot;&gt;달력과 DB의 값 차이 : UTC&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;supabase 데이터 중 시작일과 종료일이 달력과 다르게 나타났다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;929&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bXnRdh/btsM8izVnsR/MKiAJLsCHT96bVjP6RHhVk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bXnRdh/btsM8izVnsR/MKiAJLsCHT96bVjP6RHhVk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bXnRdh/btsM8izVnsR/MKiAJLsCHT96bVjP6RHhVk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbXnRdh%2FbtsM8izVnsR%2FMKiAJLsCHT96bVjP6RHhVk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;929&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;929&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;972&quot; data-origin-height=&quot;280&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JZ09Z/btsM8HsG1yp/0eyiEtK0DxWNQrBVmwlY61/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JZ09Z/btsM8HsG1yp/0eyiEtK0DxWNQrBVmwlY61/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JZ09Z/btsM8HsG1yp/0eyiEtK0DxWNQrBVmwlY61/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJZ09Z%2FbtsM8HsG1yp%2F0eyiEtK0DxWNQrBVmwlY61%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;972&quot; height=&quot;280&quot; data-origin-width=&quot;972&quot; data-origin-height=&quot;280&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;supabase의 데이터 타입을 &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;b&gt;timestamptz&lt;/b&gt; (타임스탬프 with 타임존)으로 두면&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;값이 추가될 때 자체적으로 &lt;b&gt;세계표준시간(UTC)을 적용&lt;/b&gt;한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나의 입장에서는 &lt;span style=&quot;background-color: #c0d1e7;&quot;&gt;&lt;b&gt;데이터 -&amp;gt; 브라우저 출력&lt;/b&gt;&lt;/span&gt; 이다. 때문에 테이블에 저장된 값으로부터 하루가 밀려서 출력되는 것으로 느껴지지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 입장에서는 &lt;b&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;브라우저 동작 -&amp;gt; 데이터 저장&lt;/span&gt;&lt;/b&gt; 이다. 때문에 실제 사용자는 9일 밥 먹기 일정을 추가했지만 &lt;b&gt;&lt;u&gt;-9시간(&lt;b&gt;&lt;u&gt;한국 기준&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/u&gt;&lt;/b&gt;UTC처리)&lt;/u&gt;&lt;/b&gt;가 되어 supabase에 저장이 되는 것이다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;(브라우저)4월 9일 00:00:00 로 선택 -&amp;gt; (supabase) 4월 8일 15:00:00 저장&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;padding: 0.4em 1em 0.1em 0.2em; margin: 0.5em 0em; color: #000; border-bottom: 2px #888 dotted; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;연속 일정 처리 - 종료일 정각&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 사용자가 선택한 시간에 맞추어 달력에 잘 출력된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이는 단일 일정에만 적용이 되고, &lt;b&gt;연속 일정&lt;/b&gt;에는 조금 다른 처리를 해주어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1434&quot; data-origin-height=&quot;1014&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dkFMwM/btsM89vRC1O/kCOw7ckJzy45JCEc7rkpx1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dkFMwM/btsM89vRC1O/kCOw7ckJzy45JCEc7rkpx1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dkFMwM/btsM89vRC1O/kCOw7ckJzy45JCEc7rkpx1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdkFMwM%2FbtsM89vRC1O%2FkCOw7ckJzy45JCEc7rkpx1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;670&quot; height=&quot;474&quot; data-origin-width=&quot;1434&quot; data-origin-height=&quot;1014&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 test 일정은 모두 5일 일정이다. 하지만 test-15는 4일 일정으로 출력된다. 왜 그럴까??&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론부터 말하자면 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;종료일 시간이 정각이기 때문&lt;/b&gt;&lt;/span&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1730&quot; data-origin-height=&quot;482&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wcTbQ/btsM9wEf5Zw/kMqC0ZrlwcOEUe80e3jS5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wcTbQ/btsM9wEf5Zw/kMqC0ZrlwcOEUe80e3jS5K/img.png&quot; data-alt=&quot;test-15 시작일&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wcTbQ/btsM9wEf5Zw/kMqC0ZrlwcOEUe80e3jS5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwcTbQ%2FbtsM9wEf5Zw%2FkMqC0ZrlwcOEUe80e3jS5K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;706&quot; height=&quot;482&quot; data-origin-width=&quot;1730&quot; data-origin-height=&quot;482&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;test-15 시작일&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1458&quot; data-origin-height=&quot;496&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6dnza/btsM9wc7B6j/WwVT3nyCY58VzwiAIMwlt1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6dnza/btsM9wc7B6j/WwVT3nyCY58VzwiAIMwlt1/img.png&quot; data-alt=&quot;test-15 종료일&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6dnza/btsM9wc7B6j/WwVT3nyCY58VzwiAIMwlt1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6dnza%2FbtsM9wc7B6j%2FWwVT3nyCY58VzwiAIMwlt1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;698&quot; height=&quot;237&quot; data-origin-width=&quot;1458&quot; data-origin-height=&quot;496&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;test-15 종료일&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;test-15의 일정 데이터이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; background-color: #f6e199;&quot;&gt;&lt;b&gt;일정 시작일&lt;/b&gt;&lt;/span&gt;을 보면 UTC가 적용되어 13일로 저장되어있지만 formatted 값은 14일 정각이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; background-color: #ffc1c8;&quot;&gt;&lt;b&gt;일정 종료일&lt;/b&gt;&lt;/span&gt;을 보면 UTC가 적용되어 17일로 저장되어있다. 마찬가지로&amp;nbsp;formatted 값을 보면 18일 정각이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시작일은 formatted 데이터가 정각이어도 시작하는 날이기에 정상적으로 표시되지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;종료일은 정각 시간이 적용되어&lt;/span&gt; 18일 이전까지만 달력에 표시&lt;/span&gt;&lt;/b&gt;된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 종료일을 1시간 정도 늘려 적용시키면(일정 입력 기능 담당 팀원 분이 적용)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1442&quot; data-origin-height=&quot;618&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bep49p/btsM8WpWxrR/FbxQteLQn2TYr9fNNFosLk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bep49p/btsM8WpWxrR/FbxQteLQn2TYr9fNNFosLk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bep49p/btsM8WpWxrR/FbxQteLQn2TYr9fNNFosLk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbep49p%2FbtsM8WpWxrR%2FFbxQteLQn2TYr9fNNFosLk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;679&quot; height=&quot;291&quot; data-origin-width=&quot;1442&quot; data-origin-height=&quot;618&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;종료일이 18일 오전 1시로 설정되어 위의 달력처럼(test-16) 잘 출력된다.&lt;/p&gt;</description>
      <category>Project</category>
      <author>채._.</author>
      <guid isPermaLink="true">https://coco910.tistory.com/124</guid>
      <comments>https://coco910.tistory.com/124#entry124comment</comments>
      <pubDate>Fri, 4 Apr 2025 17:49:24 +0900</pubDate>
    </item>
    <item>
      <title>[사람별_Day5] RBC 파헤치기 4 - 기존 스타일과 커스터마이징</title>
      <link>https://coco910.tistory.com/123</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://coco910.tistory.com/122&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;RBC의 렌더링 방법(CRS)&lt;/a&gt;까지 파악하여 캘린더 형태를 출력하였다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RBC의 기본적인 스타일이며, 커스터마이징을 위한 내용들을 정리한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1966&quot; data-origin-height=&quot;1128&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/byrtp0/btsM6FIgvyo/cGeGSwufAo05THYPeC2TXk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/byrtp0/btsM6FIgvyo/cGeGSwufAo05THYPeC2TXk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/byrtp0/btsM6FIgvyo/cGeGSwufAo05THYPeC2TXk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbyrtp0%2FbtsM6FIgvyo%2FcGeGSwufAo05THYPeC2TXk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1966&quot; height=&quot;1128&quot; data-origin-width=&quot;1966&quot; data-origin-height=&quot;1128&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;border-bottom: 2px solid #424242; text-align: left; color: #424242; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #fff; position: relative; padding: 10px 15px 10px 0px; bottom: -19px;&quot;&gt;RBC의 기본 스타일&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;background-color: #e6f5ff; color: #0070d1; text-align: start;&quot; href=&quot;https://www.npmjs.com/package/react-big-calendar&quot;&gt;RBC npm 문서&lt;/a&gt;를 읽던 중 SASS에 대해 알게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문서에는 다음과 같이 나와있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1472&quot; data-origin-height=&quot;766&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dSMTwl/btsM8ev7l6D/jwUtNcGHf4vMhDkCD6KDh1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dSMTwl/btsM8ev7l6D/jwUtNcGHf4vMhDkCD6KDh1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dSMTwl/btsM8ev7l6D/jwUtNcGHf4vMhDkCD6KDh1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdSMTwl%2FbtsM8ev7l6D%2FjwUtNcGHf4vMhDkCD6KDh1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;676&quot; height=&quot;766&quot; data-origin-width=&quot;1472&quot; data-origin-height=&quot;766&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 말인즉슨,&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;RBC의 &lt;b&gt;기본 스타일&lt;/b&gt;이 적용된 CSS 파일(&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;react-big-calendar.css&lt;/span&gt;)이 있고 이를 바로 import 하여 접근할 수 있다.&lt;/li&gt;
&lt;li&gt;하지만 스타일을 커스터마이징하려면 &lt;b&gt;SASS 파일을 수정&lt;/b&gt;해야 한다.&lt;/li&gt;
&lt;li&gt;이렇게 수정한 SASS 파일은 &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;b&gt;새로운 CSS 파일을 빌드&lt;/b&gt;하며 직접 import 한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;초기 세팅 중 아래와 같이 캘린더 컴포넌트에 인라인 스타일 요소를 적용시켰다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;공식 문서에 나와있듯 달력의 컨테이너 요소에 높이를 주어야 화면에 나타나기 때문이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;이때 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;CSS 파일을 import를 했기 때문에 기본 스타일에 접근&lt;/b&gt;&lt;/span&gt;할 수 있었던 것이다!&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1743662039037&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import &quot;react-big-calendar/lib/css/react-big-calendar.css&quot;;

	&amp;lt;Calendar
        localizer={localizer}
        events={events}
        startAccessor=&quot;start&quot;
        endAccessor=&quot;end&quot;
        style={{ height: 500 }} // ✅ 달력 높이(달력 나타내기 위함)
      /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;높이를 주지 않으면 다음과 같이 출력된다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1850&quot; data-origin-height=&quot;326&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/co1QbB/btsM8aAB0Fg/kckHJgoUr5U2hWIyzeECkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/co1QbB/btsM8aAB0Fg/kckHJgoUr5U2hWIyzeECkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/co1QbB/btsM8aAB0Fg/kckHJgoUr5U2hWIyzeECkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fco1QbB%2FbtsM8aAB0Fg%2FkckHJgoUr5U2hWIyzeECkK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1850&quot; height=&quot;326&quot; data-origin-width=&quot;1850&quot; data-origin-height=&quot;326&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;padding: 0.4em 1em 0.1em 0.2em; margin: 0.5em 0em; color: #000; border-bottom: 2px #888 dotted; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;RBC에서 기본적으로 제공하는 스타일 요소들&lt;/span&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;가장 기본 형태의 CSS 요소를 7개의 섹션으로 나누어 살펴보겠다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;2141&quot; data-origin-height=&quot;1188&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kBStV/btsM6YVaT1d/d65wpwnLxb5AMdH5f9TcjK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kBStV/btsM6YVaT1d/d65wpwnLxb5AMdH5f9TcjK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kBStV/btsM6YVaT1d/d65wpwnLxb5AMdH5f9TcjK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkBStV%2FbtsM6YVaT1d%2Fd65wpwnLxb5AMdH5f9TcjK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2141&quot; height=&quot;1188&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;2141&quot; data-origin-height=&quot;1188&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&lt;b&gt;1.&lt;/b&gt; &lt;span style=&quot;color: #000000;&quot;&gt;캘린더 전체 레이아웃 (&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;.rbc-calendar&lt;span style=&quot;text-align: start;&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;color: #000000;&quot; data-end=&quot;206&quot; data-start=&quot;179&quot;&gt;display: flex;로 구성되어 있음&lt;/li&gt;
&lt;li style=&quot;color: #000000;&quot; data-end=&quot;258&quot; data-start=&quot;207&quot;&gt;내부적으로&lt;span&gt;&amp;nbsp;&lt;/span&gt;rbc-toolbar,&lt;span&gt;&amp;nbsp;&lt;/span&gt;rbc-month-view&lt;span&gt;&amp;nbsp;&lt;/span&gt;등의 요소를 포함&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1743664151636&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.rbc-calendar {
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  height: 100%;
  font-family: sans-serif;
  font-size: 14px;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&lt;b&gt;2. &lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;툴바 스타일 (&lt;/span&gt;.rbc-toolbar&lt;span style=&quot;text-align: start;&quot;&gt;)&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;color: #000000;&quot; data-end=&quot;486&quot; data-start=&quot;460&quot;&gt;월 변경 버튼과 현재 날짜 표시하는 부분&lt;/li&gt;
&lt;li style=&quot;color: #000000;&quot; data-end=&quot;504&quot; data-start=&quot;487&quot;&gt;버튼 기본 스타일 적용됨&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1743664190485&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.rbc-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 10px;
}

.rbc-toolbar button {
  background-color: #f7f7f7;
  border: 1px solid #ddd;
  padding: 5px 10px;
  cursor: pointer;
}

// 기본 버튼 스타일
.rbc-toolbar button:hover {
  background-color: #e6e6e6;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-end=&quot;880&quot; data-start=&quot;845&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #f3c000;&quot;&gt;&lt;b&gt;3.&lt;/b&gt; &lt;span style=&quot;color: #000000;&quot;&gt;요일 헤더 스타일 (.rbc-header)&lt;/span&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc; color: #000000; text-align: start;&quot; data-end=&quot;917&quot; data-start=&quot;881&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;917&quot; data-start=&quot;881&quot;&gt;요일 표시하는 부분 (예: &quot;일&quot;, &quot;월&quot;, &quot;화&quot;, &amp;hellip;)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1743664262786&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.rbc-header {
  text-align: center;
  padding: 10px;
  background-color: #f7f7f7;
  border-bottom: 1px solid #ddd;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-end=&quot;1087&quot; data-start=&quot;1053&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;4.&lt;/span&gt; &lt;/b&gt;날짜 셀 스타일 (.rbc-day-bg)&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc; color: #000000; text-align: start;&quot; data-end=&quot;1108&quot; data-start=&quot;1088&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1108&quot; data-start=&quot;1088&quot;&gt;각 날짜 칸에 적용되는 스타일&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1743664322583&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.rbc-day-bg {
  border-right: 1px solid #ddd;
}

// 주말 스타일
.rbc-day-bg.rbc-off-range-bg {
  background-color: #f3f3f3;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-end=&quot;1295&quot; data-start=&quot;1263&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;5. &lt;/b&gt;&lt;/span&gt;이벤트 스타일 (.rbc-event)&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc; color: #000000; text-align: start;&quot; data-end=&quot;1321&quot; data-start=&quot;1296&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1321&quot; data-start=&quot;1296&quot;&gt;캘린더에 표시되는 일정(이벤트) 스타일&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1743664401144&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.rbc-event {
  background-color: #3174ad; /* 기본 파란색 */
  color: white;
  padding: 4px;
  border-radius: 4px;
}

// 마우스 오버 시 스타일
.rbc-event:hover {
  background-color: #265985;
}

// 연속 이벤트 스타일
.rbc-event-continues-before {
  border-top-left-radius: 0;
  border-bottom-left-radius: 0;
}
.rbc-event-continues-after {
  border-top-right-radius: 0;
  border-bottom-right-radius: 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-end=&quot;1845&quot; data-start=&quot;1812&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;6.&lt;/span&gt; &lt;/b&gt;현재 날짜 강조 (.rbc-today)&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc; color: #000000; text-align: start;&quot; data-end=&quot;1862&quot; data-start=&quot;1846&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1862&quot; data-start=&quot;1846&quot;&gt;오늘 날짜 배경색 강조&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1743664417418&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.rbc-today {
  background-color: rgba(0, 0, 255, 0.1);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-end=&quot;1981&quot; data-start=&quot;1938&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #8a3db6;&quot;&gt;7.&lt;/span&gt;&lt;/b&gt; 선택된 날짜 스타일 (.rbc-selected-cell)&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc; color: #000000; text-align: start;&quot; data-end=&quot;2008&quot; data-start=&quot;1982&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2008&quot; data-start=&quot;1982&quot;&gt;드래그로 날짜 범위 선택 시 강조 표시됨&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1743679684981&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.rbc-selected-cell {
  background-color: rgba(0, 0, 255, 0.2);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;padding: 0.4em 1em 0.1em 0.2em; margin: 0.5em 0em; color: #000; border-bottom: 2px #888 dotted; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;기존 스타일을 덮어쓰지 않고 유지하는 방법&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 본 7가지의 기존 스타일을 유지하고, 그 이외의 부분에 새로운 스타일을 적용하고 싶다면?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;1. 기존 스타일을 &lt;b&gt;확장&lt;/b&gt;하는 방식&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 스타일의 이름을 확장하여 새로운 스타일을 적용한다.&lt;/p&gt;
&lt;pre id=&quot;code_1743665097482&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.rbc-calendar {
  background-color: white; // 기존 스타일 유지
}
.rbc-calendar.custom {
  border: 2px solid red;  // 추가적인 스타일만 적용
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;2. 기존 스타일 &lt;b&gt;상속&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;@extend&lt;/b&gt;를 사용하여 기존의 스타일을 물려받아 새로운 스타일 이름을 지정한다.&lt;/p&gt;
&lt;pre id=&quot;code_1743665197297&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.custom-calendar {
  @extend .rbc-calendar; 
  border: 2px solid red;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;3. &lt;b&gt;CSS&lt;/b&gt; &lt;b&gt;변수&lt;/b&gt; 활용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;caret-color: #000000;&quot;&gt;RBC의 기&lt;b&gt;존 SCSS 변수를 변경&lt;/b&gt;하려면 &lt;span style=&quot;background-color: #c0d1e7;&quot;&gt;$변수&lt;/span&gt;를 사용하지만 &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;caret-color: #000000;&quot;&gt;&lt;b&gt;새로운 스타일을 추가&lt;/b&gt;할 때는 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;--변수&lt;/span&gt;로 선언하고 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;var(--변수)&lt;/span&gt;로 사용한다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1743665234714&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// styles.scss (기존 RBC 스타일 수정)
@import 'react-big-calendar/lib/sass/styles';

$event-background-color: red;  // 기존 이벤트 배경색 변경

.rbc-event {
  background-color: $event-background-color !important;
}



// styles.css (새로운 스타일 지정)
:root {
  --custom-event-bg: blue; // 새로운 CSS 변수
}

.my-custom-event {
  background-color: var(--custom-event-bg) !important;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;border-bottom: 2px solid #424242; text-align: left; color: #424242; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #fff; position: relative; padding: 10px 15px 10px 0px; bottom: -19px;&quot;&gt;RBC의 SASS&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;상단에 적은 것처럼 RBC의 스타일을 커스터마이징하려면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;SASS 파일을 수정&lt;/b&gt;해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 SASS는 무엇이며 어떻게 작동하는 것일까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;padding: 0.4em 1em 0.1em 0.2em; margin: 0.5em 0em; color: #000; border-bottom: 2px #888 dotted; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;SASS와 &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;SCSS&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;469&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OLVhH/btsM6sWLyBP/IxVoCzGlCY3LEnDREdBEck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OLVhH/btsM6sWLyBP/IxVoCzGlCY3LEnDREdBEck/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OLVhH/btsM6sWLyBP/IxVoCzGlCY3LEnDREdBEck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOLVhH%2FbtsM6sWLyBP%2FIxVoCzGlCY3LEnDREdBEck%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;612&quot; height=&quot;224&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;469&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;RBC 내부적&lt;/b&gt;&lt;/span&gt;으로 커스터마이징을 위해서는 SASS 파일을 수정해야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;커스터마이징 한 스타일을 &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;브라우저&lt;/b&gt;&lt;/span&gt;에 적용하기 위해서는 CSS 파일이 필요하다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;브라우저는 직접 SCSS 파일을 이해하지 못한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;즉, &lt;b&gt;SCSS 파일을 브라우저에서 사용하기 위해서&lt;/b&gt;는&lt;/span&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&amp;nbsp;SCSS 파일을 CSS 파일로 컴파일하여 브라우저가 이해할 수 있는 CSS 파일로 변환해야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;RBC와 브라우저의 간극을 해결해주는 것이 Sass 이다.&lt;/span&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 &lt;b&gt;Sass라는 것은 &lt;span style=&quot;color: #ee2323;&quot;&gt;CSS 전처리기 도구&lt;/span&gt;이며, &lt;span style=&quot;color: #ee2323;&quot;&gt;RBC의 커스터마이징을 브라우저에 적용&lt;/span&gt;할 수 있도록 도와준다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Sass(=SASS)는 CSS 전처리기 도구이다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;SCSS&lt;/b&gt;는 Sass에서 지원하는&lt;b&gt; 문법 스타일 형식&lt;/b&gt;이다.&lt;/li&gt;
&lt;li&gt;RBC 내부 스타일 파일은 SCSS(.scss) 형식으로 되어있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1743681447366&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Sass(.sass)
$primary-color: blue

body
  background: $primary-color

nav
  ul
    margin: 0
    padding: 0

// SCSS(.scss)
$primary-color: blue;

body {
  background: $primary-color;
}

nav {
  ul {
    margin: 0;
    padding: 0;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@uuujinnn/Sass%EC%9D%98-%EA%B0%9C%EC%9A%94%EC%99%80-%EC%82%AC%EC%9A%A9-%EB%B0%A9%EB%B2%95&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;참고 자료&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;padding: 0.4em 1em 0.1em 0.2em; margin: 0.5em 0em; color: #000; border-bottom: 2px #888 dotted; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;CSS 적용 우선 순위&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RBC가 갖고있는 기본 스타일과 커스터마이징을 위한 파일까지 알아보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt; 기존 스타일에 접근하지 않고 &lt;b&gt;SASS 파일을 수정&lt;/b&gt;하였을 때, 기존 스타일과 &lt;b&gt;겹치는 부분이 있다면 최종적으로 어떤 것이 반영&lt;/b&gt;될까?&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;width: 500px; padding: 15px 20px; border-radius: 10px; border: 1px solid #d9d9d9; margin: 10px 10px; box-shadow: 2px 2px 2px #999;&quot;&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li style=&quot;color: #000000;&quot; data-start=&quot;893&quot; data-end=&quot;936&quot;&gt;인라인 스타일 (style={{}})&lt;span&gt; &lt;span style=&quot;color: #000000; text-align: left;&quot;&gt;&amp;rarr;&lt;/span&gt;&lt;span style=&quot;color: #000000; text-align: left;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;가장 강함&lt;/li&gt;
&lt;li style=&quot;color: #000000;&quot; data-start=&quot;937&quot; data-end=&quot;966&quot;&gt;ID 선택자 (#calendar)&lt;/li&gt;
&lt;li style=&quot;color: #000000;&quot; data-start=&quot;967&quot; data-end=&quot;1028&quot;&gt;클래스 선택자 (.rbc-calendar) / 속성 선택자 ([type=&quot;text&quot;])&lt;/li&gt;
&lt;li style=&quot;color: #000000;&quot; data-start=&quot;1029&quot; data-end=&quot;1062&quot;&gt;태그 선택자 (div,&lt;span&gt;&amp;nbsp;&lt;/span&gt;span&lt;span&gt;&amp;nbsp;&lt;/span&gt;등)&lt;/li&gt;
&lt;li style=&quot;color: #000000;&quot; data-start=&quot;1063&quot; data-end=&quot;1091&quot;&gt;기본 브라우저 스타일 &lt;span style=&quot;color: #000000; text-align: left;&quot;&gt;&amp;rarr;&lt;/span&gt;&lt;span style=&quot;color: #000000; text-align: left;&quot;&gt;&amp;nbsp;&lt;/span&gt;가장 약함&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약, 기존 CSS와 겹칠 경우 &lt;b&gt;기존 스타일보다 강하게 적용하려면&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;color: #000000;&quot; data-end=&quot;1218&quot; data-start=&quot;1163&quot;&gt;더 강한 선택자 사용 (.my-calendar-class .rbc-event&lt;span&gt;&amp;nbsp;&lt;/span&gt;등)&lt;/li&gt;
&lt;li style=&quot;color: #000000;&quot; data-end=&quot;1245&quot; data-start=&quot;1222&quot;&gt;!important&lt;span&gt;&amp;nbsp;&lt;/span&gt;추가&lt;/li&gt;
&lt;li style=&quot;color: #000000;&quot; data-end=&quot;1284&quot; data-start=&quot;1249&quot;&gt;CSS 파일이 빌드 후 더 나중에 로드되도록 조정&lt;br /&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li style=&quot;color: #000000;&quot; data-end=&quot;1284&quot; data-start=&quot;1249&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;커스텀 CSS를 기존 CSS 이후에 import하기&lt;/span&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1743687464899&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'react-big-calendar/lib/css/react-big-calendar.css'; 
import './my-custom-calendar.scss';  // 커스텀 스타일이 더 나중에 로드됨!&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Project</category>
      <author>채._.</author>
      <guid isPermaLink="true">https://coco910.tistory.com/123</guid>
      <comments>https://coco910.tistory.com/123#entry123comment</comments>
      <pubDate>Thu, 3 Apr 2025 22:38:04 +0900</pubDate>
    </item>
    <item>
      <title>[사람별_Day4] RBC 파헤치기 3 - 캘린더는 왜 CSR?</title>
      <link>https://coco910.tistory.com/122</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;리액트 빅 캘린더(RBC)를 구현하기 전, 캘린더는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;u&gt;사용자와의 상호작용이 많이 필요하기 때문에&lt;/u&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;(이벤트 핸들러) &lt;/span&gt;클라이언트 컴포넌트여야 한다고 생각했다.&lt;/span&gt;&lt;br /&gt;실제로 &quot;use client&quot;를 명시하지 않으면 아래와 같은 오류가 발생했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1454&quot; data-origin-height=&quot;1328&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ckloM1/btsM6Hd4Z4T/kRjDtQxR2h8LNBF9BtNNy1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ckloM1/btsM6Hd4Z4T/kRjDtQxR2h8LNBF9BtNNy1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ckloM1/btsM6Hd4Z4T/kRjDtQxR2h8LNBF9BtNNy1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FckloM1%2FbtsM6Hd4Z4T%2FkRjDtQxR2h8LNBF9BtNNy1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;593&quot; height=&quot;542&quot; data-origin-width=&quot;1454&quot; data-origin-height=&quot;1328&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;padding: 0.4em 1em 0.1em 0.2em; margin: 0.5em 0em; color: #000; border-bottom: 2px #888 dotted; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;CSR인 이유&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오류가 발생했다는 것은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;필히&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;클라이언트 컴포넌트여야 한다는 것인데..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt; &lt;/b&gt;그 이유가 무엇일까?&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;1. RBC&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;는 내부적으로 클래스 컴포넌트를 사용&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;우선 사진과 같은 오류는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;b&gt;클래스 컴포넌트 기반 라이브러리&lt;/b&gt;를&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;서버 컴포넌트에서 렌더링&lt;/b&gt;하려고 할 때 발생한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;클래스 컴포넌트는 서버 컴포넌트에서 동작할 수 있도록 설계되지 않았다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;따라서 RBC는&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&amp;nbsp;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;Next.js의 서버 컴포넌트 환경에서는 이를 직접 실행할 수 없다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;2. 이벤트 핸들링&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;일정 클릭, 드래그 등 사용자와의 상호작용이 필요한 &lt;b&gt;이벤트들은 서버 컴포넌트에서 사용될 수 없다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;3.&lt;span&gt; RBC&lt;/span&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;는 DOM을 조작&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;캘린더 특성상 &lt;b&gt;DOM 요소를 조작&lt;/b&gt;하기 때문에 document와 같은 브라우저 API는 서버 사이드에서 실행할 수 없다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;padding: 0.4em 1em 0.1em 0.2em; margin: 0.5em 0em; color: #000; border-bottom: 2px #888 dotted; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;React-Big-Calendar의 DOM 조작&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;RBC는 React 기반의 라이브러리지만, 내부적으로 브라우저의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;DOM을 직접 조작하는 기능을 포함&lt;/b&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;하고 있다.&lt;/span&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;1. 이벤트 렌더링 및 스타일 적용&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;캘린더 화면을 직접 생성&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;월간/주간 뷰를 전환하면 기존 DOM을 지우고 새롭게 렌더링&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;스타일을 동적으로 적용(&lt;/span&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;eventPropGetter&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;2. 드래그 &amp;amp; 드롭&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용자의 클릭과 드래그를 통해 이동하거나 크기 조절&lt;/li&gt;
&lt;li&gt;마우스 이벤트 감지, 위치 조작&lt;/li&gt;
&lt;li&gt;DOM 이벤트를 직접 핸들링(내부 라이브러리:&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;react-dnd&lt;/span&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;3. 툴팁, 팝업, 모달&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DOM에 새로운 요소 추가, 조작(내부 라이브러리:&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;react-popper&lt;/span&gt;&lt;/span&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;4. 반응형 및 동적 높이 조정&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;브라우저 크기에 따라 동적으로 높이 조정(style 속성을 활용:&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;autoHeight&lt;/span&gt;)&lt;/li&gt;
&lt;li&gt;window.resize 이벤트를 추가하여 DOM을 다시 그리는 반응형 구현&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;br /&gt;그렇다면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;왜 DOM을 조작&lt;/b&gt;하는 것일까?&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;state로 관리하게 되면 필요한 부분만 리렌더링 되어서 사용자에게&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;변동사항을&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;더욱 빠르게 보여줄 수 있는 것 아닐까?&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;한마디로 정리하자면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;u&gt;캘린더의 복잡한 레이아웃과 성능 최적화&lt;/u&gt;&lt;/span&gt;&lt;/b&gt;를 위한 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;state가 변화하면 리렌더링이 일어나게 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;이 동작을 자세히 뜯어보자면&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;페이지 전체 중, 일부만 변동이 일어난다. -&amp;gt; 원본인 DOM과 비교하여 변동된 부분(state)만 감지한다. -&amp;gt; 리렌더링을 통해 변동된 부분을 반영한다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;state로 관리하는 부분이 적거나 확실하다면 이런 방식이 훨씬 효율적이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 RBC는&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;이벤트 하나가 바뀌어도 전체 캘린더 UI를 다시 렌더링 해야 한다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;즉각적으로 반응해야 하는 드래그&amp;amp;드롭은 실시간으로 부드러운 반영이 필요하다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;많은 기능이 있는 캘린더의 경우에는 예상보다 더 많은 요소가 다시 렌더링될 수도 있다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;캘린더와 관련이 없는 부분임에도 캘린더가 자식컴포넌트 입장이 된다면 리렌더링이 발생한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이와 같은 이유들 때문에 state로 관리하게 되면 &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;리렌더링이 불필요하게 발생&lt;/b&gt;&lt;/span&gt;할 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;padding: 0.4em 1em 0.1em 0.2em; margin: 0.5em 0em; color: #000; border-bottom: 2px #888 dotted; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;일정을 State로 관리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;RBC가 DOM(Document Object Model) 조작을 한다면,&lt;/span&gt;&lt;/p&gt;
&lt;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt; state를 어떻게 활용하는 거지? 아니, 사용할 수나 있나??&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;u&gt;&lt;i&gt;쉽게 생각하면 껍데기는 DOM으로 조작, 알맹이는 state로 관리하는 것이다.&lt;/i&gt;&lt;/u&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;일정과 관련된 데이터&lt;/b&gt;&lt;/span&gt;를 state로 관리하고, 이를 캘린더에 반영하는 방식을 사용한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;위에서 살펴보았듯이 &lt;/span&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;RBC&lt;/span&gt;는 events 배열을 기반으로 일정을 표시한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;이 events를 state로 관리하여 동적으로 일정을 관리(추가, 이동, 삭제)하는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;useState의 &lt;b&gt;초기값을 이벤트 배열&lt;/b&gt;로 두고, 일정이 변동되면 &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;setState를 통해 &lt;/span&gt;캘린더 UI가 자동으로 업데이트 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;클릭 이벤트나 API 연동 등을 통해 일정을 동적으로 관리하는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt; 예제 코드:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;events&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;를&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;useState&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;로 관리하기&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1743612106716&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useState } from &quot;react&quot;;
import { Calendar, dateFnsLocalizer } from &quot;react-big-calendar&quot;;
import { format, parse, startOfWeek, getDay } from &quot;date-fns&quot;;

const localizer = dateFnsLocalizer({
  format,
  parse,
  startOfWeek,
  getDay,
  locales: { &quot;en-US&quot;: require(&quot;date-fns/locale/en-US&quot;) },
});


const MyCalendar = () =&amp;gt; {
// ✅ `events`를 state로 관리
  const [events, setEvents] = useState([
    {
      title: &quot;회의&quot;,
      start: new Date(2024, 3, 10, 10, 0), // 4월 10일 10:00 AM
      end: new Date(2024, 3, 10, 12, 0),   // 4월 10일 12:00 PM
    },
  ]);

  // ✅ 새로운 이벤트 추가 함수
  const addEvent = () =&amp;gt; {
    const newEvent = {
      title: &quot;새 일정&quot;,
      start: new Date(2024, 3, 15, 14, 0), // 4월 15일 2:00 PM
      end: new Date(2024, 3, 15, 16, 0),   // 4월 15일 4:00 PM
    };
    setEvents([...events, newEvent]); // 기존 이벤트에 새 이벤트 추가
  };

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;button onClick={addEvent}&amp;gt;새 일정 추가&amp;lt;/button&amp;gt; {/* 버튼 클릭 시 새로운 일정 추가 */}
      &amp;lt;Calendar
        localizer={localizer}
        events={events} // ✅ state로 관리되는 events 전달
        startAccessor=&quot;start&quot;
        endAccessor=&quot;end&quot;
        style={{ height: 500 }}
      /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

export default MyCalendar;&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Project</category>
      <author>채._.</author>
      <guid isPermaLink="true">https://coco910.tistory.com/122</guid>
      <comments>https://coco910.tistory.com/122#entry122comment</comments>
      <pubDate>Wed, 2 Apr 2025 22:05:34 +0900</pubDate>
    </item>
    <item>
      <title>[사람별_Day4] RBC 파헤치기 2 - date-fns</title>
      <link>https://coco910.tistory.com/121</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.npmjs.com/package/react-big-calendar?activeTab=readme&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;RBC npm 문서&lt;/a&gt;를 읽던 중,&amp;nbsp;날짜 형식을 지정해야 한다는 점을 알았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;RBC&lt;/span&gt;는 기본적으로 날짜 및 시간을 처리하지 않기 때문에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;형식을 지정하기 위해 네 가지의 플러그인을 지원하고 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #5f6d2b;&quot;&gt;&lt;b&gt;나만의 생각 정리&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;RBC는 달력 형태를 만들어 줄 뿐,&lt;br /&gt;해당 숫자가 &lt;u&gt;날짜를 뜻하는 바&lt;/u&gt;임을 알게 해주는 것은 플러그인의 역할이다.&lt;br /&gt;즉, 캘린더와 사용자 사이에서 캘린더에게는 문자열로 전달, 사람에게는 날짜로 전달해 주는 통역가 역할이다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RBC에서 지원하는 네 가지 플러그인을 비교해보았다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1480&quot; data-origin-height=&quot;350&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DcgeP/btsM4Hs8bTV/UDcSpp0v6N6SpQAG24Ouf0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DcgeP/btsM4Hs8bTV/UDcSpp0v6N6SpQAG24Ouf0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DcgeP/btsM4Hs8bTV/UDcSpp0v6N6SpQAG24Ouf0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDcgeP%2FbtsM4Hs8bTV%2FUDcSpp0v6N6SpQAG24Ouf0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;703&quot; height=&quot;350&quot; data-origin-width=&quot;1480&quot; data-origin-height=&quot;350&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식문서:&lt;br /&gt;&lt;span&gt;&amp;nbsp;&lt;a href=&quot;https://momentjs.com/&quot;&gt;&lt;b&gt;Moment.js&lt;/b&gt;&lt;/a&gt;&amp;nbsp;,&amp;nbsp;&lt;a href=&quot;https://github.com/jquery/globalize&quot;&gt;&lt;b&gt;Globalize.js&lt;/b&gt;&lt;/a&gt;&amp;nbsp;,&amp;nbsp;&lt;a href=&quot;https://date-fns.org/&quot;&gt;&lt;b&gt;date-fns&lt;/b&gt;&lt;/a&gt;&amp;nbsp;,&amp;nbsp;&lt;a href=&quot;https://day.js.org/&quot;&gt;&lt;b&gt;Day.js&lt;/b&gt;&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 90px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 12.44186%; height: 18px; text-align: center;&quot;&gt;라이브러리&lt;/td&gt;
&lt;td style=&quot;width: 13.953488%; height: 18px; text-align: center;&quot;&gt;번들 크기&lt;/td&gt;
&lt;td style=&quot;width: 13.488372%; height: 18px; text-align: center;&quot;&gt;성능&lt;/td&gt;
&lt;td style=&quot;width: 14.069769%; height: 18px; text-align: center;&quot;&gt;Tree Shaking 지원&lt;/td&gt;
&lt;td style=&quot;width: 13.953488%; height: 18px; text-align: center;&quot;&gt;TypeScript 지원&lt;/td&gt;
&lt;td style=&quot;width: 19.127907%; height: 18px; text-align: center;&quot;&gt;타임존 지원&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 12.44186%; height: 18px; text-align: center;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;Moment.js&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 13.953488%; height: 18px; text-align: center;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;약 260KB&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 13.488372%; height: 18px; text-align: center;&quot;&gt;느림&lt;/td&gt;
&lt;td style=&quot;width: 14.069769%; height: 18px; text-align: center;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;X&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 13.953488%; height: 18px; text-align: center;&quot;&gt;&lt;b&gt;O&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 19.127907%; height: 18px; text-align: center;&quot;&gt;(moment-timezone 필요)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 12.44186%; height: 18px; text-align: center;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;Globalize.js&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 13.953488%; height: 18px; text-align: center;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;약 200KB&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 13.488372%; height: 18px; text-align: center;&quot;&gt;느림&lt;/td&gt;
&lt;td style=&quot;width: 14.069769%; height: 18px; text-align: center;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;X&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 13.953488%; height: 18px; text-align: center;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;X&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 19.127907%; height: 18px; text-align: center;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;(Intl 지원)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 12.44186%; height: 18px; text-align: center;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;date-fns&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 13.953488%; height: 18px; text-align: center;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;약 20KB&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 13.488372%; height: 18px; text-align: center;&quot;&gt;빠름&lt;/td&gt;
&lt;td style=&quot;width: 14.069769%; height: 18px; text-align: center;&quot;&gt;&lt;b&gt;O&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 13.953488%; height: 18px; text-align: center;&quot;&gt;&lt;b&gt;O&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 19.127907%; height: 18px; text-align: center;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;X&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 12.44186%; height: 18px; text-align: center;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;Day.js&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 13.953488%; height: 18px; text-align: center;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;약 2KB&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 13.488372%; height: 18px; text-align: center;&quot;&gt;매우 빠름&lt;/td&gt;
&lt;td style=&quot;width: 14.069769%; height: 18px; text-align: center;&quot;&gt;&lt;b&gt;O&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 13.953488%; height: 18px; text-align: center;&quot;&gt;&lt;b&gt;O&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 19.127907%; height: 18px; text-align: center;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;(플러그인 필요)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 &lt;u&gt;가벼운 번들 크기, 빠른 속도, 쉬운 접근성&lt;/u&gt;을 기준으로 두어 고려했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그에 적합하는 것은 date-fns로, 문서도 잘 정리되어 있는 듯 하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기준에 따르자면 Day.js가 가장 적합하지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타임존 지원에 대해 팀원들과 이야기를 나눈 결과 한국인 사용자만 고려하여 진행하기로 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 샤드시엔에서 Day Picker를 사용하는 팀원분도 date-fns를 사용해야 했기에 최종 결정은 &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;date-fns !&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;border-bottom: 2px solid #424242; text-align: left; color: #424242; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #fff; position: relative; padding: 10px 15px 10px 0px; bottom: -19px;&quot;&gt;date-fns 사용하여 캘린더 나타내기&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt; date-fns 설치&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;pnpm install date-fns&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt; TypeScript 설치&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;date-fns가 기본적으로 TS를 포함하지만, &lt;br /&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;b&gt;&lt;u&gt;모듈 'react-big-calendar'에 대한 선언 파일을 찾을 수 없습니다. 오류&lt;/u&gt;&lt;/b&gt;로 인해 설치했다.&lt;/span&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li style=&quot;color: #000000;&quot; data-end=&quot;293&quot; data-start=&quot;225&quot;&gt;react-big-calendar&lt;span&gt;&amp;nbsp;&lt;/span&gt;패키지가 TypeScript용 타입 정의 파일을 제공하지 않기 때문에 발생한 오류이다.&lt;/li&gt;
&lt;li style=&quot;color: #000000;&quot; data-end=&quot;356&quot; data-start=&quot;294&quot;&gt;TypeScript는&lt;span&gt;&amp;nbsp;&lt;/span&gt;.d.ts(타입 선언 파일)가 없는 경우, 해당 패키지를 알 수 없다고 판단한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt; 설치 후 import&lt;/p&gt;
&lt;pre id=&quot;code_1743583803383&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&quot;use client&quot;;
import { Calendar, dateFnsLocalizer } from &quot;react-big-calendar&quot;;
import { format, parse, startOfWeek, getDay } from &quot;date-fns&quot;;
import { enUS } from &quot;date-fns/locale/en-US&quot;;
import { ko } from &quot;date-fns/locale/ko&quot;;

// 아래의 import는 해당 컴포넌트를 포함하는 페이지에 입력함
import &quot;react-big-calendar/lib/css/react-big-calendar.css&quot;;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt; 지역화 설정&lt;/p&gt;
&lt;pre id=&quot;code_1743588937268&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 로케일(지역화) 설정
const locales = {
  &quot;en-US&quot;: enUS, //미국-영어 : 미국식 날짜
  &quot;ko-KR&quot;: ko, //한국-한글 : 한국식 날짜
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt; dateFnsLocalizer&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;로 RBC&lt;/span&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;와 연결&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1743589009109&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const localizer = dateFnsLocalizer({
  format, // 날짜를 문자열로 변환
  parse, // 문자열을 날짜 객체로 변환
  startOfWeek, // 한 주의 시작 요일 설정
  getDay, // 날짜의 요일 가져오기
  locales, // 다국어 설정 적용
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt; &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;RBC&lt;/span&gt;의 &amp;lt;Calendar /&amp;gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;컴포넌트 props 설정&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1743589144739&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const MainCalendar = () =&amp;gt; {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;Calendar
        localizer={localizer} // 날짜 및 시간 포맷을 지정(dateFnsLocalizer)
        events={[]} // 여기에 이벤트 추가 가능
        startAccessor=&quot;start&quot; // 각 이벤트 객체에서 시작 시간
        endAccessor=&quot;end&quot; // 종료 시간
        style={{ height: 500 }} // 캘린더의 높이(CSS 직접 지정)
      /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt; props 중 &lt;b&gt;events&lt;/b&gt; 상세 설명&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;events 배열의 각 객체는 title, start, end을 가짐&lt;/li&gt;
&lt;li&gt;따라서 startAccessor=&quot;start&quot;와 endAccessor=&quot;end&quot;는 그 속성을 가져옴&lt;/li&gt;
&lt;li&gt;new Date의 인자가 3인데 왜 4월일까?
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;new Date(year, month, day, hour, minute, second)&lt;/span&gt;&amp;nbsp;&lt;/li&gt;
&lt;li&gt;JS new Date()의 월 인덱스는 0부터 시작하는 &lt;b&gt;제로 인덱스&lt;br /&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;1월 = 0 / 2월 = 1 / 3월 = 2 / ... / 12월 = 11&lt;/span&gt;&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1743589661854&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 일정 예시
const events = [
  {
    title: &quot;회의&quot;,
    start: new Date(2024, 3, 10, 10, 0), // 4월 10일 10:00 AM
    end: new Date(2024, 3, 10, 12, 0),   // 4월 10일 12:00 PM
  },
];&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt; 전체 로직&lt;/h4&gt;
&lt;pre id=&quot;code_1743584051069&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// src &amp;gt; components &amp;gt; Calendar.tsx

&quot;use client&quot;;
import { Calendar, dateFnsLocalizer } from &quot;react-big-calendar&quot;;
import { format, parse, startOfWeek, getDay } from &quot;date-fns&quot;;
import { enUS } from &quot;date-fns/locale/en-US&quot;;
import { ko } from &quot;date-fns/locale/ko&quot;;


const locales = {
  &quot;en-US&quot;: enUS,
  &quot;ko-KR&quot;: ko, 
};

const localizer = dateFnsLocalizer({
  format,
  parse,
  startOfWeek,
  getDay,
  locales,
});

const MainCalendar = () =&amp;gt; {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;Calendar
        localizer={localizer}
        events={[]}
        startAccessor=&quot;start&quot;
        endAccessor=&quot;end&quot;
        style={{ height: 500 }}
      /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

export default MainCalendar;&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Project</category>
      <author>채._.</author>
      <guid isPermaLink="true">https://coco910.tistory.com/121</guid>
      <comments>https://coco910.tistory.com/121#entry121comment</comments>
      <pubDate>Wed, 2 Apr 2025 21:54:43 +0900</pubDate>
    </item>
    <item>
      <title>[사람별_Day4] RBC 파헤치기 1 - Webpack의 CSS 처리</title>
      <link>https://coco910.tistory.com/120</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;리액트 빅 캘린더(RBC)를 프로젝트에 적용하기 전, 파악 겸 연습으로 개인 파일을 생성했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기 세팅부터 난관이 시작되는데...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;협업 시 적용하기 위해 기록해본다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;border-bottom: 2px solid #424242; text-align: left; color: #424242; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #fff; position: relative; padding: 10px 15px 10px 0px; bottom: -19px;&quot;&gt;진행 상황&lt;/span&gt;&lt;span style=&quot;background-color: #fff; position: relative; padding: 10px 15px 10px 0px; bottom: -19px;&quot;&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;리액트 빅 캘린더 설치&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;pnpm dev&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000; letter-spacing: 0px;&quot;&gt;오류 : You forgot to add 'mini-css-extract-plugin' plugin&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1936&quot; data-origin-height=&quot;962&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b6Bfxo/btsM6cd95Im/B8uzFFtJksSU0nvGetkTKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b6Bfxo/btsM6cd95Im/B8uzFFtJksSU0nvGetkTKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b6Bfxo/btsM6cd95Im/B8uzFFtJksSU0nvGetkTKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb6Bfxo%2FbtsM6cd95Im%2FB8uzFFtJksSU0nvGetkTKK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1936&quot; height=&quot;962&quot; data-origin-width=&quot;1936&quot; data-origin-height=&quot;962&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp;&lt;a style=&quot;color: #0070d1; text-align: start;&quot; href=&quot;https://github.com/webpack-contrib/mini-css-extract-plugin#getting-started&quot;&gt;'please read' 링크&lt;/a&gt;로 들어가보니 '&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;CSS 코드가 포함된 JS 파일 별로 CSS 파일을 생성' 무슨 말??&lt;/span&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;Webpack&lt;/span&gt;&lt;/b&gt;은 모든 코드(JS, CSS 등)를 번들링해서 하나의 JavaScript 파일로 만듬&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;하지만 이렇게 하면 CSS가 JS 번들에 포함되어 브라우저에서 로딩할 때 최적화되지 않을 수 있음&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;border-bottom: 2px solid #424242; text-align: left; color: #424242; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #fff; position: relative; padding: 10px 15px 10px 0px; bottom: -19px;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;mini-css-extrant-plugin 적용&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;padding: 0.4em 1em 0.1em 0.2em; margin: 0.5em 0em; color: #000; border-bottom: 2px #888 dotted; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;[리액트 환경]&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;아래는 &lt;b&gt;리액트&lt;/b&gt; 환경에서 mini-css-extrant-plugin을 적용하는 방법이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt; 작동 방식&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;b&gt;CSS 코드 추출:&lt;/b&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Webpack이 JS 파일을 처리할 때&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;import &quot;./style.css&quot;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;같은 코드가 있으면&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;원래는 이 CSS가 JS 내부에 포함되지만,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;mini-css-extract-plugin&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;을 사용하면 CSS를 별도의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;.css&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;파일로 분리&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;b&gt;CSS 파일 생성:&lt;/b&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;번들링이 완료되면 JS 번들과 별개로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;.css&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;파일이 생성됨&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;b&gt;CSS 로드 최적화:&lt;/b&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;이렇게 하면 브라우저가 CSS를 별도로 로드할 수 있어&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;JS + CSS&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;를 한 번에 로드하는 것보다 더 효율적&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt; 적용 방법 (React 프로젝트에서 적용)&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;설치&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot; data-ke-style=&quot;style3&quot;&gt;npm install mini-css-extract-plugin --save-dev&amp;nbsp;&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;Webpack 설정 변경 (&lt;/span&gt;webpack.config.js&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;)&lt;/span&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1743609209099&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const MiniCssExtractPlugin = require(&quot;mini-css-extract-plugin&quot;);

module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: [MiniCssExtractPlugin.loader, &quot;css-loader&quot;],
      },
    ],
  },
  plugins: [new MiniCssExtractPlugin()], // 플러그인 추가
};&lt;/code&gt;&lt;/pre&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt; react-big-calendar&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;에서 왜 필요할까?&lt;/span&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;react-big-calendar&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;는 내부적으로 CSS를 사용한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;하지만 Webpack이 CSS를 처리할 때,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;mini-css-extract-plugin&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;이 없으면 CSS가 JS 번들 내부에 포함되려고 해서 충돌이 발생한다. &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;따라서 해당 플러그인을 추가하면 RBC 내부 CSS를 별도의 .css 파일로 생성하게 되고, &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;JS 번들과 분리되어 캘린더만의 CSS가 적용&lt;/b&gt;&lt;/span&gt;된다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;그로 인한 효과&lt;/span&gt;&lt;/b&gt;는&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;1. 번들 크기 최적화&lt;/span&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;JS 로딩과 CSS 로딩이 동시에 진행할 수 있어 성능이 좋아진다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;JS 로드 이후에 CSS 적용이 아닌, 스타일이 먼저 로드될 수 있음.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt; JS가 로드되기도 전에 스타일이 먼저 로드되면 CSS가 적용 안 되는 거 아니야?&lt;/span&gt;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;오히려 더 빠르게 보이게 된다!&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;JS에 CSS가 포함되어 있을 때는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;u&gt;'JS 로드 -&amp;gt; CSS 적용'&lt;/u&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;순서이기 때문에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;스타일이 적용되지 않은 HTML 요소가 먼저 보이며, 깜빡이는 현상이 발생할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;반면 스타일이 먼저 적용되면 지연 없이 화면 구성이 이루어진다.&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;caret-color: #000000;&quot;&gt;2. 캐싱 최적화&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;스타일이 변경되지 않는 한 CSS는 브라우저에서 캐싱되고, JS 파일만 새로 받도록 할 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;3. 스타일 충돌 방지&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;RBC 내부에서 사용하는 CSS와 프로젝트에서 직접 정의한 CSS가 충돌할 가능성이 있는데,&lt;br /&gt;RBC의 CSS를 독립적으로 유지 가능하다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;padding: 0.4em 1em 0.1em 0.2em; margin: 0.5em 0em; color: #000; border-bottom: 2px #888 dotted; font-weight: bold;&quot; data-ke-size=&quot;size23&quot;&gt;[Next.js 환경]&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 나와 같이&lt;b&gt; Next.js&lt;/b&gt;로 프로젝트를 진행하는 경우에는 한 줄만 추가하면 된다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;import &quot;react-big-calendar/lib/css/react-big-calendar.css&quot;;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜냐하면 &lt;b&gt;Next.js는 &lt;span style=&quot;color: #ee2323;&quot;&gt;Webpack 설정을 자동 관리&lt;/span&gt;하고 &lt;span style=&quot;color: #ee2323;&quot;&gt;CSS 로더가 기본으로 포함&lt;/span&gt;되어 있기 때문&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;413&quot; data-start=&quot;326&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;409&quot; data-start=&quot;326&quot;&gt;Next.js는&lt;span&gt;&amp;nbsp;&lt;/span&gt;next.config.js&lt;span&gt;&amp;nbsp;&lt;/span&gt;내부에서 Webpack을 자동으로 설정하기 때문에, 기본적인 CSS 처리는 추가 설정 없이 동작한다.&lt;/li&gt;
&lt;li data-end=&quot;409&quot; data-start=&quot;326&quot;&gt;Next.js는&lt;span&gt;&amp;nbsp;&lt;/span&gt;.css&lt;span&gt;&amp;nbsp;&lt;/span&gt;파일을&lt;span&gt;&amp;nbsp;&lt;/span&gt;import하는 것을 지원하므로,&lt;span&gt;&amp;nbsp;&lt;/span&gt;mini-css-extract-plugin을 직접 설정할 필요 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt; import&lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;를 어디서 하느냐??&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통 여러 페이지에서 캘린더 CSS를 적용해야 하는 경우, RootLayout에 적용하여 전역 스타일로 지정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이번 프로젝트의 경우 '캘린더 페이지'에서만 리액트 빅 캘린더를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt; &lt;/b&gt;만약, 이런 경우에 RootLayout에 import를 하게 된다면?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캘린더를 사용하지 않는 페이지의 CSS와 &lt;u&gt;충돌 가능성&lt;/u&gt;이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;캘린더 컴포넌트가 포함된 해당 페이지에 import&lt;/b&gt;&lt;/span&gt;를 해주면 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2040&quot; data-origin-height=&quot;942&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dCn9fk/btsM5SNSHUL/28BXulQNUjOcRUgaYYmiQ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dCn9fk/btsM5SNSHUL/28BXulQNUjOcRUgaYYmiQ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dCn9fk/btsM5SNSHUL/28BXulQNUjOcRUgaYYmiQ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdCn9fk%2FbtsM5SNSHUL%2F28BXulQNUjOcRUgaYYmiQ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2040&quot; height=&quot;942&quot; data-origin-width=&quot;2040&quot; data-origin-height=&quot;942&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정상 작동 확인!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Project</category>
      <author>채._.</author>
      <guid isPermaLink="true">https://coco910.tistory.com/120</guid>
      <comments>https://coco910.tistory.com/120#entry120comment</comments>
      <pubDate>Wed, 2 Apr 2025 16:12:40 +0900</pubDate>
    </item>
  </channel>
</rss>