<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>dogFootHistory</title>
    <link>https://wheatherisgood.tistory.com/</link>
    <description>https://github.com/kimdonghyeok1352</description>
    <language>ko</language>
    <pubDate>Tue, 14 Jul 2026 05:03:09 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>날씨는 맑음</managingEditor>
    <image>
      <title>dogFootHistory</title>
      <url>https://tistory1.daumcdn.net/tistory/6659220/attach/689d570cbf2b4cebb9a6a73eda63f4d9</url>
      <link>https://wheatherisgood.tistory.com</link>
    </image>
    <item>
      <title>웹 접근성을 고려해야 하는 이유</title>
      <link>https://wheatherisgood.tistory.com/95</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;웹 접근성을 고려해야 하는 이유&lt;/h4&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;서비스를 진행하면 항상 최고 사양의 휴대폰으로 진행할 수 없는 사용자가 있고, 몸이 조금 불편하신 사용자도 있을 수 있다. 그런 경우이더라도 서비스는 모두에게 최대의 효율성을 제공하려고 노력을 해야한다. 이러한 이유로 웹 접근성을 고려해야하고, 프론트 개발자가 갖추어야 할 자질이라 생각한다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;인식이 가능해야 함( Perceivable )&lt;/h4&gt;
&lt;p data-end=&quot;561&quot; data-start=&quot;530&quot; data-ke-size=&quot;size16&quot;&gt;사용자가 콘텐츠를 &amp;ldquo;볼 수 없더라도&amp;rdquo; 이해 가능해야 함.&lt;/p&gt;
&lt;p data-end=&quot;565&quot; data-start=&quot;563&quot; data-ke-size=&quot;size16&quot;&gt;예:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;622&quot; data-start=&quot;566&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;582&quot; data-start=&quot;566&quot; data-section-id=&quot;ra8b94&quot;&gt;이미지에 대체 텍스트 제공&lt;/li&gt;
&lt;li data-end=&quot;593&quot; data-start=&quot;583&quot; data-section-id=&quot;1e94m1k&quot;&gt;영상 자막 제공&lt;/li&gt;
&lt;li data-end=&quot;610&quot; data-start=&quot;594&quot; data-section-id=&quot;tdzc0h&quot;&gt;색상만으로 상태 전달 금지&lt;/li&gt;
&lt;li data-end=&quot;622&quot; data-start=&quot;611&quot; data-section-id=&quot;f9lsj8&quot;&gt;충분한 대비 제공&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;조작 가능해야 함 ( Operable )&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;736&quot; data-start=&quot;718&quot; data-ke-size=&quot;size16&quot;&gt;마우스 없어도 조작 가능해야 함.&lt;/p&gt;
&lt;p data-end=&quot;740&quot; data-start=&quot;738&quot; data-ke-size=&quot;size16&quot;&gt;예:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;798&quot; data-start=&quot;741&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;752&quot; data-start=&quot;741&quot; data-section-id=&quot;18i28s7&quot;&gt;Tab 이동 가능&lt;/li&gt;
&lt;li data-end=&quot;774&quot; data-start=&quot;753&quot; data-section-id=&quot;bhoi4m&quot;&gt;Enter / Space 동작 가능&lt;/li&gt;
&lt;li data-end=&quot;786&quot; data-start=&quot;775&quot; data-section-id=&quot;37mg6s&quot;&gt;포커스 표시 보임&lt;/li&gt;
&lt;li data-end=&quot;798&quot; data-start=&quot;787&quot; data-section-id=&quot;9zhp6b&quot;&gt;키보드 함정 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 통신을 하거나, 계산이 진행하는 버튼인 경우는 &amp;lt;button&amp;gt;를 사용해야 웹 접근성에 안전함. &amp;lt;div&amp;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;이해 가능해야 함( Understandable )&lt;/h4&gt;
&lt;p data-end=&quot;975&quot; data-start=&quot;956&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;975&quot; data-start=&quot;956&quot; data-ke-size=&quot;size16&quot;&gt;UI가 예측 가능하고 명확해야 함.&lt;/p&gt;
&lt;p data-end=&quot;979&quot; data-start=&quot;977&quot; data-ke-size=&quot;size16&quot;&gt;예:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1033&quot; data-start=&quot;980&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;991&quot; data-start=&quot;980&quot; data-section-id=&quot;10vx9y5&quot;&gt;라벨 있는 입력창&lt;/li&gt;
&lt;li data-end=&quot;1004&quot; data-start=&quot;992&quot; data-section-id=&quot;1eeehf4&quot;&gt;명확한 에러 메시지&lt;/li&gt;
&lt;li data-end=&quot;1016&quot; data-start=&quot;1005&quot; data-section-id=&quot;uk90yw&quot;&gt;일관된 네비게이션&lt;/li&gt;
&lt;li data-end=&quot;1033&quot; data-start=&quot;1017&quot; data-section-id=&quot;1qatd2h&quot;&gt;지나치게 복잡한 문구 지양&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- label과 관련 있는 input태그는 id-for 일치시켜야 함.&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;h2 data-end=&quot;1353&quot; data-start=&quot;1324&quot; data-section-id=&quot;hcndk0&quot; data-ke-size=&quot;size26&quot;&gt;div 남발하지 말고 의미 있는 태그 사용&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1407&quot; data-start=&quot;1354&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1362&quot; data-start=&quot;1354&quot; data-section-id=&quot;139ov93&quot;&gt;header&lt;/li&gt;
&lt;li data-end=&quot;1369&quot; data-start=&quot;1363&quot; data-section-id=&quot;1j36lsz&quot;&gt;main&lt;/li&gt;
&lt;li data-end=&quot;1375&quot; data-start=&quot;1370&quot; data-section-id=&quot;1o3mpd&quot;&gt;nav&lt;/li&gt;
&lt;li data-end=&quot;1384&quot; data-start=&quot;1376&quot; data-section-id=&quot;1ck0li6&quot;&gt;button&lt;/li&gt;
&lt;li data-end=&quot;1391&quot; data-start=&quot;1385&quot; data-section-id=&quot;1j3hqri&quot;&gt;form&lt;/li&gt;
&lt;li data-end=&quot;1399&quot; data-start=&quot;1392&quot; data-section-id=&quot;165uvym&quot;&gt;label&lt;/li&gt;
&lt;li data-end=&quot;1407&quot; data-start=&quot;1400&quot; data-section-id=&quot;16msx86&quot;&gt;table&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발 공부/웹 접근성</category>
      <author>날씨는 맑음</author>
      <guid isPermaLink="true">https://wheatherisgood.tistory.com/95</guid>
      <comments>https://wheatherisgood.tistory.com/95#entry95comment</comments>
      <pubDate>Sun, 12 Apr 2026 13:48:25 +0900</pubDate>
    </item>
    <item>
      <title>[개인 프로젝트] StockList 개인 토이 프로젝트 회고록</title>
      <link>https://wheatherisgood.tistory.com/93</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;- 프롤로그&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; 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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Next.js 16&lt;/li&gt;
&lt;li&gt;React 19&lt;/li&gt;
&lt;li&gt;TypeScript&lt;/li&gt;
&lt;li&gt;React Query v5&lt;/li&gt;
&lt;li&gt;Zustand&lt;/li&gt;
&lt;li&gt;Tailwind CSS&lt;/li&gt;
&lt;li&gt;Vitest&lt;/li&gt;
&lt;li&gt;Testing Libary&lt;/li&gt;
&lt;/ul&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;주식&amp;nbsp;목록&amp;nbsp;조회&lt;br /&gt;-&amp;nbsp;종목&amp;nbsp;검색&lt;br /&gt;-&amp;nbsp;가격&amp;nbsp;/&amp;nbsp;등락률&amp;nbsp;정렬&lt;br /&gt;-&amp;nbsp;관심종목&amp;nbsp;저장&amp;nbsp;및&amp;nbsp;유지&lt;br /&gt;-&amp;nbsp;종목&amp;nbsp;상세&amp;nbsp;페이지&amp;nbsp;조회&lt;br /&gt;-&amp;nbsp;캔들&amp;nbsp;차트&amp;nbsp;조회&lt;br /&gt;-&amp;nbsp;종목&amp;nbsp;관련&amp;nbsp;뉴스&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- CSR과 SSR을 어떠한 상황에 써야하고, 어떻게 쓰면 사용자가 느끼기에 자연스럽다고 느낄지.&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;- App&amp;nbsp;Router&amp;nbsp;기반&amp;nbsp;라우팅과&amp;nbsp;API&amp;nbsp;Route&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;github - &lt;a href=&quot;https://github.com/kimdonghyeok135/stockList&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/kimdonghyeok135/stockList&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1774592169338&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - kimdonghyeok135/stockList: stockList Study&quot; data-og-description=&quot;stockList Study. Contribute to kimdonghyeok135/stockList development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/kimdonghyeok135/stockList&quot; data-og-url=&quot;https://github.com/kimdonghyeok135/stockList&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/iNQL6/dJMb82MCS5K/YlEJmzoC5GOwtPrGwmG8Dk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/cTWjgk/dJMb8WMpnoC/LQxZj83IT0CQEgx5rtVaCk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/kimdonghyeok135/stockList&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/kimdonghyeok135/stockList&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/iNQL6/dJMb82MCS5K/YlEJmzoC5GOwtPrGwmG8Dk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/cTWjgk/dJMb8WMpnoC/LQxZj83IT0CQEgx5rtVaCk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - kimdonghyeok135/stockList: stockList Study&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;stockList Study. Contribute to kimdonghyeok135/stockList development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배포 링크 - &lt;a href=&quot;https://stock-list-p82m.vercel.app&quot;&gt;https://stock-list-p82m.vercel.app&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;후기..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레거시 프로젝트로 업무를 진행했던 내가 이런 토이프로젝트를 진행함에 처음에는 막막하기도 했고, 진행도 뎌디긴 했지만. 꾸준함으로 완성했다. 항상 10을 할 수는 없지만 항상 꾸준히 하기만 한다면 좋은 결과가 있다고 믿고 프로젝트를 만들었다.&amp;nbsp;&lt;/p&gt;</description>
      <category>개인 프로젝트</category>
      <author>날씨는 맑음</author>
      <guid isPermaLink="true">https://wheatherisgood.tistory.com/93</guid>
      <comments>https://wheatherisgood.tistory.com/93#entry93comment</comments>
      <pubDate>Fri, 27 Mar 2026 15:17:06 +0900</pubDate>
    </item>
    <item>
      <title>[Next] useOptimistic</title>
      <link>https://wheatherisgood.tistory.com/92</link>
      <description>&lt;h3 style=&quot;color: #0a0a0a; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;useOptimistic이란&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;사용자 인터페이스(UI)를 서버 응답 전에 미리 업데이트하여, 애플리케이션이 더 빠르고 반응성이 좋게 느껴지도록 만드는 React 훅&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;useOptimistic은 &lt;b&gt;임시적인 &quot;낙관적(optimistic)&quot; 상태&lt;/b&gt;를 만들어 관리합니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;액션 시작&lt;/b&gt;: 사용자가 버튼을 클릭하여 서버 액션을 호출합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;낙관적 상태 업데이트&lt;/b&gt;: 서버 액션이 실행되기 전에 useOptimistic 훅이 제공하는 함수를 호출하여 UI의 상태를 &lt;b&gt;즉시&lt;/b&gt; 변경합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;서버 통신&lt;/b&gt;: 실제 서버 액션이 백그라운드에서 실행됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;최종 상태 적용&lt;/b&gt;: 서버 액션이 성공적으로 완료되면, Next.js는 최신 서버 상태로 UI를 다시 렌더링합니다. 이때 임시적인 낙관적 상태는 사라지고 실제 데이터가 화면에 표시됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;실패 시 복구&lt;/b&gt;: 만약 서버 액션이 실패하면, UI는 원래의 상태로 되돌아갑니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwjEpN6M09SPAxUAAAAAHQAAAAAQpgU&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;&lt;span&gt;JavaScript&lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// `app/comments/page.tsx`
&quot;use client&quot;;

import { useState } from &quot;react&quot;;
import { useOptimistic } from &quot;react&quot;;
import { addComment } from &quot;./actions&quot;; // 서버 액션 import

export default function Page() {
  const [inputValue, setInputValue] = useState(&quot;&quot;);

  // useOptimistic 훅 사용
  const [optimisticComments, addOptimistic] = useOptimistic(
    [], // 초기 상태 (여기서는 댓글 목록 배열)
    (currentComments, newComment) =&amp;gt; [ // updater 함수: 현재 상태와 새로운 값으로 낙관적 상태를 반환
      ...currentComments,
      { text: newComment, sending: true }, // 임시로 'sending' 속성 추가
    ]
  );

  return (
    &amp;lt;div&amp;gt;
      {optimisticComments.map((comment, index) =&amp;gt; (
        &amp;lt;div key={index}&amp;gt;
          {comment.text}
          {comment.sending &amp;amp;&amp;amp; &amp;lt;span&amp;gt;... 전송 중&amp;lt;/span&amp;gt;}
        &amp;lt;/div&amp;gt;
      ))}
      &amp;lt;form onSubmit={async (e) =&amp;gt; {
        e.preventDefault();
        const commentText = inputValue;

        addOptimistic(commentText); // ✅ 서버 응답 전에 UI를 즉시 업데이트

        await addComment(commentText); // 서버 액션 호출 (비동기)

        setInputValue(&quot;&quot;);
      }}&amp;gt;
        &amp;lt;input
          type=&quot;text&quot;
          value={inputValue}
          onChange={(e) =&amp;gt; setInputValue(e.target.value)}
        /&amp;gt;
        &amp;lt;button type=&quot;submit&quot;&amp;gt;댓글 추가&amp;lt;/button&amp;gt;
      &amp;lt;/form&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useOptimistic은 사용자에게 즉각적인 피드백을 제공함으로써 애플리케이션의 반응성을 극대화하는 강력한 도구입니다.&lt;/p&gt;</description>
      <category>개발 공부/Next</category>
      <author>날씨는 맑음</author>
      <guid isPermaLink="true">https://wheatherisgood.tistory.com/92</guid>
      <comments>https://wheatherisgood.tistory.com/92#entry92comment</comments>
      <pubDate>Thu, 18 Sep 2025 21:55:52 +0900</pubDate>
    </item>
    <item>
      <title>[Next] generateStaticParams</title>
      <link>https://wheatherisgood.tistory.com/91</link>
      <description>&lt;h4 style=&quot;color: #0a0a0a; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;generateStaticParams이란&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Next.js에서 &lt;b&gt;동적 라우트(Dynamic Routes)를 정적으로 생성&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 data-ke-size=&quot;size23&quot;&gt;왜 필요한가요?  &lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동적 라우트는 URL에 변수(예: [slug], [id])를 포함하는 경로를 의미합니다. 예를 들어, 블로그의 [slug] 페이지는 post-1, post-2 등 다양한 slug 값을 가질 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;generateStaticParams를 사용하면, 서버가 각 요청에 대해 페이지를 동적으로 렌더링하는 대신, &lt;b&gt;빌드 시점에 모든 가능한 slug 값에 대한 페이지를 미리 생성&lt;/b&gt;할 수 있습니다. 이렇게 미리 만들어진 HTML 파일은 CDN에 배포되어 매우 빠르게 사용자에게 전달될 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&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;b&gt;빌드 타임 실행&lt;/b&gt;: generateStaticParams 함수는 next build 명령어가 실행될 때 딱 한 번 실행됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;매개변수 배열 반환&lt;/b&gt;: 이 함수는 동적 세그먼트에 들어갈 매개변수(params) 객체의 배열을 반환해야 합니다. Next.js는 이 배열의 각 객체에 대해 정적 페이지를 생성합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;정적 페이지 생성&lt;/b&gt;: 반환된 매개변수 배열에 따라 동적 라우트 페이지가 모두 빌드됩니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;코드 예시  &lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;블로그 게시물 페이지를 정적으로 생성하는 예시입니다.&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahcKEwjEpN6M09SPAxUAAAAAHQAAAAAQdQ&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;&lt;span&gt;JavaScript&lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// app/blog/[slug]/page.tsx

// 1. generateStaticParams 함수를 정의합니다.
export async function generateStaticParams() {
  const posts = await getPostsFromDatabase(); // DB 또는 API에서 모든 게시물 데이터를 가져옴

  // 2. 각 게시물에 대한 slug를 포함하는 객체 배열을 반환합니다.
  return posts.map((post) =&amp;gt; ({
    slug: post.slug,
  }));
}

// 3. 페이지 컴포넌트는 매개변수를 받습니다.
export default function PostPage({ params }: { params: { slug: string } }) {
  // params.slug를 사용하여 특정 게시물 데이터를 가져와 렌더링
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h1&amp;gt;게시물 제목: {params.slug}&amp;lt;/h1&amp;gt;
      {/* ... */}
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;주의할 점 ⚠️&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;빌드 시점&lt;/b&gt;: generateStaticParams는 빌드 시에만 실행되므로, 런타임에 동적으로 추가되는 새로운 게시물은 자동으로 포함되지 않습니다. 새로운 게시물이 추가되면 빌드를 다시 해야 합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;dynamicParams&lt;/b&gt;: generateStaticParams를 정의하면 dynamicParams의 기본값이 true에서 false로 변경됩니다. 이는 generateStaticParams가 반환하지 않은 경로에 접근하면 &lt;b&gt;404 페이지를 반환&lt;/b&gt;한다는 의미입니다. 만약 알 수 없는 경로도 서버에서 동적으로 렌더링하고 싶다면, dynamicParams = true를 명시적으로 설정해야 합니다&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>개발 공부/Next</category>
      <author>날씨는 맑음</author>
      <guid isPermaLink="true">https://wheatherisgood.tistory.com/91</guid>
      <comments>https://wheatherisgood.tistory.com/91#entry91comment</comments>
      <pubDate>Sat, 13 Sep 2025 11:07:09 +0900</pubDate>
    </item>
    <item>
      <title>[Next] Route Segment Config</title>
      <link>https://wheatherisgood.tistory.com/90</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Route Segment Config는 Next.js의 **앱 라우터(App Router)**에서 특정 경로(segment)의 동작 방식을 제어하는 설정 파일입니다. 이 파일을 사용하면 페이지나 레이아웃의 &lt;b&gt;정적/동적 렌더링 방식&lt;/b&gt;, 캐싱 전략 등을 세밀하게 조정할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;page.tsx 또는 layout.tsx 파일 내에서 export const 문을 사용하여 설정 변수를 정의합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;주요 설정 옵션&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. dynamic&lt;/h4&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;'auto' (기본값): Next.js가 자동으로 정적(Static) 또는 동적(Dynamic) 렌더링을 판단합니다.&lt;/li&gt;
&lt;li&gt;'force-static': 해당 경로를 무조건 정적으로 렌더링합니다. 요청이나 쿠키와 같은 동적 데이터가 있더라도 빌드 시 HTML을 생성합니다.&lt;/li&gt;
&lt;li&gt;'force-dynamic': 해당 경로를 무조건 동적으로 렌더링합니다. 모든 요청에 대해 서버에서 렌더링을 다시 수행합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;사용 예시:&lt;/b&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwi7q8qc0NCPAxUAAAAAHQAAAAAQ2AE&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;&lt;span&gt;JavaScript&lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;dart&quot;&gt;&lt;code&gt;// app/blog/[slug]/page.tsx
export const dynamic = 'force-static';
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. revalidate&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 옵션은 정적 렌더링된 페이지의 &lt;b&gt;재검증(revalidation)&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;false: 캐싱하지 않고, 항상 동적으로 렌더링합니다.&lt;/li&gt;
&lt;li&gt;0: 캐싱하지만, 매 요청마다 캐시를 재검증합니다.&lt;/li&gt;
&lt;li&gt;number: 캐시의 유효 기간을 초 단위로 설정합니다. 이 시간이 지나면 다음 요청 시 페이지를 다시 렌더링합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;사용 예시:&lt;/b&gt;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwi7q8qc0NCPAxUAAAAAHQAAAAAQ2QE&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;&lt;span&gt;JavaScript&lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;// app/products/page.tsx
export const revalidate = 60; // 60초마다 페이지를 재검증합니다.
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. fetchCache&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;fetch 함수에 대한 캐싱 동작을 제어합니다. revalidate 옵션과 연동되어 동작하며, 기본값은 auto입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;'force-no-store': 모든 fetch 요청을 캐싱하지 않도록 강제합니다.&lt;/li&gt;
&lt;li&gt;'default-no-store': 기본적으로 캐싱하지 않지만, revalidate 옵션이 설정된 fetch 요청은 캐시합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;왜 필요한가?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Route Segment Config는 Next.js의 핵심 기능인 **하이브리드 렌더링(Hybrid Rendering)**을 세밀하게 제어하는 데 필수적입니다. 개발자는 각 페이지의 특성(예: 자주 바뀌는 데이터 vs. 정적인 콘텐츠)에 따라 최적의 렌더링 방식을 선택하여 &lt;b&gt;성능과 SEO&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #0a0a0a; text-align: start;&quot;&gt;&lt;a href=&quot;https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config&quot;&gt;https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;</description>
      <category>개발 공부/Next</category>
      <author>날씨는 맑음</author>
      <guid isPermaLink="true">https://wheatherisgood.tistory.com/90</guid>
      <comments>https://wheatherisgood.tistory.com/90#entry90comment</comments>
      <pubDate>Thu, 11 Sep 2025 21:33:28 +0900</pubDate>
    </item>
    <item>
      <title>[Next] unstable_cache</title>
      <link>https://wheatherisgood.tistory.com/89</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;unstable_cache란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;unstable_cache는 특정 함수 호출의 결과를 캐싱해서, 같은 인수로 함수가 다시 호출될 때 &lt;b&gt;데이터베이스를 다시 조회하거나 API를 호출하는 대신 캐시된 값을 즉시 반환&lt;/b&gt;하게 만듭니다. 이로써 불필요한 데이터 요청을 줄여 응답 시간을 단축하고 서버 부하를 줄일 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 함수는 Next.js의 내장 캐싱 시스템과 통합되어 동작하기 때문에, 일반적인 메모이제이션(memoization) 함수보다 더 강력합니다. 특히 Vercel 환경에서 배포될 때 Next.js 캐시 레이어와 긴밀하게 작동하도록 설계되었습니다.&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 data-ke-size=&quot;size23&quot;&gt;사용법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;unstable_cache는 revalidate와 tags라는 두 가지 주요 옵션을 사용합니다.&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahcKEwi7q8qc0NCPAxUAAAAAHQAAAAAQQQ&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;&lt;span&gt;JavaScript&lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import { unstable_cache } from 'next/cache';

const getMyData = unstable_cache(
  async (userId) =&amp;gt; {
    // 이 함수의 결과가 캐시됩니다.
    // userId가 같은 요청이 오면 이 코드는 다시 실행되지 않습니다.
    const data = await fetch(`https://api.example.com/data/${userId}`);
    return data.json();
  },
  ['my-data-key'], // 캐시 식별자
  {
    revalidate: 60, // 60초마다 캐시 갱신
    tags: ['posts'], // 'posts' 태그가 무효화될 때 함께 무효화
  }
);
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;주요 옵션&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;revalidate&lt;/b&gt;: 캐시의 유효 기간을 초 단위로 설정합니다. 이 시간이 지나면 다음 요청 시 함수가 다시 실행되어 캐시를 갱신합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;tags&lt;/b&gt;: 캐시에 태그를 부여합니다. revalidateTag(&amp;lsquo;posts&amp;rsquo;) 함수를 사용하여 특정 태그를 가진 모든 캐시를 수동으로 무효화할 수 있습니다. 이는 데이터가 변경되었을 때 즉시 캐시를 최신 상태로 만드는 데 유용합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&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 data-ke-size=&quot;size23&quot;&gt;revalidatePath의 역할&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;revalidatePath는 사용자가 특정 페이지에 접근했을 때 생성된 HTML 캐시를 무효화합니다. 이렇게 캐시가 무효화되면, 다음 요청 시 해당 페이지는 캐시된 데이터를 사용하는 대신 서버에서 다시 렌더링되어 최신 정보를 가져오게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 기능은 CMS(콘텐츠 관리 시스템)나 백엔드에서 데이터가 업데이트되었을 때, 프런트엔드 페이지를 자동으로 갱신해야 할 때 특히 유용합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;사용법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;revalidatePath는 revalidateTag와 유사하게 동작하지만, 태그 대신 **경로(path)**를 기반으로 캐시를 무효화합니다.&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwi7q8qc0NCPAxUAAAAAHQAAAAAQrQE&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;&lt;span&gt;JavaScript&lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// app/actions.ts (Server Action 예시)
&quot;use server&quot;;

import { revalidatePath } from 'next/cache';

export async function createPost() {
  // 1. 데이터베이스에 새 게시물 추가
  await db.post.create({ /* ... */ });

  // 2. 게시물 목록 페이지의 캐시를 초기화
  revalidatePath('/posts');
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드에서 createPost라는 서버 액션이 실행되면, revalidatePath('/posts')가 호출되어 /posts 경로의 캐시가 무효화됩니다. 다음 사용자가 /posts 페이지에 접근하면, Next.js는 최신 게시물 목록을 포함하여 페이지를 다시 렌더링하게 됩니다.&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;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;fetch함수에서도 비슷한 기능을 사용할 수 있다&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;fetch 함수의 캐싱 기능&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Next.js 환경에서 fetch 함수는 기본적으로 &lt;b&gt;자동으로 캐시를 적용&lt;/b&gt;합니다. 이 동작은 cache 옵션을 통해 제어할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;cache: 'force-cache' (기본값)&lt;/b&gt;: fetch는 요청을 캐시하고, 동일한 URL에 대한 다음 요청 시 캐시된 결과를 사용합니다. 서버 컴포넌트에서 fetch를 사용하면 이 옵션이 기본적으로 활성화됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;cache: 'no-store'&lt;/b&gt;: 요청을 캐시하지 않고, 항상 새로운 데이터를 가져옵니다. 동적인 데이터나 자주 변경되는 데이터를 가져올 때 유용합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;cache: 'no-cache'&lt;/b&gt;: 요청을 캐시하지만, 다음 요청 시 서버에 데이터를 재검증(revalidate)하여 최신 버전인지 확인합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;revalidate 옵션을 통한 재검증&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;fetch 함수는 revalidate 옵션을 사용하여 &lt;b&gt;시간 기반 캐시 재검증&lt;/b&gt;을 설정할 수 있습니다.&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwi7q8qc0NCPAxUAAAAAHQAAAAAQwwE&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;&lt;span&gt;JavaScript&lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;rust&quot;&gt;&lt;code&gt;// 60초마다 캐시 갱신
const response = await fetch('https://api.example.com/data', {
  next: { revalidate: 60 } // 60초가 지나면 다음 요청 시 캐시 갱신
});
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드는 revalidate 태그를 사용한 것과 유사하게, 60초마다 데이터가 자동으로 갱신됩니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;태그를 이용한 재검증 (tags)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;fetch 함수는 revalidateTag와 직접적으로 연동되는 &lt;b&gt;tags 옵션&lt;/b&gt;도 제공합니다.&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwi7q8qc0NCPAxUAAAAAHQAAAAAQxAE&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;&lt;span&gt;JavaScript&lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// 'posts' 태그를 사용하여 fetch 요청
const response = await fetch('https://api.example.com/posts', {
  next: { tags: ['posts'] }
});

// 데이터가 업데이트되면 태그를 통해 캐시를 초기화
// (서버 액션, 라우트 핸들러 등에서 사용)
import { revalidateTag } from 'next/cache';

revalidateTag('posts');&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description>
      <category>개발 공부/Next</category>
      <author>날씨는 맑음</author>
      <guid isPermaLink="true">https://wheatherisgood.tistory.com/89</guid>
      <comments>https://wheatherisgood.tistory.com/89#entry89comment</comments>
      <pubDate>Thu, 11 Sep 2025 20:47:43 +0900</pubDate>
    </item>
    <item>
      <title>[Next] Parallel Routes</title>
      <link>https://wheatherisgood.tistory.com/88</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;Parallel Routes란&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Parallel Routes는 Next.js에서 &lt;b&gt;하나의 레이아웃 내에 여러 개의 페이지를 동시에 렌더링&lt;/b&gt;할 수 있게 해주는 기능입니다. 전통적인 라우팅 방식이 페이지 전체를 교체하는 것과 달리, Parallel Routes는 페이지의 특정 섹션만 독립적으로 로드하거나 업데이트할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 기능은 복잡한 대시보드나 UI를 구성할 때 특히 유용합니다. 페이지의 각 부분을 분리하여 관리하고, 사용자가 상호작용하는 부분만 동적으로 업데이트함으로써 전반적인 성능과 사용자 경험을 향상시킬 수 있습니다.&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;style2&quot; /&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 data-ke-size=&quot;size23&quot;&gt;작동 방식 ⚙️&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Parallel Routes는 @ 기호로 시작하는 특별한 폴더, 즉 **'슬롯(slots)'**을 만들어 정의합니다. 각 슬롯은 독립적으로 렌더링되는 UI 컴포넌트를 나타냅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, 대시보드에 사용자 프로필과 통계 차트가 동시에 보이는 구조를 만들고 싶다면, 다음과 같은 폴더 구조를 사용할 수 있습니다.&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiK8ILM6MKPAxUAAAAAHQAAAAAQmwI&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;/app
├── dashboard/
│   ├── @user/
│   │   ├── page.tsx      // 사용자 프로필 컴포넌트
│   ├── @analytics/
│   │   ├── page.tsx    // 통계 차트 컴포넌트
│   ├── layout.tsx      // 메인 대시보드 레이아웃
├── page.tsx          
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구조에서 dashboard/layout.tsx 파일은 @user와 @analytics 슬롯을 **props**로 받아서 원하는 위치에 렌더링합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;주요 활용 사례 ✨&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;대시보드&lt;/b&gt;: 가장 일반적인 사용 사례입니다. 최근 활동, 사용자 목록, 차트 등 여러 독립적인 섹션을 하나의 페이지에 보여줄 때 유용합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;영구적인 UI&lt;/b&gt;: 음악 플레이어나 채팅 위젯처럼 사용자가 페이지를 이동해도 계속해서 보이는 UI 요소를 만들 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;조건부 렌더링&lt;/b&gt;: 사용자의 역할(예: 관리자 vs. 일반 사용자)에 따라 다른 슬롯을 조건부로 렌더링할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;코드 구현 예시  &lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Parallel Routes의 핵심은 부모 layout.tsx 파일에서 슬롯을 props로 받아 렌더링하는 것입니다.&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiK8ILM6MKPAxUAAAAAHQAAAAAQnAI&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;&lt;span&gt;JavaScript&lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// app/dashboard/layout.tsx

export default function DashboardLayout({
  children,
  user,
  analytics,
}: {
  children: React.ReactNode;
  user: React.ReactNode;
  analytics: React.ReactNode;
}) {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;div className=&quot;flex&quot;&amp;gt;
        {user}        {/* @user/page.tsx의 콘텐츠를 렌더링 */}
        {analytics}   {/* @analytics/page.tsx의 콘텐츠를 렌더링 */}
      &amp;lt;/div&amp;gt;
      &amp;lt;div className=&quot;main-content&quot;&amp;gt;
        {children}    {/* /dashboard/settings와 같은 메인 라우트의 콘텐츠를 렌더링 */}
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 설정을 통해 모든 슬롯(@user, @analytics)과 메인 콘텐츠(children)가 동일한 레이아웃 내에서 동시에 렌더링됩니다. 이는 관심사를 명확히 분리하고 더 나은 사용자 경험을 제공하는 효과적인 방법입니다.&lt;/p&gt;</description>
      <category>개발 공부/Next</category>
      <author>날씨는 맑음</author>
      <guid isPermaLink="true">https://wheatherisgood.tistory.com/88</guid>
      <comments>https://wheatherisgood.tistory.com/88#entry88comment</comments>
      <pubDate>Sun, 7 Sep 2025 10:28:09 +0900</pubDate>
    </item>
    <item>
      <title>[Next] Intercepting Route</title>
      <link>https://wheatherisgood.tistory.com/87</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;Intercepting Routef란&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Next.js의 **인터셉팅 라우트(Intercepting Routes)**는 사용자가 현재 페이지에 머물면서도 다른 라우트로 이동한 것처럼 보이게 하는 고급 라우팅 기능입니다. 마치 팝업 모달처럼 현재 페이지 위에 새로운 페이지를 렌더링하는 효과를 구현할 때 주로 사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 기능의 핵심은, 사용자가 라우트를 이동할 때 URL은 변경되지만 실제 페이지 전환이 일어나지 않고 현재 페이지의 컨텍스트를 유지하는 것입니다. 이를 통해 사용자 경험을 매끄럽게 만들고, 불필요한 페이지 리로드를 줄일 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;작동 방식  ️&amp;zwj;♂️&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Next.js에서 인터셉팅 라우트를 사용하려면 파일 시스템에서 특정 경로 앞에 &lt;b&gt;(...)&lt;/b&gt; 문법을 추가합니다. 이 문법은 Next.js에게 해당 라우트를 인터셉트할 의도임을 알려줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, photos 페이지에서 특정 사진을 클릭했을 때 모달을 띄워 사진 상세 정보를 보여주고 싶다고 가정해 봅시다.&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiK8ILM6MKPAxUAAAAAHQAAAAAQ5wE&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;/app
├── dashboard
│   ├── page.tsx
├── photos
│   ├── page.tsx          // 메인 사진 갤러리 페이지
│   └── [id]
│       └── page.tsx      // 일반적인 사진 상세 페이지
├── (..)photos
│   └── [id]
│       └── page.tsx      // ✅ 인터셉팅 라우트
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구조에서 사용자가 /photos/[id]로 이동하면, Next.js는 두 가지 경로를 확인합니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;일반적인 라우트&lt;/b&gt;: 사용자가 브라우저 주소창에 /photos/123를 직접 입력하거나 외부에서 접근한 경우, Next.js는 photos/[id]/page.tsx를 렌더링합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;인터셉팅 라우트&lt;/b&gt;: 사용자가 /photos 페이지에서 &amp;lt;Link&amp;gt;를 통해 photos/123으로 이동한 경우, Next.js는 (..)photos/[id]/page.tsx를 찾아 현재 photos 페이지 위에 모달처럼 겹쳐 렌더링합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;주요 장점&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;향상된 사용자 경험&lt;/b&gt;: 페이지 전체를 새로 로드하지 않고 필요한 정보만 팝업 형태로 보여주어 사용자가 맥락을 잃지 않게 합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;성능 최적화&lt;/b&gt;: 페이지 리로드가 없어 불필요한 데이터 재요청이나 렌더링 오버헤드를 줄일 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;간결한 URL&lt;/b&gt;: 모달이 띄워져도 URL은 실제 경로(/photos/[id])를 유지하므로, 북마크하거나 공유하기에 편리합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 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;&lt;b&gt;(.)&lt;/b&gt;: 같은 폴더 레벨의 라우트를 가로챕니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;_ 또는 (..)&lt;/b&gt;: 한 단계 위 폴더의 라우트를 가로챕니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;(..)&lt;/b&gt;: 두 단계 위 폴더의 라우트를 가로챕니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;(...)&lt;/b&gt;: 루트 폴더의 라우트를 가로챕니다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>개발 공부/Next</category>
      <author>날씨는 맑음</author>
      <guid isPermaLink="true">https://wheatherisgood.tistory.com/87</guid>
      <comments>https://wheatherisgood.tistory.com/87#entry87comment</comments>
      <pubDate>Sun, 7 Sep 2025 09:53:34 +0900</pubDate>
    </item>
    <item>
      <title>[Next] IntersectionObserver</title>
      <link>https://wheatherisgood.tistory.com/86</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;IntersectionObserver란&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;웹 브라우저 API로, 개발자가 특정 요소가 뷰포트(viewport)나 다른 요소(루트)와 교차하는지(intersecting) 여부를 비동기적으로 감지하는 데 사용됩니다. 즉, &lt;b&gt;사용자가 화면을 스크롤할 때 특정 요소가 화면에 나타났는지, 혹은 사라졌는지&lt;/b&gt;를 효율적으로 알아내는 도구입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;주요 역할과 장점&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;스크롤 애니메이션/효과&lt;/b&gt;: 스크롤이 특정 지점에 도달했을 때 요소를 페이드인(fade-in)시키거나 나타나게 하는 애니메이션을 구현할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;지연 로딩 (Lazy Loading)&lt;/b&gt;: 이미지가 화면에 보일 때만 로딩하도록 구현하여 초기 페이지 로딩 속도를 향상시킬 수 있습니다. 이는 사용자가 스크롤을 내릴 때 비로소 이미지를 불러오는 방식입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;무한 스크롤&lt;/b&gt;: 페이지 맨 아래에 도달했을 때 자동으로 다음 콘텐츠를 불러오는 무한 스크롤 기능을 구현할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;성능 최적화&lt;/b&gt;: 기존의 scroll 이벤트 리스너는 모든 스크롤 동작마다 코드를 실행하여 성능 저하를 일으킬 수 있습니다. IntersectionObserver는 교차 상태 변화가 발생할 때만 콜백 함수를 실행하므로 훨씬 효율적입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기본 사용법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IntersectionObserver를 사용하는 단계는 간단합니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Observer 생성&lt;/b&gt;: IntersectionObserver 객체를 생성할 때, 두 개의 인자를 전달합니다. 첫 번째는 교차 상태가 변경될 때 실행될 &lt;b&gt;콜백 함수&lt;/b&gt;이고, 두 번째는 교차를 감지할 환경을 설정하는 &lt;b&gt;옵션 객체&lt;/b&gt;입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;관찰 시작&lt;/b&gt;: observer.observe(targetElement) 메서드를 사용하여 교차를 감지할 **타겟 요소(target element)**를 지정합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;코드 예시:&lt;/h4&gt;
&lt;div data-ved=&quot;0CAAQhtANahcKEwiK8ILM6MKPAxUAAAAAHQAAAAAQNw&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;&lt;span&gt;JavaScript&lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// 옵션 설정: 뷰포트의 10%가 보이면 콜백 함수를 실행
const options = {
  root: null, // 뷰포트(브라우저 화면)를 기준으로 관찰
  rootMargin: '0px',
  threshold: 0.1 // 타겟 요소가 10% 보일 때
};

// IntersectionObserver 객체 생성
const observer = new IntersectionObserver((entries, observer) =&amp;gt; {
  entries.forEach(entry =&amp;gt; {
    if (entry.isIntersecting) {
      // isIntersecting이 true이면, 타겟 요소가 뷰포트에 보인다는 의미
      console.log('요소가 화면에 나타났습니다!');
      // TODO: 요소에 애니메이션을 적용하거나 데이터를 로딩하는 코드 작성
      // 더 이상 관찰할 필요가 없다면,
      // observer.unobserve(entry.target);
    }
  });
}, options);

// 관찰할 요소 지정
const target = document.querySelector('.my-element');
observer.observe(target);
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 예시는 .my-element가 화면에 나타날 때 콘솔에 메시지를 출력하는 간단한 코드입니다. 이를 활용하여 지연 로딩, 스크롤 애니메이션 등 다양한 기능을 구현할 수 있습니다.&lt;/p&gt;</description>
      <category>개발 공부/Next</category>
      <author>날씨는 맑음</author>
      <guid isPermaLink="true">https://wheatherisgood.tistory.com/86</guid>
      <comments>https://wheatherisgood.tistory.com/86#entry86comment</comments>
      <pubDate>Sat, 6 Sep 2025 17:08:58 +0900</pubDate>
    </item>
    <item>
      <title>[Next] middleware</title>
      <link>https://wheatherisgood.tistory.com/85</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;Next.js에서 middleware란&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;미들웨어(Middleware)**는 사용자가 특정 페이지로 이동하기 전에 실행되는 코드입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;middle의 역할&lt;/h3&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;&lt;b&gt;인증(Authentication)&lt;/b&gt;: 로그인하지 않은 사용자가 특정 페이지(예: 마이페이지, 관리자 페이지)에 접근하는 것을 막고 로그인 페이지로 리다이렉트합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;권한 부여(Authorization)&lt;/b&gt;: 로그인한 사용자라도 특정 권한이 없는 경우(예: 일반 사용자가 관리자 페이지에 접근) 접근을 차단합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;리다이렉션&lt;/b&gt;: 특정 조건(예: 구 버전 URL)에 따라 다른 페이지로 이동시킵니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;A/B 테스트&lt;/b&gt;: 사용자에게 무작위로 다른 버전의 페이지를 보여주는 A/B 테스트를 구현할 때 사용합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;다국어(i18n)&lt;/b&gt;: 사용자의 언어 설정에 따라 적절한 언어 버전의 페이지로 안내합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Next.js에서는 middleware.ts 또는 middleware.js 파일을 프로젝트의 &lt;b&gt;루트 디렉토리&lt;/b&gt;에 생성하기만 하면 미들웨어가 자동으로 활성화됩니다.&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwia_5TVl7GPAxUAAAAAHQAAAAAQ2QE&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;/
├── app/
├── pages/
├── middleware.ts  &amp;lt;-- 여기에 미들웨어를 작성합니다.
├── package.json&lt;/code&gt;&lt;/pre&gt;
&lt;/div&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;p data-ke-size=&quot;size16&quot;&gt;단! middleware에서는 node.js 코드를 실행할 수 없습니다!&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Node.js 코드를 사용할 수 없는 이유&lt;/h3&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;하지만 이러한 속도를 위해, 미들웨어는 Node.js 환경에서 사용되는 파일 시스템(fs), http 모듈, 또는 다른 Node.js 전용 라이브러리(예: bcrypt)를 사용할 수 없습니다. 이 때문에 console.log와 같은 기본적인 기능만 사용이 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발 공부/Next</category>
      <author>날씨는 맑음</author>
      <guid isPermaLink="true">https://wheatherisgood.tistory.com/85</guid>
      <comments>https://wheatherisgood.tistory.com/85#entry85comment</comments>
      <pubDate>Sat, 30 Aug 2025 14:58:12 +0900</pubDate>
    </item>
  </channel>
</rss>