<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>항해하는 개발자</title>
    <link>https://voyage-dev.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Sat, 25 Jul 2026 15:04:31 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>Voyage_dev</managingEditor>
    <image>
      <title>항해하는 개발자</title>
      <url>https://tistory1.daumcdn.net/tistory/5259832/attach/1c14aa692d0e4015a2446a8774911be5</url>
      <link>https://voyage-dev.tistory.com</link>
    </image>
    <item>
      <title>Next.js 리액트 서버 컴포넌트</title>
      <link>https://voyage-dev.tistory.com/208</link>
      <description>&lt;blockquote data-ke-style=&quot;style3&quot;&gt;✔️ 기존 SSR의 한계 및 Next.js 서버 컴포넌트의 개념에 대해서 살펴보자&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;React Server Component는 리액트 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;&lt;b&gt;기존 리액트 컴포넌트와 SSR의 한계&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;기존의 React 컴포넌트는 클라이언트에서 작성이 되고 브라우저에서 자바스크립트 코드에 대한 처리가 이루어지게 된다. 그래서 React로 만들어진 페이지는 유저가 웹사이트를 방문했을 때 React 실행에 필요한 코드를 먼저 다운로드 받고 React 컴포넌트 트리를 브라우저에서 만들어주게 된다. React 컴포넌트 트리를 바탕으로 DOM에서 렌더링을 진행하게 된다.&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;기존의 이런 클라이언트 사이드 렌더링 방식에서 여러 가지 한계들이나 성능상의 이슈들을 마주했기 때문에 나온 게 서버 사이드 렌더링이다. 미리 서버에서 DOM을 만들고 만든 DOM을 클라이언트에서 받아서 그 DOM에서 Hydration만 진행해 주는 식으로 DOM을 만드는 작업을 서버 쪽으로 위임을 한 방법이다.&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;기존의 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;&lt;b&gt;자바스크립트 번틀 크기가 항상 존재&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import sanitizeHtml from 'sanitize-html' // 260k

function ExampleComponent({text}: {text:string}) {
	const html = useMemo(() =&amp;gt; sanitizeHtml(text), [text])
	
	return &amp;lt;div dangerouslySetInnerHTML={{_html: html }} /&amp;gt;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;npm 라이브러리 import 시 번들 사이즈가 있고, 클라이언트인 브라우저에서 해당 라이브러리를 다운로드 받아야 하고 실행도 해야 한다. 이 각각의 라이브러리의 번들 사이즈만큼 부담을 안게 된다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&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;/li&gt;
&lt;li&gt;라이브러리는 서버에서 가지고 있고, 라이브러리를 실행한 결과와 컴포넌트 렌더링 결과물만 클라이언트에 제공&lt;/li&gt;
&lt;/ul&gt;
&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;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import db from 'db'

async function ExampleComponent({id}: {id: string}) {
	const text = await db.board.get(id);
	
	return &amp;lt;&amp;gt;{text}&amp;lt;/&amp;gt;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기존의 코드는 클라이언트에서 직접 백엔드에 접근해서 원하는 데이터를 가져올 수가 없었다. 그러다 보니 API 로직이 필요했고 추가적인 백엔드 개발자들의 작업이 늘어났다.&lt;/li&gt;
&lt;li&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;/li&gt;
&lt;li&gt;서버 사이드 렌더링을 하면서 서버 단에서 DOM이 만들어지기 때문에 직접 컴포넌트 단에서 DB에서 데이터를 가져와도 문제가 될 것은 없다.&lt;/li&gt;
&lt;li&gt;이러한 백엔드에서 직접적인 접근을 할 수 없는 것들이 이슈가 되었고 개선하기 위해서 React 서버 컴포넌트의 개념이 나오게 되었다.&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&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;이 작업을 모두 서버에서 작업한다면?
&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;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;리액트는 템플릿 언어가 아니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;템플릿 언어는 HTML에서 할 수 없는 for, if 문 등을 처리 가능하나, 복잡한 추상화나 함수 사용은 어렵다.&lt;/li&gt;
&lt;li&gt;리액트는 자바스크립트 기반으로 다양한 작업이 가능하다 &amp;rarr; 자유를 주지만, 추상화가 복잡해 질 수록 코드양이 많아지고 런타임 오버헤드도 많아진다.&lt;/li&gt;
&lt;li&gt;컴포넌트는 복잡하나, 사용자에게 전달되는 HTML은 단순할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&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;클라이언트, 서버 간 주고받는 결과물이 가벼워 진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;✔️ 새로운 fetch&lt;/blockquote&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 12 버전까지 SSR과 SSG를 위해 사용되던 getServersideProps, getStaticProps, getInitialProps는 앱 라우터에서는 삭제되었다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 데이터 요청은 웹에서 제공하는 표준 API인 fetch를 기반으로 이루어진다&lt;/p&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;예제 코드&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;import { Children } from &quot;react&quot;;

async function getData() {
  const result = await fetch('&amp;lt;https://jsonplaceholder.typicode.com/posts&amp;gt;')

  if(!result.ok) {
    throw new Error('실패');
  }

  return result.json();
}

export default async function Page() {
  const data = await getData();

  return (
  	{data?.map((article: any) =&amp;gt; { return (
		{article.title}
		{article.body}
    	)}
 	  )}
     )
}&lt;/code&gt;&lt;/pre&gt;
&lt;main&gt;&lt;/main&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이런 식으로 앱 라우터 버전에서는 기존의 getServerSideProps 같은 메서드가 아니라 fetch를 가지고 굉장히 간단하게 데이터를 불러올 수 있게 되었다.&lt;/li&gt;
&lt;li&gt;getServerSideProps는 SSR만을 위한 것이었으므로, 이제 서버에서 데이터를 직접 불러올 수 있게 되었다&lt;/li&gt;
&lt;li&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;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Next.js는 fetch 요청에 대한 내용을 서버에서는 렌더링이 한 번 끝날 때 까지 캐싱. 중복된 요청을 방지한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;최초 &amp;rarr; 1-2s&lt;/li&gt;
&lt;li&gt;그 다음 &amp;rarr; 수십 ms&lt;/li&gt;
&lt;li&gt;매번 렌더링을 하는 것이 아니라 한 번 렌더링을 처음에 해주고 그다음에 그 값을 그대로 들고 있다가 보여줄 수가 있어서 서버 사이드 렌더링의 장점을 활용해서 보여주게 된다. 그래서 두 번째 부터는 캐싱을 적용하면 굉장히 빠르게 HTML파일을 불러올 수 있다.&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Next.js 12 버전
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;getStaticProps로 서버 데이터가 변경되지 않는 경우 정적 페이지를 만들어서 제공&lt;/li&gt;
&lt;li&gt;해당 주소의 결과물이 항상 동일 &amp;rarr; CDN 캐싱으로 SSR보다 더 빠르게 제공&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Next.js 13 버전
&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;/ul&gt;
&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;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;캐싱을 했을 때 단점은 데이터가 캐시 시간이 오래 지날수록 stale 하게 바뀔 가능성이 높다. 즉, 낡은 데이터를 계속 볼 수가 있다.&lt;/li&gt;
&lt;li&gt;실시간으로 가장 최신 데이터를 받아와야 되는 서비스의 경우에는 캐시 옵션을 꺼줘야 할 수도 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;// cache : 'no-cache' 옵션 추가
  const result = await fetch(&quot;&amp;lt;https://jsonplaceholder.typicode.com/posts&amp;gt;&quot;, {
    cache:&quot;no-cache&quot;
  });

// Next.js 옵션을 사용하는 것도 가능
    const result = await fetch(&quot;&amp;lt;https://jsonplaceholder.typicode.com/posts&amp;gt;&quot;, {
	    next: { revalidate: 0 }
  });
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;fetch에서 캐싱을 하지 않으면, Next.js는 해당 요청을 미리 빌드해서 대기시켜 두지 않고 요청이 올 때마다 fetch 요청 이후 렌더링을 수행하게 된다.&lt;/li&gt;
&lt;li&gt;시간은 다소 걸리지만 가장 최신의 데이터를 바로바로 받아볼 수 있기 때문에 그러한 기능이 필요한 서비스라면 캐시를 끄고 사용하는 것도 방법이 될 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;fetch 옵션에 따른 작동 방식&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;{ cache: &amp;lsquo;force-cache&amp;rsquo; } : 기본값으로, getStaticProps와 유사하게 불러온 데이터를 캐싱해 해당 데이터로 관리한다.&lt;/li&gt;
&lt;li&gt;{ cache: &amp;lsquo;no-store&amp;rsquo; }: getServerSideProps와 유사하게 캐싱하지 않고 매번 새로운 데이터를 불러온다.&lt;/li&gt;
&lt;li&gt;{ next: { revalidate: 0 }} : getStaticProps에 revalidate를 추가한 것과 동일하며, 정해진 유효시간 동안에는 캐싱하고, 이 유효시간이 지나면 캐싱을 파기한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;캐시와 mutating, revalidating&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;fetch의 기본 작동을 재정의하여, 해당 데이터의 유효한 시간을 정해두고 이 시간이 지나면 다시 데이터를 불러와서 페이지를 렌더링 하는 것이 가능하다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;// app/page.tsx

// 하위에 있는 모든 라우팅에서는 페이지를 1시간(3600초) 간격으로 갱신해 새로 렌더링 하게 된다.
export const revalidate = 3600; 
&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;&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;최초로 해당 라우트로 요청이 올 때는 미리 정적으로 캐시 해 둔 데이터를 보여준다.&lt;/li&gt;
&lt;li&gt;캐시된 초기 요청은 revalidate에 선언된 값 만큼 유지된다.&lt;/li&gt;
&lt;li&gt;해당 시간이 지나도 일단 캐시된 데이터를 보여준다.&lt;/li&gt;
&lt;li&gt;캐시된 데이터를 보여주는 한 편, 시간이 경과했으므로 백그라운드에서 다시 데이터를 불러온다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;✔️ 새롭게 나온 기능들인 터보팩(beta), 서버 액션 등에 대해서 살펴보자&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;Next 13, 14 버전을 통해서 굉장히 새로운 기능들이 많이 등장을 하게 되었다. 그중에서는 Next팀에서 실험적으로 이런 기능을 만들어보면 어떨까? 하는 그러한 시도들도 많이 나타나고 있다. 보통 이러한 시도들은 알파 혹은 베타 버전으로 먼저 출시되는 경우가 많다. 이러한 버전의 기능들을 주의깊게 살펴본다면 정식으로 나왔을 때 좀 더 친숙하게 사용할 수가 있다. 하지만 아직은 안정된 버전이 아니기 때문에 프로덕션 환경에서 사용할 때는 조금 주의를 할 필요가 있다.&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;터보팩(beta)&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;터보팩은 Rust로 작성된 JS, TS를 위한 번들러이다.&lt;/li&gt;
&lt;li&gt;기존의 Webpack을 대체하기 위해 만들어 졌다.&lt;/li&gt;
&lt;li&gt;현재는 개발 환경에서만 사용 가능하다.&lt;/li&gt;
&lt;li&gt;개발 서버 실행 시 -turbo 플래그를 사용&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nextjs.org/docs/architecture/turbopack&quot;&gt;https://nextjs.org/docs/architecture/turbopack&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;scripts&quot;: {
    &quot;dev&quot;: &quot;next dev --turbo&quot;, // --turbo
    &quot;build&quot;: &quot;next build&quot;,
    &quot;start&quot;: &quot;next start&quot;,
    &quot;lint&quot;: &quot;next lint&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아직 베타 버전이고 자바스크립트와 타입스크립트에 최적화된 증분하는 bundler이고 Rust로 작성이 되었고 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;&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;터보팩은 기본적으로 별도의 환경 설정을 할 필요가 없다. &amp;rArr; 굉장히 가볍고 의존이 없기 때문에 다른 확정을 할 수 있는 가능성이 굉장히 열려있다.&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;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;Webpack을 대체하기 위해서 나온 기능이기 때문에 이제 Webpack의 환경 설정을 지원을 하고 있지 않다.&lt;/li&gt;
&lt;li&gt;터보팩은 SWC 컴파일러를 컴파일러의 모든 트랜스파일링 변환과 최적화를 이제 사용할 수 있기 때문에 즉, 레버리지에서 사용을 했기 때문에 Babel 역시 포함이 되지 않는다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Babel은 타입스크립트를 자바스크립트로 트랜스파일링 하는 도구이다. 근데 그 도구를 대체하는 역할로 SWC가 이미 사용이 되었기 때문에 지원을 하고 있지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&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;p data-ke-size=&quot;size16&quot;&gt;서버 액션은 Next 13번에서는 실험 버전으로 나왔고 14 버전에서 정식 버전으로 나온 기능이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서버 액션은 기본적으로 서버에서 실행되는 비동기 함수이다. 그리고 서버 컴포넌트와 클라이언트 컴포넌트에서 form을 제출하거나 데이터를 변경할 때 사용할 수 있는 도구이다.&lt;/li&gt;
&lt;li&gt;서버 액션은 API를 굳이 생성하지 않더라도 함수 수준에서 서버에 직접 접근해 데이터 요청 등을 수행할 수 있는 기능이다.&lt;/li&gt;
&lt;li&gt;서버 액션은 반드시 &amp;ldquo;use server&amp;rdquo; 지시자 안에서만 사용될 수 있다. 서버 컴포넌트와 클라이언트 컴포넌트에서 둘 다 사용이 가능하다. 단, 클라이언트 컴포넌트에서는 모듈 레벨에서 액션을 import 하는 것만 가능하다.&lt;/li&gt;
&lt;li&gt;&amp;ldquo;use server&amp;rdquo;를 컴포넌트의 최상단에 넣던지 비동기 함수의 가장 상단에 넣어서 사용&lt;/li&gt;
&lt;li&gt;일반적으로 &amp;lt;form&amp;gt; 요소의 action 메서드에 의해 사용된다. 하지만 form 데이터 이외에도 useEffect나 이벤트 핸들러에도 함께 사용할 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations&quot;&gt;https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// Server Component
export default function Page() {
  // Server Action
  async function create() {
    'use server'
 
    // ...
  }
 
  return (
    // ...
  )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서버 컴포넌트에서는 인라인 레벨의 함수 그리고 모듈 레벨에서 'use server'라는 지시자를 포함을 해서 사용&lt;/li&gt;
&lt;li&gt;이 서버 액션을 안에다 넣기 위해서는 그 함수의 가장 최상단에 'use server' 지시자를 사용&lt;/li&gt;
&lt;li&gt;함수는 비동기 함수여야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;'use server'
 
export async function create() {
  // ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라이언트 컴포넌트에서는 모듈 레벨에서 import 할 때 사용할 수가 있고 'use server'라는 지시자를 포함해서 사용&lt;/li&gt;
&lt;li&gt;이 파일 안에서는 모든 함수들은 서버 액션으로 표시가 되고 클라이언트 컴포넌트와 서버 컴포넌트에서 재사용이 되는 함수를 만들 수가 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import { create } from '@/app/actions'
 
export function Button() {
  return (
    // ...
  )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버 액션을 클라이언트 컴포넌트의 props 형태로도 전달해서 사용할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;ClientComponent updateItem={updateItem} /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;'use client'
 
export default function ClientComponent({ updateItem }) {
  return &amp;lt;form action={updateItem}&amp;gt;{/* ... */}&amp;lt;/form&amp;gt;
}
&lt;/code&gt;&lt;/pre&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;일반적으로 이 서버 액션이 가장 많이 사용되는 부분은 form 요소에서 액션 속성을 우리가 유발할 때 많이 사용된다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서버 컴포넌트가 기본적으로 점진적인 보강을 지원을 하는데 form이 자바스크립트에서 제출이 될 때 자바스크립트가 아직 로드가 되지 않거나 사용되지 않는 경우에도 form이 제출이 될 수 있도록 서버 컴포넌트에서는 지원을 하고 있다.&lt;/li&gt;
&lt;li&gt;클라이언트 컴포넌트에서는 form이 서버 액션을 유발을 해서 form 제출을 큐에다가 넣는 작업을 할 수가 있다. 이 역시 자바스크립트가 아직 로드가 되지 않거나 클라이언트 쪽에서 일어난 hydration 작업에 우선순위가 밀리더라도 form 제출을 할 수 있게 해준다.&lt;/li&gt;
&lt;li&gt;hydration 작업이 다 되고 나서 브라우저는 form 제출을 refresh 하지 않게 해주는 기능도 있다.&lt;/li&gt;
&lt;li&gt;서버 액션은 이렇게 form에서만 국한되서 사용되는 것이 아니라 이벤트 핸들러라든지 useEffect 그리고 서드파티 도구들 버튼과 같은 다른 form 요소들에서도 사용이 될 수가 있다.&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;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Form&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;React는 HTML의 form 이라는 요소를 확장해서 쓸 수가 있는데 그 요소 안에서 서버 액션을 action props 안에서 사용할 수가 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;export default function Page() {
  async function createInvoice(formData: FormData) {
    'use server'
 
    const rawFormData = {
      customerId: formData.get('customerId'),
      amount: formData.get('amount'),
      status: formData.get('status'),
    }
 
    // mutate data
    // revalidate cache
  }
 
  return &amp;lt;form action={createInvoice}&amp;gt;...&amp;lt;/form&amp;gt;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 액션 속성에서는 자동으로 formData 오브젝트를 수신을 받게 된다. 이때 이 formData 데이터 안에 상태를 관리를 해야 되는데 이제는 더이상 useState 같은 상태관리 훅을 사용할 필요가 없다.&lt;/li&gt;
&lt;li&gt;대신에 formData에서 제공하는 메서드를 네이티브 메서드들을 추출해서 사용할 수가 있다.&lt;/li&gt;
&lt;li&gt;createInvoice 함수 안에서 서버 액션을 만들어주고 안에서 formData를 설정 해주고 데이터를 수정해주고 revalidate해주는 역할까지 할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;'use client'
 
import { updateUser } from './actions'
 
export function UserProfile({ userId }: { userId: string }) {
  const updateUserWithId = updateUser.bind(null, userId)
 
  return (
    &amp;lt;form action={updateUserWithId}&amp;gt;
      &amp;lt;input type=&quot;text&quot; name=&quot;name&quot; /&amp;gt;
      &amp;lt;button type=&quot;submit&quot;&amp;gt;Update User Name&amp;lt;/button&amp;gt;
    &amp;lt;/form&amp;gt;
  )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라이언트 컴포넌트에서는 추가적인 인자들을 bind 같은 메서드를 통해서 추가해 줄 수도 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;'use client'
 
import { useFormStatus } from 'react-dom'
 
export function SubmitButton() {
  const { pending } = useFormStatus()
 
  return (
    &amp;lt;button type=&quot;submit&quot; disabled={pending}&amp;gt;
      Add
    &amp;lt;/button&amp;gt;
  )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;formData를 제출하기 전에 pending 되는 상태들이 있을 수가 있는데 useFormStatus라는 훅을 통해서 이 pending 상태를 제출하기 전에 pending 상태를 받아올 수가 있다. 그래서 이 상태를 form에서 버튼 같은 데에서 아직 제출이 되기 전이면 스피너 같은 걸 보여준다든지 아니면 비활성화를 시켜준다든지 중복되는 요청을 막을 수 있는 로직도 구현을 할 수가 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;'use server'
 
import { z } from 'zod'
 
const schema = z.object({
  email: z.string({
    invalid_type_error: 'Invalid Email',
  }),
})
 
export default async function createUser(formData: FormData) {
  const validatedFields = schema.safeParse({
    email: formData.get('email'),
  })
 
  // Return early if the form data is invalid
  if (!validatedFields.success) {
    return {
      errors: validatedFields.error.flatten().fieldErrors,
    }
  }
 
  // Mutate data
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;아무래도 formData를 처리하면서 가장 신경을 많이 쓰는 부분 중의 하나가 validation 즉, 유효성 검증하는 부분이다.&lt;/li&gt;
&lt;li&gt;이러한 formData를 유효성 검증하고 또 에러를 처리하는 부분을 서버 사이드에서 해줄 수도 있다.&lt;/li&gt;
&lt;li&gt;form 필드를 zod라는 라이브러리를 통해 수정을 하기 전에 검증을 할 수가 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;useFormState&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;'use client'
 
import { useFormState } from 'react-dom'
import { createUser } from '@/app/actions'
 
const initialState = {
  message: '',
}
 
export function Signup() {
  const [state, formAction] = useFormState(createUser, initialState)
 
  return (
    &amp;lt;form action={formAction}&amp;gt;
      &amp;lt;label htmlFor=&quot;email&quot;&amp;gt;Email&amp;lt;/label&amp;gt;
      &amp;lt;input type=&quot;text&quot; id=&quot;email&quot; name=&quot;email&quot; required /&amp;gt;
      {/* ... */}
      &amp;lt;p aria-live=&quot;polite&quot; className=&quot;sr-only&quot;&amp;gt;
        {state?.message}
      &amp;lt;/p&amp;gt;
      &amp;lt;button&amp;gt;Sign up&amp;lt;/button&amp;gt;
    &amp;lt;/form&amp;gt;
  )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;필드가 서버 안에서 유효함이 입증이 됐을 때 서버는 직렬화된 오브젝트를 리액트 클라이언트 쪽으로 보내주게 된다. 리액트에서 useFormState로 사용자에게 메시지를 보여줄 수가 있게 된다.&lt;/li&gt;
&lt;li&gt;createUser라는 서버 액션이 유저를 만드는 작업을 하면서 만약에 이메일이 유효하지 않았을 때 state의 메시지를 통해서 에러를 보여준다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;useOptimistic&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;'use client'
 
import { useOptimistic } from 'react'
import { send } from './actions'
 
type Message = {
  message: string
}
 
export function Thread({ messages }: { messages: Message[] }) {
  const [optimisticMessages, addOptimisticMessage] = useOptimistic&amp;lt;
    Message[],
    string
  &amp;gt;(messages, (state, newMessage) =&amp;gt; [...state, { message: newMessage }])
 
  return (
    &amp;lt;div&amp;gt;
      {optimisticMessages.map((m, k) =&amp;gt; (
        &amp;lt;div key={k}&amp;gt;{m.message}&amp;lt;/div&amp;gt;
      ))}
      &amp;lt;form
        action={async (formData: FormData) =&amp;gt; {
          const message = formData.get('message')
          addOptimisticMessage(message)
          await send(message)
        }}
      &amp;gt;
        &amp;lt;input type=&quot;text&quot; name=&quot;message&quot; /&amp;gt;
        &amp;lt;button type=&quot;submit&quot;&amp;gt;Send&amp;lt;/button&amp;gt;
      &amp;lt;/form&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;업테이트를 하는 과정에서도 UI를 업데이트할 때 굉장히 불필요한 상태 변경이라든지 렌더링이 일어날 수가 있는데 렌더링을 하는 부분을 최적화 해 줄 있는 훅이 바로 useOptimistic이다.&lt;/li&gt;
&lt;li&gt;응답을 기다리기보다는 서버 액션이 끝나기 전에 상태를 UI를 업데이트 해주는 과정에서 최적화를 해주는 훅이라고 보면 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Non-form Elements&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;FormData를 사용할 때 뿐만 아니고 서버 액션은 formData가 아닌 경우에도 사용을 할 수가 있다&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이벤트 핸들러를 사용을 할 때 예를 들면, onClick을 사용을 할 때 서버 액션을 통해서 처리를 해 줄 수가 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;'use client'
 
import { incrementLike } from './actions'
import { useState } from 'react'
 
export default function LikeButton({ initialLikes }: { initialLikes: number }) {
  const [likes, setLikes] = useState(initialLikes)
 
  return (
    &amp;lt;&amp;gt;
      &amp;lt;p&amp;gt;Total Likes: {likes}&amp;lt;/p&amp;gt;
      &amp;lt;button
        onClick={async () =&amp;gt; {
          const updatedLikes = await incrementLike()
          setLikes(updatedLikes)
        }}
      &amp;gt;
        Like
      &amp;lt;/button&amp;gt;
    &amp;lt;/&amp;gt;
  )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;onClick 안에서 incrementLike라는 서버 액션을 설정을 해줘서 likes의 업데이트를 해주는 방법을 설정을 해줄 수가 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;'use client'
 
import { publishPost, saveDraft } from './actions'
 
export default function EditPost() {
  return (
    &amp;lt;form action={publishPost}&amp;gt;
      &amp;lt;textarea
        name=&quot;content&quot;
        onChange={async (e) =&amp;gt; {
          await saveDraft(e.target.value)
        }}
      /&amp;gt;
      &amp;lt;button type=&quot;submit&quot;&amp;gt;Publish&amp;lt;/button&amp;gt;
    &amp;lt;/form&amp;gt;
  )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;그리고 이 과정에서 아까 살펴봤었던 useOptimistic이나 useTransition을 통해서 UI 업데이트를 조금 최적화하는 방법도 고려를 해볼 수가 있다.&lt;/li&gt;
&lt;li&gt;이벤트 핸들러를 form 요소에 붙일 수도 있다. 값이 바뀔 때 마다 실행되는 onChange 핸들러의 서버 액션인 saveDraft를 호출해서 사용할 수가 있다.&lt;/li&gt;
&lt;li&gt;이런 경우에 여러 이벤트가 빠르게 연속적으로 발생할 수가 있다. 예를 들면, textarea의 값이 바뀌는 경우 서버 액션의 디바운스를 쓰는 걸 권장한다.&lt;/li&gt;
&lt;li&gt;왜냐하면 여러 이벤트가 동시에 발생되면서 상태들이 올바르게 업데이트가 안 될 수도 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;useEffect&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;useEffect를 사용할 때도 서버 액션으로 트리거를 해 줄 수가 있다.&lt;/li&gt;
&lt;li&gt;예를 들어 컴포넌트가 마운트가 될 때 그리고 의존성이 바뀔 때 변화들을 자동적으로 쫓아갈 수 있게 하는 과정에서 서버 액션이 도움이 될 수가 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;'use client'
 
import { incrementViews } from './actions'
import { useState, useEffect } from 'react'
 
export default function ViewCount({ initialViews }: { initialViews: number }) {
  const [views, setViews] = useState(initialViews)
 
  useEffect(() =&amp;gt; {
    const updateViews = async () =&amp;gt; {
      const updatedViews = await incrementViews()
      setViews(updatedViews)
    }
 
    updateViews()
  }, [])
 
  return &amp;lt;p&amp;gt;Total Views: {views}&amp;lt;/p&amp;gt;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;initialViews 라는 값을 받고 incrementViews 라는 이제 뷰 값을 올려주는 서버 액션을 이 useEffect 안에서 비동기로 처리해 줄 수가 있다.&lt;/li&gt;
&lt;li&gt;그렇게 해서 상태를 업데이트 하면 incrementViews가 서버 쪽에서 로직이 수행이 되면서 상태가 업데이트가 된 뷰 값이 클라이언트에서 올바르게 보여진다.&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;이러한 요소들 외에도 에러를 처리하는 방법, 데이터를 다시 revalidate 하는 방법, redirecting 하는 방법 등등 이 서버 액션을 통해서 이런 이벤트를 처리하거나 데이터를 변경하는 과정에서 쓰이는 것 뿐만 아니라 그 이후에 하는 작업들에서 사용되는 경우가 많이 있다. 이러한 문법들은 공식문서를 읽어보면서 필요에 따라 사용해 보자.&lt;/p&gt;</description>
      <category>Front-End/Next</category>
      <category>NeXT</category>
      <category>next.js</category>
      <category>nextjs</category>
      <category>server components</category>
      <category>공부</category>
      <category>기록</category>
      <category>리액트 서버 컴포넌트</category>
      <category>복습</category>
      <category>서버 컴포넌트</category>
      <author>Voyage_dev</author>
      <guid isPermaLink="true">https://voyage-dev.tistory.com/208</guid>
      <comments>https://voyage-dev.tistory.com/208#entry208comment</comments>
      <pubDate>Sun, 18 Aug 2024 16:02:36 +0900</pubDate>
    </item>
    <item>
      <title>Next.js App Router</title>
      <link>https://voyage-dev.tistory.com/207</link>
      <description>&lt;blockquote data-ke-style=&quot;style3&quot;&gt;✔️ Next.js 앱 라우터의 개념과 Page, Layout에 대해서 살펴보자&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;Next 12 버전까지는 페이지 라우터 방식이 기본 방식으로 잘 사용되어 왔다. 기존의 페이지 라우터 방식을 잘 사용하고 있었는데 왜 13 버전부터는 앱 라우터라는 새로운 라우터 방식이 나왔을까?&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;Page Router 방식의 한계&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 페이지에 들어가는 &amp;lt;Header /&amp;gt; 컴포넌트를 Next.js 페이지 라우터 방식으로 구현을 하기 위해서는 어떻게 해야 할까?&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;각각의 페이지 컴포넌트에 &amp;lt;Header /&amp;gt;를 일일이 넣는다 (권장하지 않는다)&lt;/li&gt;
&lt;li&gt;_app, _document 같은 파일에 &amp;lt;Header /&amp;gt;를 넣는다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이전에 살펴본 _app, _document의 목적&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;_app : 페이지를 초기화하기 위한 용도로 사용
&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;global CSS 주입&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;_document : 페이지에서 쓰이는 &amp;lt;html&amp;gt;, &amp;lt;body&amp;gt; 태그를 수정하거나, SSR 시 styled-components 같은 일부 CSS-in-JS를 지원하기 위한 코드 삽입 하는 제한적인 용도로 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 &amp;lt;Header /&amp;gt;가 모든 페이지에서 다 들어간 경우도 있지만 일부 페이지에서만 들어가게 되는 경우 예를 들어 A,B,C 페이지에서는 들어가지만 D,E 넣지 않을 때 즉, 일부 특정 컴포넌트에서만 넣어줘야 된다라고 한다면 앱이나 도큐멘트에서 사용하는 것은 살짝 무리가 있을 수가 있다. 그리고 프로젝트가 복잡해질수록 이렇게 여러 컴포넌트에서 같이 써야 되는 컴포넌트들이 굉장히 많아질 수가 있다. 근데 그거를 모두 앱이나 도큐멘트에서 관리를 하게 된다면 복잡도가 굉장히 높아지고 좋지 않은 구조가 될 가능성이 높다.&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;&amp;rarr; 이러한 한계를 극복하기 위해 나온 것이 바로 app 레이아웃이다&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;App Route는 Next 13.4 버전부터 정식 기능으로 릴리즈&lt;/p&gt;
&lt;pre class=&quot;dsconfig&quot;&gt;&lt;code&gt;npx create-next-app@latest
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;TypeScript : Y&lt;/li&gt;
&lt;li&gt;ESLint : Y&lt;/li&gt;
&lt;li&gt;Tailwind CSS (옵션)&lt;/li&gt;
&lt;li&gt;/src directory : N&lt;/li&gt;
&lt;li&gt;&lt;b&gt;App Router : Y&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;import alias : N&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기존에 /pages 로 정의하던 라우팅 방식이 /app 디렉터리로 이동했다는 점&lt;/li&gt;
&lt;li&gt;파일 명으로 라우팅 하는 것이 불가능했졌다는 점&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;layout.tsx&lt;/b&gt;&lt;/p&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;631&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5eIYs/btsIUwPtKw9/OAUh4LlZUKBrjfJEmG5bPk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5eIYs/btsIUwPtKw9/OAUh4LlZUKBrjfJEmG5bPk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5eIYs/btsIUwPtKw9/OAUh4LlZUKBrjfJEmG5bPk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5eIYs%2FbtsIUwPtKw9%2FOAUh4LlZUKBrjfJEmG5bPk%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;631&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;631&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;create-next-app로 우리가 보일러 플레이트로 프로젝트를 생성했을 때 기본으로 만들어지는 파일 중에 하나이다. 이 파일의 목적은 페이지의 레이아웃을 구성하는 요소로서 역할을 하고 있다. 즉, 어떤 폴더에 app 이라는 디렉터리가 있는데 이 폴더에 레이아웃이 있다면 이 레이아웃은 이 폴더 그리고 이 폴더의 하위 주소에 모두 영향을 미친다.&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;루트에는 단 하나의 layouy을 둘 수 있다&lt;/li&gt;
&lt;li&gt;이 layout은 모든 페이지에 영향을 미치는 공통 레이아웃&lt;/li&gt;
&lt;li&gt;페이지 하위에 추가되는 layout은 해당 주소 하위에만 적용&lt;/li&gt;
&lt;li&gt;layout은 주소별 공통 UI를 포함할 수 있을 뿐 아니라 _app과 _document를 대신해 웹 페이지를 시작하는데 필요한 공통 코드 삽입&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 레이아웃은 앱 디렉터리 내부에서 사용하는 예약어이다. Next.js 에서 지정된 예약어이기 때문에 무조건 레이아웃을 사용해야 한다. 즉, 다른 목적으로는 사용할 수가 없다.&lt;/li&gt;
&lt;li&gt;레이아웃은 children을 받아서 뿌려주는 역할을 하고 있다. 그래서 우리가 만들어야 되는 컴포넌트는 외부에서 주입을 받아서 그려야 한다.&lt;/li&gt;
&lt;li&gt;레이아웃 컴포넌트는 반드시 export default로 내보내는 컴포넌트를 가지고 있어야 한다.&lt;/li&gt;
&lt;li&gt;API와 같은 비동기 작업도 할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;page.tsx&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;li&gt;page는 앞에서 구성했던 layout을 기반으로 리액트 컴포넌트 노출&lt;/li&gt;
&lt;li&gt;page가 받는 props
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;params : 옵셔널 값, [&amp;hellip;id]와 같은 동적 라우트 파라미터 사용 시 값이 들어온다&lt;/li&gt;
&lt;li&gt;searchParams : ?a=1 과 같은 URLSearchParams값을 의미한다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 값은 layout에서는 제공되지 않는다. 왜냐하면 layout은 페이지 탐색 중에 리렌더링을 수행하지 않기 때문이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&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;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://nextjs.org/docs/app/building-your-application/routing&quot;&gt;https://nextjs.org/docs/app/building-your-application/routing&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://nextjs.org/docs/app/building-your-application/routing/defining-routes&quot;&gt;https://nextjs.org/docs/app/building-your-application/routing/defining-routes&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://nextjs.org/docs/app/building-your-application/routing/pages-and-layouts&quot;&gt;https://nextjs.org/docs/app/building-your-application/routing/pages-and-layouts&lt;/a&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;page.js : 라우트에 유니크한 UI 컴포넌트&lt;/li&gt;
&lt;li&gt;layout.js : 여러 라우트에 걸쳐서 공유되는 UI 컴포넌트&lt;/li&gt;
&lt;li&gt;template.js : 레이아웃과 비슷한 요소
&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;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&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;&lt;b&gt;링킹 &amp;amp; 네비게이팅&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;b&gt;라우트 네비게이션을 할 수 있는 4가지 방법&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://nextjs.org/docs/app/building-your-application/routing/linking-and-navigating&quot;&gt;https://nextjs.org/docs/app/building-your-application/routing/linking-and-navigating&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1722874951135&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Routing: Linking and Navigating | Next.js&quot; data-og-description=&quot;Learn how navigation works in Next.js, and how to use the Link Component and &amp;#96;useRouter&amp;#96; hook.&quot; data-og-host=&quot;nextjs.org&quot; data-og-source-url=&quot;https://nextjs.org/docs/app/building-your-application/routing/linking-and-navigating&quot; data-og-url=&quot;https://nextjs.org/docs/app/building-your-application/routing/linking-and-navigating&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/citzSs/hyWKKTaXqo/82XSed70RAH1iAYeZ2YbK1/img.png?width=843&amp;amp;height=441&amp;amp;face=0_0_843_441,https://scrap.kakaocdn.net/dn/N67oi/hyWKEekpqI/UTUqM8WW0juEsUaA7l0VE0/img.png?width=843&amp;amp;height=441&amp;amp;face=0_0_843_441,https://scrap.kakaocdn.net/dn/byAxd8/hyWKvBH2Au/DNu6UHdTKTgG2wpysEknZ1/img.png?width=1600&amp;amp;height=945&amp;amp;face=0_0_1600_945&quot;&gt;&lt;a href=&quot;https://nextjs.org/docs/app/building-your-application/routing/linking-and-navigating&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://nextjs.org/docs/app/building-your-application/routing/linking-and-navigating&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/citzSs/hyWKKTaXqo/82XSed70RAH1iAYeZ2YbK1/img.png?width=843&amp;amp;height=441&amp;amp;face=0_0_843_441,https://scrap.kakaocdn.net/dn/N67oi/hyWKEekpqI/UTUqM8WW0juEsUaA7l0VE0/img.png?width=843&amp;amp;height=441&amp;amp;face=0_0_843_441,https://scrap.kakaocdn.net/dn/byAxd8/hyWKvBH2Au/DNu6UHdTKTgG2wpysEknZ1/img.png?width=1600&amp;amp;height=945&amp;amp;face=0_0_1600_945');&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;Routing: Linking and Navigating | Next.js&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Learn how navigation works in Next.js, and how to use the Link Component and `useRouter` hook.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;nextjs.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Link 컴포넌트 (가장 권장)&lt;/li&gt;
&lt;li&gt;useRouter 훅 (클라이언트 컴포넌트)&lt;/li&gt;
&lt;li&gt;redirect 함수 (서버 컴포넌트)&lt;/li&gt;
&lt;li&gt;History API&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;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;Error.js 동작원리&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://nextjs.org/docs/app/building-your-application/routing/error-handling&quot;&gt;https://nextjs.org/docs/app/building-your-application/routing/error-handling&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1722874952874&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Routing: Error Handling | Next.js&quot; data-og-description=&quot;Handle runtime errors by automatically wrapping route segments and their nested children in a React Error Boundary.&quot; data-og-host=&quot;nextjs.org&quot; data-og-source-url=&quot;https://nextjs.org/docs/app/building-your-application/routing/error-handling&quot; data-og-url=&quot;https://nextjs.org/docs/app/building-your-application/routing/error-handling&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cjOsMU/hyWKy6fQCc/vzaBtZg4b6Y9kBbAH7vR5K/img.png?width=843&amp;amp;height=441&amp;amp;face=0_0_843_441,https://scrap.kakaocdn.net/dn/PHOsc/hyWKJzW7Pc/pBq43mTEOTMmyjhCUsJOM0/img.png?width=843&amp;amp;height=441&amp;amp;face=0_0_843_441,https://scrap.kakaocdn.net/dn/jWz2M/hyWKwN7AnH/KX33TulZiJShKXNS8bhGw1/img.png?width=1600&amp;amp;height=901&amp;amp;face=0_0_1600_901&quot;&gt;&lt;a href=&quot;https://nextjs.org/docs/app/building-your-application/routing/error-handling&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://nextjs.org/docs/app/building-your-application/routing/error-handling&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cjOsMU/hyWKy6fQCc/vzaBtZg4b6Y9kBbAH7vR5K/img.png?width=843&amp;amp;height=441&amp;amp;face=0_0_843_441,https://scrap.kakaocdn.net/dn/PHOsc/hyWKJzW7Pc/pBq43mTEOTMmyjhCUsJOM0/img.png?width=843&amp;amp;height=441&amp;amp;face=0_0_843_441,https://scrap.kakaocdn.net/dn/jWz2M/hyWKwN7AnH/KX33TulZiJShKXNS8bhGw1/img.png?width=1600&amp;amp;height=901&amp;amp;face=0_0_1600_901');&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;Routing: Error Handling | Next.js&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Handle runtime errors by automatically wrapping route segments and their nested children in a React Error Boundary.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;nextjs.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Next.js 에러 핸들링은 기본적으로 중접된 라우트를 기본으로 사용하고 런타임에서 오류를 좀 우아하게 처리를 하는 방식을 지향하고 있다. 그래서 Error를 처리할 때 그냥 Error가 났다고 바로 Error 페이지로 이동하는게 아니라 Error가 발생한 그 레이아웃 그리고 그 Error가 영향을 받는 자식 컴포넌트들을 기반으로 Error에 대한 처리를 할 영역을 경계를 랩핑하는 작업을 첫 번째로 하게 된다.&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;그래서 이 랩핑 된 이 영역 외에 다른 부분은 정상적으로 동작을 할 수 있게 만드는 것이 Next.js의 Error 핸들링의 중요한 원칙 중의 하나이다. 즉, 전체 페이지를 다 다시 로드하는게 아니라 Error에서 회복을 할 수 있는, 리커버를 할 수 있는 기능을 줌으로써 잘 사용되던 기능들을 제대로 최대한 살리고 Error가 난 부분만 빠르게 복구할 수 있게 하는 전략을 가져가고 있다. 그리고 Error가 발생했을 때 Error를 대체할 그런 대체 컴포넌트들을 활성화 시켜준다.&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;전체 페이지를 다시 로드하는 것이 아닌, Error에서 recover 기능 추가&lt;/li&gt;
&lt;li&gt;Error 발생 시 대체 컴포넌트를 활성화, 상호작용이 가능하며 reset 기능 추가&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&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;&lt;b&gt;라우트 그룹&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://nextjs.org/docs/app/building-your-application/routing/route-groups&quot;&gt;https://nextjs.org/docs/app/building-your-application/routing/route-groups&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1722874970589&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Routing: Route Groups | Next.js&quot; data-og-description=&quot;Route Groups can be used to partition your Next.js application into different sections.&quot; data-og-host=&quot;nextjs.org&quot; data-og-source-url=&quot;https://nextjs.org/docs/app/building-your-application/routing/route-groups&quot; data-og-url=&quot;https://nextjs.org/docs/app/building-your-application/routing/route-groups&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/Fhpq0/hyWKx7ni7v/GO1ujhkAmgddFYz3Is5UY0/img.png?width=843&amp;amp;height=441&amp;amp;face=0_0_843_441,https://scrap.kakaocdn.net/dn/mGHMf/hyWKyrDygR/TO7zMKLBTgK81D9IEJC5Yk/img.png?width=843&amp;amp;height=441&amp;amp;face=0_0_843_441,https://scrap.kakaocdn.net/dn/fXjch/hyWKJ01WLr/fK85TckxJK6s5xidkaBNk1/img.png?width=1600&amp;amp;height=930&amp;amp;face=0_0_1600_930&quot;&gt;&lt;a href=&quot;https://nextjs.org/docs/app/building-your-application/routing/route-groups&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://nextjs.org/docs/app/building-your-application/routing/route-groups&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/Fhpq0/hyWKx7ni7v/GO1ujhkAmgddFYz3Is5UY0/img.png?width=843&amp;amp;height=441&amp;amp;face=0_0_843_441,https://scrap.kakaocdn.net/dn/mGHMf/hyWKyrDygR/TO7zMKLBTgK81D9IEJC5Yk/img.png?width=843&amp;amp;height=441&amp;amp;face=0_0_843_441,https://scrap.kakaocdn.net/dn/fXjch/hyWKJ01WLr/fK85TckxJK6s5xidkaBNk1/img.png?width=1600&amp;amp;height=930&amp;amp;face=0_0_1600_930');&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;Routing: Route Groups | Next.js&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Route Groups can be used to partition your Next.js application into different sections.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;nextjs.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&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;우리가 폴더 구조를 만들고 경로를 만들 때 모든 경로를 다 필요로 하지는 않을 때가 있다. 어떤 경로는 빼야 되는 상황도 있을 수가 있다. 이때 우리가 App Router에서 사용되는 개념이 바로 라우트 그룹이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;폴더를 라우트 그룹으로 표시하여 라우트의 URL 경로에 폴더가 포함되지 않도록 할 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 이를 통해 URL 경로 구조에 영향을 주지 않고 라우트 세그먼트와 프로젝트 파일을 논리적 그룹으로 조작할 수 있다.&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;e.g.&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;app/(marketing)/account &amp;rarr; /account
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;앱 디렉토리에 marketing 이라는 폴더를 만들고 그 하위에 account라는 폴더를 만들어서 거기에서 페이지나 레이아웃이라는 컴포넌트를 만들었을 때 marketing에 해당되는 이 폴더명을 쓰고 싶지 않다 라고 한다면 이 폴더를 이렇게 () 괄호 처리를 해줄 수가 있다.&lt;/li&gt;
&lt;li&gt;그러면 이 account 안에 있는 페이지 컴포넌트나 레이아웃 컴포넌트는 경로를 marketing account가 아니라 그냥 account로 받게 된다.&lt;/li&gt;
&lt;li&gt;이렇게 라우트 그룹을 통해서 사용하지 않는 경로 구조를 필요에 따라서 제거해 줄 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;❗ 라우트 그룹을 포함하는 라우트는 다른 라우트와 동일한 URL 경로로 해석되어서는 안된다&lt;br /&gt;&lt;br /&gt;예를 들어, 라우트 그룹이 URL 구조에 영향을 주지 않기 때문에, (marketing)/about/page.js와 (shop)/about/page.js는 모두 /about 으로 해석되어 오류를 일으킬 수 있다&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;&lt;b&gt;병렬 라우트&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://nextjs.org/docs/app/building-your-application/routing/parallel-routes&quot;&gt;https://nextjs.org/docs/app/building-your-application/routing/parallel-routes&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1722875004891&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Routing: Parallel Routes | Next.js&quot; data-og-description=&quot;Simultaneously render one or more pages in the same view that can be navigated independently. A pattern for highly dynamic applications.&quot; data-og-host=&quot;nextjs.org&quot; data-og-source-url=&quot;https://nextjs.org/docs/app/building-your-application/routing/parallel-routes&quot; data-og-url=&quot;https://nextjs.org/docs/app/building-your-application/routing/parallel-routes&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/XJ8Gp/hyWKFEjEOr/hbcuFGvdWilJGTBolyqvyk/img.png?width=843&amp;amp;height=441&amp;amp;face=0_0_843_441,https://scrap.kakaocdn.net/dn/dNSMUH/hyWKxGgzAF/uiFOYtJ2xb5XETlSUBxI3k/img.png?width=843&amp;amp;height=441&amp;amp;face=0_0_843_441,https://scrap.kakaocdn.net/dn/bOONBk/hyWKyd6tRo/WFPAmIrIopj6FNNhyQorRk/img.png?width=1600&amp;amp;height=942&amp;amp;face=0_0_1600_942&quot;&gt;&lt;a href=&quot;https://nextjs.org/docs/app/building-your-application/routing/parallel-routes&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://nextjs.org/docs/app/building-your-application/routing/parallel-routes&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/XJ8Gp/hyWKFEjEOr/hbcuFGvdWilJGTBolyqvyk/img.png?width=843&amp;amp;height=441&amp;amp;face=0_0_843_441,https://scrap.kakaocdn.net/dn/dNSMUH/hyWKxGgzAF/uiFOYtJ2xb5XETlSUBxI3k/img.png?width=843&amp;amp;height=441&amp;amp;face=0_0_843_441,https://scrap.kakaocdn.net/dn/bOONBk/hyWKyd6tRo/WFPAmIrIopj6FNNhyQorRk/img.png?width=1600&amp;amp;height=942&amp;amp;face=0_0_1600_942');&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;Routing: Parallel Routes | Next.js&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Simultaneously render one or more pages in the same view that can be navigated independently. A pattern for highly dynamic applications.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;nextjs.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&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;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;슬롯은 라우트 세그먼트가 아니며, URL 구조에 영향을 주지 않는다&lt;/li&gt;
&lt;/ul&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;753&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Pzski/btsIWwtPy8m/TdQP2czKbBeOsjGCr9Cpy0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Pzski/btsIWwtPy8m/TdQP2czKbBeOsjGCr9Cpy0/img.png&quot; data-alt=&quot;https://nextjs.org/docs/app/building-your-application/routing/parallel-routes&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Pzski/btsIWwtPy8m/TdQP2czKbBeOsjGCr9Cpy0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPzski%2FbtsIWwtPy8m%2FTdQP2czKbBeOsjGCr9Cpy0%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;753&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;753&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://nextjs.org/docs/app/building-your-application/routing/parallel-routes&lt;/figcaption&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;이렇게 앱 디렉토리 하위에서 @team 이라는 이름의 병렬 라우트 슬롯 그리고 @analytics라는 슬롯을 만들고 둘 다 이렇게 페이지라는 이름으로 페이지 컴포넌트를 만들었을 때 이 요소들을 다음과 같이 레이아웃에서 두 개를 동시에 보여주는 병렬 라우팅을 처리를 할 수가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Next.js는 각각의 슬롯에 대해서 활성 상태 추적을 하고 있다. 그런데 이 슬롯 안에서 렌더링이 되는 컨텐츠는 어떠한 타입의 네비게이션에 의해서 이루어지는지에 따라서 렌더링 되는 방식이 조금 달라진다. 크게 두 가지 네비게이션 방식이 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Soft Navigation : 클라이언트 측에서 네비게이션을 하는 과정에서 Next.js가 부분적인 렌더링을 수행을 하고 슬롯 안에 있는 하위 페이지들을 변경을 해주게 된다. 그리고 그 동안에 지금 URL과 일치하지 않더라도 다른 슬롯에 활성된 하위 페이지를 유지하는 과정이 바로 Soft Navigation의 방법이다.&lt;/li&gt;
&lt;li&gt;Hard Navigation : 전체 페이지를 로드하고 나서 Next.js는 지금 URL과 일치하지 않는 슬롯의 활성 상태를 결정하지 않는다. 대신에 일치하지 않은 슬롯은 디폴트 파일을 통해서 렌더링을 하거나 디폴트 파일이 없으면 404를 통해서 렌더링을 하는 방식으로 사용이 된다.&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;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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;718&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bMkBoG/btsITtTyjFM/Hn4GrSZQA5LC2P03Qfmyl1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bMkBoG/btsITtTyjFM/Hn4GrSZQA5LC2P03Qfmyl1/img.png&quot; data-alt=&quot;https://nextjs.org/docs/app/building-your-application/routing/parallel-routes&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bMkBoG/btsITtTyjFM/Hn4GrSZQA5LC2P03Qfmyl1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbMkBoG%2FbtsITtTyjFM%2FHn4GrSZQA5LC2P03Qfmyl1%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;718&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;718&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://nextjs.org/docs/app/building-your-application/routing/parallel-routes&lt;/figcaption&gt;
&lt;/figure&gt;
&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;어떤 조건에 따라서, 특히 대시보드 같은 서비스를 만들 때는 유저의 권한에 따라서 관리자 권한인지 일반 사용자 권한인지 등 이런 역할에 따라서 다른 컴포넌트를 보여줘야 될 때가 있다. 그런 경우에 조건부 라우트를 작업을 할 때 병렬 라우트를 사용할 수가 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Tab Groups&lt;/b&gt;&lt;/p&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;614&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/z55tL/btsIWdBhwMp/ZbPoFWCabzJ7iOVGsXjQLk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/z55tL/btsIWdBhwMp/ZbPoFWCabzJ7iOVGsXjQLk/img.png&quot; data-alt=&quot;https://nextjs.org/docs/app/building-your-application/routing/parallel-routes&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/z55tL/btsIWdBhwMp/ZbPoFWCabzJ7iOVGsXjQLk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fz55tL%2FbtsIWdBhwMp%2FZbPoFWCabzJ7iOVGsXjQLk%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;614&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;614&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://nextjs.org/docs/app/building-your-application/routing/parallel-routes&lt;/figcaption&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;슬롯 안에서 레이아웃을 추가를 해서 사용자가 슬롯을 독립적으로 네비게이션 할 수 있도록 만들어서 Tap을 만드는데 굉장히 유용하게 사용할 수도 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Modals&lt;/b&gt;&lt;/p&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;549&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhVyce/btsITDIGIu9/UToX8Mpdclbuc5YDqDfxKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhVyce/btsITDIGIu9/UToX8Mpdclbuc5YDqDfxKk/img.png&quot; data-alt=&quot;https://nextjs.org/docs/app/building-your-application/routing/parallel-routes&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhVyce/btsITDIGIu9/UToX8Mpdclbuc5YDqDfxKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbhVyce%2FbtsITDIGIu9%2FUToX8Mpdclbuc5YDqDfxKk%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;549&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;549&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://nextjs.org/docs/app/building-your-application/routing/parallel-routes&lt;/figcaption&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;병렬 라우트는 Intercepting Route와 함께 사용해서 modal을 만들어줄 수가 있다. 이렇게 했을 때 기존에 우리가 modal을 만들면서 가지고 있었던 여러가지 문제들을 해결할 수가 있게 된다. 예를 들면 modal 컨텐츠를 우리가 URL을 공유함으로써 그 modal 컨텐츠도 같이 공유를 할 수가 있게되고 페이지를 새로고침 할 때 modal이 닫히지 않고 컨텍스트를 유지하게 만들 수도 있다. 그리고 modal을 닫을 때 이전 상태 / 이전 페이지로 되돌아가는 네비게이션을 통해서 modal을 닫을 수도 있고 앞으로 탐색을 할 때 modal을 다시 열 수도 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;병렬 라우트는 Next.js 앱 라우터 방식에서 새롭게 추가된 여러 컴포넌트들을 동시에 또는 조건부로 설정해 줄 수 있는 방법이다. 이 방법을 통해서 어떤 권한을 부여하거나 modal을 작업하거나 조건부로 라우팅을 해야 되는 작업들을 할 때 잘 사용하면 Next.js가 기존에 만들어 놓은 기능들을 통해서 여러 성능적인 측면에서 이점을 얻을 수 있다.&lt;/blockquote&gt;</description>
      <category>Front-End/Next</category>
      <category>App Router</category>
      <category>dynamic routes</category>
      <category>linking and navigating</category>
      <category>next.js</category>
      <category>parallel routes</category>
      <category>route groups</category>
      <category>공부</category>
      <category>라우팅</category>
      <category>앱 라우팅</category>
      <category>코딩</category>
      <author>Voyage_dev</author>
      <guid isPermaLink="true">https://voyage-dev.tistory.com/207</guid>
      <comments>https://voyage-dev.tistory.com/207#entry207comment</comments>
      <pubDate>Tue, 6 Aug 2024 01:26:31 +0900</pubDate>
    </item>
    <item>
      <title>Next.js 프레임워크 라우팅 구조와 데이터 패칭</title>
      <link>https://voyage-dev.tistory.com/206</link>
      <description>&lt;blockquote data-ke-style=&quot;style3&quot;&gt;✔️ Next.js 프레임워크의 라우팅 구조와 데이터 패칭에 대해 살펴보자&lt;/blockquote&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;b&gt;페이지 라우트 구조&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페이지 라우터 구조는 pages라는 디렉토리 하위의 경로에 따라서 라우터 구조를 자동으로 가져가고 있다. Next.js를 템플릿을 사용해서 프로젝트를 시작을 하면 pages라는 디렉토리가 기본으로 만들어지게 된다. 해당 디렉토리 하위에 폴더도 만들 수 있고 파일도 만들 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;/pages/index.tsx : 웹 사이트의 루트(localhost:3000)&lt;/li&gt;
&lt;li&gt;/pages/hello.tsx : 경로는 /hello 로 접근할 수 있다(localhost:3000/hello)&lt;/li&gt;
&lt;li&gt;/pages/hello/[id].tsx : 대괄호 [] 는 여기에 어떠한 문자도 올 수 있음을 의미한다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;/pages/hello/123&lt;/li&gt;
&lt;li&gt;/pages/hello/world 등 가능&lt;/li&gt;
&lt;li&gt;서버사이드에서는 123, world가 id 변수에 담겨져서 들어온다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;/pages/hello/[&amp;hellip;props].tsx : /hello 하위의 주소가 모두 가능하다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;/pages/hello/world&lt;/li&gt;
&lt;li&gt;/pages/hello/world/123&lt;/li&gt;
&lt;li&gt;/pages/hello/world/123/456&lt;/li&gt;
&lt;li&gt;서버사이드에는 props 변수에 해당 경로가 담겨져서 들어온다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// /pages/hello/[&amp;hellip;props].tsx

import { NextPageContext } from &quot;next&quot;;
import { useRouter } from &quot;next/router&quot;;
import { useEffect } from &quot;react&quot;;

export default function HelloAll({props: serverProps}: {props: string[]}) {
    const {
        query: {props},
    } = useRouter();

    useEffect(() =&amp;gt; {
        console.log('props', props);
        console.log(JSON.stringify(props) === JSON.stringify(serverProps))
    }, [props, serverProps]);

    return (
        &amp;lt;&amp;gt;
            &amp;lt;p&amp;gt;Hello&amp;lt;/p&amp;gt;
            &amp;lt;ul&amp;gt;
                {
                    serverProps.map((item) =&amp;gt; (
                        &amp;lt;li key={item}&amp;gt;{item}&amp;lt;/li&amp;gt;
                    ))
                }
            &amp;lt;/ul&amp;gt;
        &amp;lt;/&amp;gt;
    )
}

export const getServerSideProps = (context: NextPageContext) =&amp;gt; {
    const {
        query: {props},
    } = context;

    return {
        props: {
            props
        }
    }
}
&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;p data-ke-size=&quot;size16&quot;&gt;Next.js는 서버 사이드 렌더링을 수행하면서 동시에 클라이언트 사이드 렌더링도 수행한다. 우리가 routing 방식을 사용할 때 크게 두 가지 방식을 생각할 수 있다.&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;&amp;lt;a&amp;gt;와 &amp;lt;Link&amp;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;&amp;lt;a&amp;gt; 태그는 어플리케이션에 필요한 모든 리소스(webpack, framework, main 등)를 다 가져온다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서버에서 렌더링도 수행하고, 클라이언트에서 hydrate도 진행한다. (콘솔이 server, client 모두 찍힌 것을 보면 알 수 있다)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&amp;lt;Link&amp;gt; 태그는 서버 사이드 렌더링은 하지 않고, 클라이언트 자바스크립트만 불러와서 클라이언트 라우팅, 렌더링 방식으로 진행한다 &amp;rarr; 더 사용을 권장한다&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// pages/index.tsx

import { NextPage } from &quot;next&quot;;
import Link from &quot;next/link&quot;;

const Home: NextPage = () =&amp;gt; {
  return (
    &amp;lt;ul&amp;gt;
      &amp;lt;li&amp;gt;
        &amp;lt;a href=&quot;/routing&quot;&amp;gt;a 태그로 이동&amp;lt;/a&amp;gt;
      &amp;lt;/li&amp;gt;
      &amp;lt;li&amp;gt;
        &amp;lt;Link prefetch={false} href=&quot;/routing&quot;&amp;gt;
          next/link로 이동
        &amp;lt;/Link&amp;gt;
      &amp;lt;/li&amp;gt;
    &amp;lt;/ul&amp;gt;
  );
};

export default Home;

&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// pages/routing/index.tsx

export default function Routing() {
  console.log(typeof window === &quot;undefined&quot; ? &quot;server&quot; : &quot;client&quot;);

  return &amp;lt;p&amp;gt;라우팅&amp;lt;/p&amp;gt;;
}

export const getServerSideProps = () =&amp;gt; {
  return {
    props: {},
  };
};

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Next.js는 서버 사이드 렌더링의 장점(빠른 최초 페이지 제공)과 SPA의 장점(자연스러운 라우팅) 같은 장점을 각각 가지고 만들어진 프레임워크이다. 이와 비슷한 이유로 window.location.push 보다 router.push의 사용을 더 권장한다.&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;/api&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pages/api 디렉토리에는 서버의 API를 정의하는 폴더가 있다. 우리가 pages에서 어떤 폴더를 만들면 그 폴더가 라우터 구조가 된다고 살펴보았는데 이 API라는 이름은 네이밍 스페이스 규칙을 가지고 있어서 이 /api 하위 파일은 HTML을 요청하는게 아니라 단순 서버 요청을 주고받는 역할을 한다. 즉, 다른 pages 하위 파일들은 HTML을 요청하지만 /api 하위 파일들은 단순한 서버 요청을 주고 받는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버 데이터를 가지고 BFF(Backend For Frontend)를 만들거나, CORS(Cross-Origin Resource Sharing) 문제를 해결하기 위해 사용할 수 있다.&lt;/p&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;789&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dtDcXJ/btsIUI9c66X/rWX5Z3l8M36HpZZsIns3Tk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dtDcXJ/btsIUI9c66X/rWX5Z3l8M36HpZZsIns3Tk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dtDcXJ/btsIUI9c66X/rWX5Z3l8M36HpZZsIns3Tk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdtDcXJ%2FbtsIUI9c66X%2FrWX5Z3l8M36HpZZsIns3Tk%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;789&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;789&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;이렇게 HTML을 받아오는게 아니라 JSON 데이터를 불러오는걸 확인할 수가 있다&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;p data-ke-size=&quot;size16&quot;&gt;Next.js에서는 SSR 지원을 위한 몇 가지 데이터 불러오기 전략이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 함수들은 pages 하위 디렉토리에서만 사용할 수 있다.&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;getStaticPaths&lt;/b&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; - Next.js가 해당 경로를 전략적으로 pre-rendering 하기 위해서 사용&lt;/li&gt;
&lt;li&gt;&lt;b&gt;언제?&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터가 headless CMS, DB, 파일 시스템, 퍼블릭 캐시 등에서 올 때&lt;/li&gt;
&lt;li&gt;페이지가 미리 렌더되어서 빠르게 실행이 되어야 할 때&lt;/li&gt;
&lt;/ul&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;getStaticProps와 같이 사용해야 한다&lt;/li&gt;
&lt;li&gt;getServerSideProps와 함께 사용할 수 없다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;export const getStaticPaths = (async () =&amp;gt; {
  return {
    paths: [
      {
        params: {
          name: 'next.js',
        },
      }, 
    ],
    fallback: true, 
  }
}) satisfies GetStaticPaths

// return에 paths와 fallback props가 들어가게 된다
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;getStaticProps&lt;/b&gt; : 앞에서 정의한 페이지(by getStaticPaths)를 기준으로 해당 페이지로 요청이 왔을 때 제공할 props를 반환하는 함수
&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; - 미리 호출된 페이지를 빌드 타임에 pre-render 하고 이 때 getStaticProps의 props 리턴값을 가지고 렌더링한다&lt;/li&gt;
&lt;li&gt;&lt;b&gt;언제?&lt;/b&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;데이터가 headless CMS에서 올 때&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;실행?&lt;/b&gt; - 서버에서 실행, 클라이언트에서는 절대 실행되지 않는다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;next build 시에 항상 실행&lt;/li&gt;
&lt;li&gt;옵션 fallback : true &amp;rarr; 백그라운드에서 실행된다&lt;/li&gt;
&lt;li&gt;옵션 fallback : blocking&amp;rarr; 초기 렌더 전에 호출된다&lt;/li&gt;
&lt;li&gt;revalidate 메서드를 통해 백그라운드에서 실행 가능하다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;어디서?&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;오직 page 디렉토리에서 export 가능하다&lt;/li&gt;
&lt;li&gt;_app, _document, _error 등 페이지에서는 export 불가능하다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;import type { InferGetStaticPropsType, GetStaticProps } from 'next'
 
type Repo = {
  name: string
  stargazers_count: number
}
 
export const getStaticProps = (async (context) =&amp;gt; {
  const res = await fetch('&amp;lt;https://api.github.com/repos/vercel/next.js&amp;gt;')
  const repo = await res.json()
  return { props: { repo } }
}) satisfies GetStaticProps&amp;lt;{
  repo: Repo
}&amp;gt;
 
export default function Page({
  repo,
}: InferGetStaticPropsType) {
  return repo.stargazers_count
}
&lt;/code&gt;&lt;/pre&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;getServerSideProps&lt;/b&gt; : 위의 두 함수는 정적 페이지 제공을 위해 사용했다면, getServerSideProps는 서버에서 실행되고, 페이지 진입 전 실행된다.
&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;유저의 개인화된 데이터에 의존하는 페이지이거나 요청 시 데이터를 받아서 만들어야 하는 페이지는 getServerSideProps로 렌더링을 해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;import type { InferGetServerSidePropsType, GetServerSideProps } from 'next'
 
type Repo = {
  name: string
  stargazers_count: number
}
 
export const getServerSideProps = (async () =&amp;gt; {

  const res = await fetch('https://api.github.com/repos/vercel/next.js')
  const repo: Repo = await res.json()

  return { props: { repo } }
}) satisfies GetServerSideProps&amp;lt;{ repo: Repo }&amp;gt;
 
export default function Page({
  repo,
}: InferGetServerSidePropsType&amp;lt;typeof getServerSideProps&amp;gt;) {
  return (
    &amp;lt;main&amp;gt;
      &amp;lt;p&amp;gt;{repo.stargazers_count}&amp;lt;/p&amp;gt;
    &amp;lt;/main&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;getStaticProps와 getServerSideProps는 동시에 같이 사용할 수가 없다. 예를 들어, 블로그 같은 글을 만들 때는 어떤 유저가 들어오더라도 같은 결과물을 보여줘야 하기 때문에 getStaticProps와 getStaticPaths로 미리 프리렌더링을 해놓은 페이지를 보여주는 것이 조금 더 빠를 수도 있다. 반대로 어떤 대시보드를 만들고 거기에서 유저마다 개인화된 데이터를 보여줘야 되는 서비스를 만들 때는 getServerSideProps를 사용하는 편이 훨씬 더 적합할 것으로 보여진다. 이렇게 목적에 맞게 이러한 함수들을 데이터를 패칭을 하는 함수들을 사용하면 서버 사이드 렌더링에서 가져갈 수 있는 장점들을 잘 가져갈 수 있을것이다.&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;Next는 12버전까지는 페이지 라우팅 방식을 모두 기본 설정으로 사용을 했는데 13버전부터 앱 라우터 방식이 새롭게 등장하게 되었다. 다음에는 앱 라우터 방식과 같이 사용할 수 있는 리액트 서버 컴포넌트에 대해서 알아보자!&lt;/p&gt;</description>
      <category>Front-End/Next</category>
      <category>getServerSideProps</category>
      <category>getStaticPaths</category>
      <category>getStaticProps</category>
      <category>next 라우팅</category>
      <category>next.js</category>
      <category>공부</category>
      <category>데이터 패칭</category>
      <category>복습</category>
      <category>코딩</category>
      <category>학습</category>
      <author>Voyage_dev</author>
      <guid isPermaLink="true">https://voyage-dev.tistory.com/206</guid>
      <comments>https://voyage-dev.tistory.com/206#entry206comment</comments>
      <pubDate>Sun, 4 Aug 2024 16:51:14 +0900</pubDate>
    </item>
    <item>
      <title>Next.js 프레임워크</title>
      <link>https://voyage-dev.tistory.com/205</link>
      <description>&lt;blockquote data-ke-style=&quot;style3&quot;&gt;✔️ Next.js 프레임워크가 무엇이고 동작 원리 및 페이지 역할에 대해 살펴보자&lt;/blockquote&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;b&gt;Next.js란?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://nextjs.org/&quot;&gt;https://nextjs.org/&lt;/a&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Vercel에서 만든 풀 스택 웹 어플리케이션 구축 프레임워크&lt;/li&gt;
&lt;li&gt;페이스북 팀에서 만든 react-page라는 리액트 기반 서버 사이드 렌더링 프로젝트 &amp;rarr; 여기서 영감을 받음&lt;/li&gt;
&lt;li&gt;라이브러리가 아니다&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;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;489&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/r4z0A/btsIS64oDlO/goLo7sqqHyxWUIkQjgFRAk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/r4z0A/btsIS64oDlO/goLo7sqqHyxWUIkQjgFRAk/img.png&quot; data-alt=&quot;https://nextjs.org/docs/app/building-your-application/routing/loading-ui-and-streaming&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/r4z0A/btsIS64oDlO/goLo7sqqHyxWUIkQjgFRAk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fr4z0A%2FbtsIS64oDlO%2FgoLo7sqqHyxWUIkQjgFRAk%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;489&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;489&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://nextjs.org/docs/app/building-your-application/routing/loading-ui-and-streaming&lt;/figcaption&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;해당 페이지에서 필요한 모든 데이터가 서버로 fetch된다 (A)&lt;/li&gt;
&lt;li&gt;서버는 이 HTML을 렌더링을 한다 (B)&lt;/li&gt;
&lt;li&gt;B에서 만든 HTML과 CSS, JavaSCript가 클라이언트로 보내지게 된다 (C)&lt;/li&gt;
&lt;li&gt;인터랙션이 없는 UI가 HTML과 CSS에 의해서 보여지게 된다&lt;/li&gt;
&lt;li&gt;마지막으로, React는 Hydration이라는 과정을 통해서 인터랙션을 브라우저 화면에서 적용을 시켜준다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방법은 굉장이 연속적이고 블록킹이 많이 존재한다. 모든 데이터가 패치가 되었을 때 서버가 하나의 페이지 즉 하나의 HTML을 하나씩 렌더링을 할 수 있다는 것을 의미한다. 클라이언트 차원에서도 하나의 페이지에 모든 컴포넌트가 다운로드 된 이후에 UI를 한 번에 하나씩 Hydration을 할 수가 있다. 하지만 이 방법은 성능적으로 굉장히 좋지가 않다. 페이지 한 번에 하나씩밖에 할 수가 없고 렌더링을 하는데 오래 걸리면 기다리는 시간도 굉장히 길다.&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;/b&gt;&lt;/p&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;628&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AUj11/btsIULq1DNp/LIL83GS2PZPxfbszxfrwW1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AUj11/btsIULq1DNp/LIL83GS2PZPxfbszxfrwW1/img.png&quot; data-alt=&quot;https://nextjs.org/docs/app/building-your-application/routing/loading-ui-and-streaming&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AUj11/btsIULq1DNp/LIL83GS2PZPxfbszxfrwW1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAUj11%2FbtsIULq1DNp%2FLIL83GS2PZPxfbszxfrwW1%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;628&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;628&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://nextjs.org/docs/app/building-your-application/routing/loading-ui-and-streaming&lt;/figcaption&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;보이는 것처럼 HTML을 쪼개서 청크로 만들고 연속적으로 서버에서 클라이언트로 하나씩 보내는 방법을 선택했다&lt;/li&gt;
&lt;/ul&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;584&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b7Yy5s/btsITTXAN9M/NpvPzTkOwnWGGJqAv4Txbk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b7Yy5s/btsITTXAN9M/NpvPzTkOwnWGGJqAv4Txbk/img.png&quot; data-alt=&quot;https://nextjs.org/docs/app/building-your-application/routing/loading-ui-and-streaming&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b7Yy5s/btsITTXAN9M/NpvPzTkOwnWGGJqAv4Txbk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb7Yy5s%2FbtsITTXAN9M%2FNpvPzTkOwnWGGJqAv4Txbk%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;584&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;584&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://nextjs.org/docs/app/building-your-application/routing/loading-ui-and-streaming&lt;/figcaption&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;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트를 쪼갤 때 예를 들어, 상품 정보와 같이 우선순위가 높은 컴포넌트 그리고 레이아웃과 같이 데이터에 의존하지 않는 컴포넌트들을 먼저 전송을 할 수가 있게 되었다. 그러면 리액트는 그 컴포넌트들을 먼저 Hydration을 할 수가 있다. 그 다음에 리뷰라든지 연관 상품 같은 우선순위가 좀 낮은 컴포넌트들을 처리를 할 수가 있게 된다. 이렇게 로딩 UI와 스트리밍을 통해서 리액트는 페이지를 렌더링하는 성능을 굉장히 혁신적으로 끌어 올렸다.&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;Next.js 시작하기&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;dsconfig&quot;&gt;&lt;code&gt;npx create-next-app@latest
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;TypeScript : Y&lt;/li&gt;
&lt;li&gt;ESLint : Y&lt;/li&gt;
&lt;li&gt;Tailwind CSS (옵션)&lt;/li&gt;
&lt;li&gt;/src directory : N&lt;/li&gt;
&lt;li&gt;App Router : N (다음에 다룰 예정)&lt;/li&gt;
&lt;li&gt;import alias : N&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;dockerfile&quot;&gt;&lt;code&gt;npm run dev
&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;&lt;b&gt;_app.tsx&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;li&gt;이 루트 컴포넌트에서 하위에 이 컴포넌트들이 하나씩 하나씩 다 꼬리를 자식 노드 형태로 퍼져 나가는 구조이다&lt;/li&gt;
&lt;li&gt;에러 바운더리를 설정을 해서 전역에서 발생하는 에러를 처리하게 된다. _app..tsx에서 감싸주게 되면 모든 프로젝트의 컴포넌트에 영향을 다 미치기 때문에 에러 바운더리를 설정할 수 있다&lt;/li&gt;
&lt;li&gt;전역 CSS를 여기서 설정해 줄 수가 있다. 모든 컴포넌트에 적용되는 스타일을 적용하고 싶을 때 반영해 줄 수 있다&lt;/li&gt;
&lt;li&gt;모든 페이지에 사용되는 데이터를 제공하는 데도 설정해 줄 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 뭔가 전역에서 작업을 해야 되는 일이 있을 때 _app..tsx 파일을 가장 먼저 찾으면 원하는 설정을 할 수 있다.&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;_document.tsx&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;_app..tsx가 전체를 초기화를 한다면 _document.tsx는 어플리케이션의 HTML을 초기화하는 역할을 하고 있다&lt;/li&gt;
&lt;li&gt;CSS-in-JS의 스타일을 서버에서 모아서 HTML로 제공하는 역할도 하고 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;_app.tsx vs _document.tsx 차이점&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;&amp;lt;html&amp;gt;이나 &amp;lt;body&amp;gt;에 DOM 속성을 추가하고 싶다면 _document.tsx 사용&lt;/li&gt;
&lt;li&gt;_app..tsx는 클라이언트 또는 서버에서 실행이 될 수 있지만, _document.tsx는 무조건 서버에서 실행
&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;이벤트 추가는 클라이언트의 hydrate에서 이루어지기 때문이다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;next/document의 &amp;lt;Head /&amp;gt;는 _document.tsx에서만 사용할 수 있다. 또 다른 &amp;lt;head /&amp;gt;는 next/head에서 제공하는데 이 &amp;lt;head /&amp;gt;는 페이지에서 사용할 수 있다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;next/document &amp;lt;Head /&amp;gt; 에서는 &amp;lt;title /&amp;gt; 속성을 사용할 수 없다. next/head &amp;lt;head /&amp;gt;에서는 title 등 SEO에 필요한 정보를 사용할 수 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;getServerSideProps, getStaticProps등을 여기에서는 사용할 수 없다&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;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;b&gt;pages/_error.tsx&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import { NextPageContext } from 'next';

function Error({ statusCode = 500 }) {
	return &amp;lt;p&amp;gt;{statusCode}에러가 발생&amp;lt;/p&amp;gt;
}

Error.getInitialProps = ({res, err}: NextPageContext) =&amp;gt; {
	const statusCode = res ? res.statusCode : err ? err.statusCode : ''
	
	return { statusCode }
}

export default Error
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;pages/404.tsx : 404 클라이언트 에러 발생시 나타나는 페이지&lt;/li&gt;
&lt;li&gt;pages/500.tsx : 500 서버 에러 발생시 나타나는 페이지&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음에는 Next.js 프레임워크에서 라우트 구조를 어떻게 가져가고 사버 라우팅과 클라이언트 라우팅의 차이 그리고 API랑 데이터 패칭 관련된 내용을 정리해 보자&lt;/p&gt;</description>
      <category>Front-End/Next</category>
      <category>next.js</category>
      <category>next.js 프레임워크</category>
      <category>공부</category>
      <category>복습</category>
      <category>서버사이드 렌더링</category>
      <category>코딩</category>
      <author>Voyage_dev</author>
      <guid isPermaLink="true">https://voyage-dev.tistory.com/205</guid>
      <comments>https://voyage-dev.tistory.com/205#entry205comment</comments>
      <pubDate>Fri, 2 Aug 2024 23:29:46 +0900</pubDate>
    </item>
    <item>
      <title>Suspense</title>
      <link>https://voyage-dev.tistory.com/204</link>
      <description>&lt;blockquote data-ke-style=&quot;style3&quot;&gt;✔️ React 18에서 추가된 Suspense에 대해 살펴보자&lt;/blockquote&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;b&gt;Suspense란?&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;fallback 컴포넌트를 로딩이 끝날 때 까지 보여주는 기능이다.&lt;/li&gt;
&lt;li&gt;사실 Suspense는 React 16.6 버전에서 실험적으로 도입된 기능이었다.&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Next.js에서 사용할 수 없었다. &amp;rarr; (React 18) Next.js에서도 사용할 수 있게 되었다.&lt;/li&gt;
&lt;li&gt;컴포넌트가 마운트가 되기 전에 effect가 실행되는 문제가 발생했다. &amp;rarr; (React 18) 컴포넌트가 화면에 실제로 노출될 때 effect가 실행된다.&lt;/li&gt;
&lt;li&gt;이전에는 컴포넌트가 스스로 Suspense에 의해 보여지고 있는지 없는지 알 방법이 없었다. &amp;rarr; (React 18) Suspense로 인해 컴포넌트가 보이거나 사라질 때도 effect가 정상적으로 실행이 되는 방식으로 바뀌었다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Suspense에 의해 노출된다면 useLayoutEffectdml effect(componentDidMount)가&lt;/li&gt;
&lt;li&gt;가려진다면 useLayOutEffect의 cleanUp(componentWillMount)이 정상적으로 실행&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Suspense 내의 쓰로틀링이 추가
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;중첩된 Suspense의 fallback이 있다면, 자동으로 쓰로틀링 되어 최대한 자연스럽게 보여준다&lt;/li&gt;
&lt;/ul&gt;
&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;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://react.dev/reference/react/Suspense&quot;&gt;https://react.dev/reference/react/Suspense&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;Suspense는 기본적으로 다음과 같이 사용된다&lt;/p&gt;
&lt;pre class=&quot;django&quot;&gt;&lt;code&gt;&amp;lt;Suspense fallback={&amp;lt;Loading /&amp;gt;}&amp;gt;
  &amp;lt;SomeComponent /&amp;gt;
&amp;lt;/Suspense&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;어떤 컴포넌트가 있고 그 컴포넌트를 바깥에서 Suspense로 감싸준 후에 그 Suspense의 fallback props를 통해서 어떤 컴포넌트를 이 children이 자식 컴포넌트가 Loading이 끝났을 때까지 보여줄 것인지를 넣어주면 된다.&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;React는 최초 마운트가 되기 전에 중단된 렌더링 상태에 대해서는 어떤한 상태도 보존이 되지 않는다. 만약에 컴포넌트가 로드가 되면 React는 중단된 트리를 처음부터 다시 리렌더링 시도를 하게 된다&lt;/li&gt;
&lt;li&gt;Suspense가 트리에서 컨텐츠를 표시하다가 중단된 경우 그 업데이트가 startTransition 또는 useDeferredValue에 의해서 발생된게 아니라면 fallback 컴포넌트가 다시 보여지게 된다.&lt;/li&gt;
&lt;li&gt;React가 이미 보이는 컨텐츠를 다시 중단해서 숨겨야 할 때 React는 컨텐츠 트리에서 레이아웃 이펙트를 정리를 하게 된다. 만약에 다시 컨텐츠를 표시할 준비가 되었을 때 React는 이 레이아웃 이펙트를 다시 발생을 시키는데 이러한 작업은 컨텐츠가 숨겨진 동안 DOM 레이아웃을 측정하는 이펙트가 이를 시도하지 않도록 보장하는 것을 의미한다.&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;pre class=&quot;xquery&quot;&gt;&lt;code&gt;import { Suspense } from 'react';
import Albums from './Albums.js';

export default function ArtistPage({ artist }) {
  return (
    &amp;lt;&amp;gt;
      &amp;lt;h1&amp;gt;{artist.name}&amp;lt;/h1&amp;gt;
      &amp;lt;Suspense fallback={&amp;lt;Loading /&amp;gt;}&amp;gt;
        &amp;lt;Albums artistId={artist.id} /&amp;gt;
      &amp;lt;/Suspense&amp;gt;
    &amp;lt;/&amp;gt;
  );
}

function Loading() {
  return &amp;lt;h2&amp;gt;  Loading...&amp;lt;/h2&amp;gt;;
}

&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이러한 어떤 Albums라는 자식 컴포넌트가 데이터를 불러올 때까지 Loading을 보여주고 싶다고 했을 때 Suspense를 감싸주고 Loading 컴포넌트로 fallbac을 넣어주면 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Suspense안에 하나의 컨텐츠만 들어갈 수도 있지만 여러 개의 컨텐츠가 들어갈 수도 있다&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;django&quot;&gt;&lt;code&gt;&amp;lt;Suspense fallback={&amp;lt;Loading /&amp;gt;}&amp;gt;
  &amp;lt;Biography /&amp;gt;
  &amp;lt;Panel&amp;gt;
    &amp;lt;Albums /&amp;gt;
  &amp;lt;/Panel&amp;gt;
&amp;lt;/Suspense&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이렇게 여러 개의 컨텐츠가 들어간 경우에는 동일하게 Loading이 보여지고 여러 개의 컨텐츠가 모두 다 Loading이 끝난 이후에 한꺼번에 다 보여주게 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 어떤 컴포넌트는 금방 Loading이 되는데 어떤 컴포넌트는 굉장히 오래 걸리는 경우는 어떻게 해야 될까? 그러면 마지막 컴포넌트가 끝날 때 까지 기다려야 하나?&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;Suspense에서는 중첩된 방식으로 구현할 수도 있다!&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;django&quot;&gt;&lt;code&gt;&amp;lt;Suspense fallback={&amp;lt;BigSpinner /&amp;gt;}&amp;gt;
  &amp;lt;Biography /&amp;gt;
  &amp;lt;Suspense fallback={&amp;lt;AlbumsGlimmer /&amp;gt;}&amp;gt;
    &amp;lt;Panel&amp;gt;
      &amp;lt;Albums /&amp;gt;
    &amp;lt;/Panel&amp;gt;
  &amp;lt;/Suspense&amp;gt;
&amp;lt;/Suspense&amp;gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이렇게 여러 개의 컨텐츠가 있을 때 컨텐츠별로 중첩해서 Suspense를 만들어 줄 수가 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 순서는 Biography가 로드가 되기 전에는 BigSpinner가 돌아가게 된다 &amp;rarr; Biography로드가 다 되었으면 BigSpinner가 사라지고 Biography가 보여지게 된다 &amp;rarr; 밑에 애들은 Suspense 보여지지 않는 상태가 있다 &amp;rarr; Panel과 Albums 이 부분이 로드가 안 되었기 때문에 Biography가 보여지고 나서도 AlbumsGlimmer는 계속 보여지게 된다 &amp;rarr; Panel과 Albums이 다 로드가 되었을 때 AlbumsGlimmer가 사라지고 Panel과 Albums이 보여진다.&lt;/p&gt;
&lt;pre class=&quot;xquery&quot;&gt;&lt;code&gt;import { Suspense } from 'react';
import Albums from './Albums.js';
import Biography from './Biography.js';
import Panel from './Panel.js';

export default function ArtistPage({ artist }) {
  return (
    &amp;lt;&amp;gt;
      &amp;lt;h1&amp;gt;{artist.name}&amp;lt;/h1&amp;gt;
      &amp;lt;Suspense fallback={&amp;lt;BigSpinner /&amp;gt;}&amp;gt;
        &amp;lt;Biography artistId={artist.id} /&amp;gt;
        &amp;lt;Suspense fallback={&amp;lt;AlbumsGlimmer /&amp;gt;}&amp;gt;
          &amp;lt;Panel&amp;gt;
            &amp;lt;Albums artistId={artist.id} /&amp;gt;
          &amp;lt;/Panel&amp;gt;
        &amp;lt;/Suspense&amp;gt;
      &amp;lt;/Suspense&amp;gt;
    &amp;lt;/&amp;gt;
  );
}

function BigSpinner() {
  return &amp;lt;h2&amp;gt;  Loading...&amp;lt;/h2&amp;gt;;
}

function AlbumsGlimmer() {
  return (
    &amp;lt;div className=&quot;glimmer-panel&quot;&amp;gt;
      &amp;lt;div className=&quot;glimmer-line&quot; /&amp;gt;
      &amp;lt;div className=&quot;glimmer-line&quot; /&amp;gt;
      &amp;lt;div className=&quot;glimmer-line&quot; /&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;&lt;b&gt;만약에 컨텐츠가 조금 지연될 경우 어떻게 될까? (useDeferredValue)&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;먼저 우리가 어떤 쿼리를 한다고 했을 때 쿼리를 하면 쿼리 데이터를 불러오는데 시간 때문에 Suspense가 보여지게 된다&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import { Suspense, useState } from 'react';
import SearchResults from './SearchResults.js';

export default function App() {
  const [query, setQuery] = useState('');
  return (
    &amp;lt;&amp;gt;
      &amp;lt;label&amp;gt;
        Search albums:
        &amp;lt;input value={query} onChange={e =&amp;gt; setQuery(e.target.value)} /&amp;gt;
      &amp;lt;/label&amp;gt;
      &amp;lt;Suspense fallback={&amp;lt;h2&amp;gt;Loading...&amp;lt;/h2&amp;gt;}&amp;gt;
        &amp;lt;SearchResults query={query} /&amp;gt;
      &amp;lt;/Suspense&amp;gt;
    &amp;lt;/&amp;gt;
  );
}

&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Loading 보여지고 데이터가 보여지고를 반복하는 코드다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 만약에 우리가 deferredQuery를 사용한다면 state가 바뀔 때마다 매번 바로 Loading이 이루어지는게 아니라 UI 렌더링을 방해하지 않는 선에서 지연된 쿼리를 받아볼 수 있다는 특징을 가지고 있다.&lt;/p&gt;
&lt;pre class=&quot;django&quot;&gt;&lt;code&gt;export default function App() {
  const [query, setQuery] = useState('');
  const deferredQuery = useDeferredValue(query);
  return (
    &amp;lt;&amp;gt;
      &amp;lt;label&amp;gt;
        Search albums:
        &amp;lt;input value={query} onChange={e =&amp;gt; setQuery(e.target.value)} /&amp;gt;
      &amp;lt;/label&amp;gt;
      &amp;lt;Suspense fallback={&amp;lt;h2&amp;gt;Loading...&amp;lt;/h2&amp;gt;}&amp;gt;
        &amp;lt;SearchResults query={deferredQuery} /&amp;gt;
      &amp;lt;/Suspense&amp;gt;
    &amp;lt;/&amp;gt;
  );
}

&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;쿼리를 이렇게 deferredQuery로 한 번 감싸주고&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;div style={{
  opacity: query !== deferredQuery ? 0.5 : 1 
}}&amp;gt;
  &amp;lt;SearchResults query={deferredQuery} /&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;deferredQuery가 실제 쿼리랑 변화를 눈으로 볼 수 있게 한 번 처리를 해준 다음에 deferredQuery를 넣어보자&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;django&quot;&gt;&lt;code&gt;import { Suspense, useState, useDeferredValue } from 'react';
import SearchResults from './SearchResults.js';

export default function App() {
  const [query, setQuery] = useState('');
  const deferredQuery = useDeferredValue(query);
  const isStale = query !== deferredQuery;
  return (
    &amp;lt;&amp;gt;
      &amp;lt;label&amp;gt;
        Search albums:
        &amp;lt;input value={query} onChange={e =&amp;gt; setQuery(e.target.value)} /&amp;gt;
      &amp;lt;/label&amp;gt;
      &amp;lt;Suspense fallback={&amp;lt;h2&amp;gt;Loading...&amp;lt;/h2&amp;gt;}&amp;gt;
        &amp;lt;div style={{ opacity: isStale ? 0.5 : 1 }}&amp;gt;
          &amp;lt;SearchResults query={deferredQuery} /&amp;gt;
        &amp;lt;/div&amp;gt;
      &amp;lt;/Suspense&amp;gt;
    &amp;lt;/&amp;gt;
  );
}

&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Loading이라는 fallback을 넣어도 보지 못 한다. Loading을 Suspense에서 인지하고 있는게 아니라 deferred 처리로 인해서 state가 지연된 처리가 이루어졌기 때문이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨텐츠가 부분적으로 리렌더링 될 때 기존 방식은 이미 보여지고 있었던 컨텐츠가 사라지게 되고 숨겨지게 되는 요소이기 때문에 그렇게 좋은 권장하는 패턴은 아니다. 기존에 만들어 놨던 것들은 계속해서 유지를 한 상태로 이제 새로운 컨텐츠들을 Suspense로 보여주는 것이 굉장히 중요하다 &amp;rarr; 여기서 사용하는 개념이 startTransition이다.&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;startTransition을 사용을 하게되면 Route처리 navigate를 하는 처리에서 URL 상태 페이지의 상태를 바꿔줄 때 그냥 바꿔주는게 아니라 기존에는 그냥 바로 바꿔줬다면&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import { Suspense, useState } from 'react';
import IndexPage from './IndexPage.js';
import ArtistPage from './ArtistPage.js';
import Layout from './Layout.js';

export default function App() {
  return (
    &amp;lt;Suspense fallback={&amp;lt;BigSpinner /&amp;gt;}&amp;gt;
      &amp;lt;Router /&amp;gt;
    &amp;lt;/Suspense&amp;gt;
  );
}

function Router() {
  const [page, setPage] = useState('/');

  function navigate(url) {
    setPage(url);
  }

  let content;
  if (page === '/') {
    content = (
      &amp;lt;IndexPage navigate={navigate} /&amp;gt;
    );
  } else if (page === '/the-beatles') {
    content = (
      &amp;lt;ArtistPage
        artist={{
          id: 'the-beatles',
          name: 'The Beatles',
        }}
      /&amp;gt;
    );
  }
  return (
    &amp;lt;Layout&amp;gt;
      {content}
    &amp;lt;/Layout&amp;gt;
  );
}

function BigSpinner() {
  return &amp;lt;h2&amp;gt;  Loading...&amp;lt;/h2&amp;gt;;
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; startTransition을 감싸줘서 바꿔주면&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import { Suspense, startTransition, useState } from 'react';
import IndexPage from './IndexPage.js';
import ArtistPage from './ArtistPage.js';
import Layout from './Layout.js';

export default function App() {
  return (
    &amp;lt;Suspense fallback={&amp;lt;BigSpinner /&amp;gt;}&amp;gt;
      &amp;lt;Router /&amp;gt;
    &amp;lt;/Suspense&amp;gt;
  );
}

function Router() {
  const [page, setPage] = useState('/');

  function navigate(url) {
    startTransition(() =&amp;gt; {
      setPage(url);
    });
  }

  let content;
  if (page === '/') {
    content = (
      &amp;lt;IndexPage navigate={navigate} /&amp;gt;
    );
  } else if (page === '/the-beatles') {
    content = (
      &amp;lt;ArtistPage
        artist={{
          id: 'the-beatles',
          name: 'The Beatles',
        }}
      /&amp;gt;
    );
  }
  return (
    &amp;lt;Layout&amp;gt;
      {content}
    &amp;lt;/Layout&amp;gt;
  );
}

function BigSpinner() {
  return &amp;lt;h2&amp;gt;  Loading...&amp;lt;/h2&amp;gt;;
}

&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;브라우저는 그대로 있고 이 안에 컨텐츠만 새롭게 fallback도 보여지고 그리고 fallback 안에 중첩이 된 컨텐츠들이 보여지게 된다.&lt;/li&gt;
&lt;li&gt;만약에 state를 그냥 바꾸면 가장 가까운 부모의 Suspense의 fallback을 나타내 주게 된다.&lt;/li&gt;
&lt;li&gt;startTransition 쓰게 되면 React는 이 상태 변화가 급한게 아니고 천천히 해도 된다고 인식을 하고 이미 보여줬던 것들을 그대로 놔두고 하위 컴포넌트가 보여질 때까지 기존 것을 유지하는 경향을 가지고 있다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Front-End/React</category>
      <category>coding</category>
      <category>react</category>
      <category>react suspense</category>
      <category>suspense</category>
      <category>공부</category>
      <category>리액트</category>
      <category>복습</category>
      <category>서스펜스</category>
      <category>코딩</category>
      <author>Voyage_dev</author>
      <guid isPermaLink="true">https://voyage-dev.tistory.com/204</guid>
      <comments>https://voyage-dev.tistory.com/204#entry204comment</comments>
      <pubDate>Fri, 2 Aug 2024 21:59:10 +0900</pubDate>
    </item>
    <item>
      <title>자동 배치(Automatic Batching)</title>
      <link>https://voyage-dev.tistory.com/203</link>
      <description>&lt;blockquote data-ke-style=&quot;style3&quot;&gt;✔️ React 18에서 추가된 자동 배치(Automatic Batching)에 대해 살펴보자&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;리액트에서 항상 관심을 가지는 있는 주제는 바로 상태를 어떻게 업데이트 할 것인가 이다.&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;useTransition&lt;/b&gt;이나 &lt;b&gt;useDeferredValue&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;리액트 컴포넌트에서는 하나의 상태만 가지고 있기 보다는 두 개 이상의 상태를 가지고 있는 경우가 훨씬 더 많다. 이러한 경우 상태가 하나씩 업데이트가 될 때마다 컴포넌트는 리렌더링을 수행하게 된다. 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;이러한 문제를 해결하기 위해서 나온 기능이 바로 자동 배치 기능이다. 리액트 18 버전에서는 하나의 컴포넌트 안에서 여러 개의 상태를 하나의 그룹으로 묶어서 업데이트를 해주는 자동 배치 기능이 추가 되었다.&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;b&gt;자동 배치란?&lt;/b&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;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://react.dev/blog/2022/03/08/react-18-upgrade-guide#automatic-batching&quot;&gt;https://react.dev/blog/2022/03/08/react-18-upgrade-guide#automatic-batching&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;figure id=&quot;og_1722405560325&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;How to Upgrade to React 18 &amp;ndash; React&quot; data-og-description=&quot;The library for web and native user interfaces&quot; data-og-host=&quot;react.dev&quot; data-og-source-url=&quot;https://react.dev/blog/2022/03/08/react-18-upgrade-guide#automatic-batching&quot; data-og-url=&quot;https://react.dev/blog/2022/03/08/react-18-upgrade-guide&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bi1HFj/hyWGRFuGFO/1PK0TdS4DqR2h1tZSdkD2k/img.png?width=1080&amp;amp;height=567&amp;amp;face=0_0_1080_567,https://scrap.kakaocdn.net/dn/ntEiI/hyWG03vEMM/kQVYflBKsslrEpmCWgQHg1/img.png?width=1080&amp;amp;height=567&amp;amp;face=0_0_1080_567&quot;&gt;&lt;a href=&quot;https://react.dev/blog/2022/03/08/react-18-upgrade-guide#automatic-batching&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://react.dev/blog/2022/03/08/react-18-upgrade-guide#automatic-batching&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bi1HFj/hyWGRFuGFO/1PK0TdS4DqR2h1tZSdkD2k/img.png?width=1080&amp;amp;height=567&amp;amp;face=0_0_1080_567,https://scrap.kakaocdn.net/dn/ntEiI/hyWG03vEMM/kQVYflBKsslrEpmCWgQHg1/img.png?width=1080&amp;amp;height=567&amp;amp;face=0_0_1080_567');&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;How to Upgrade to React 18 &amp;ndash; React&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;The library for web and native user interfaces&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;react.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&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;리액트 18 이전 : 이벤트 핸들러&lt;/li&gt;
&lt;li&gt;리액트 18 : 프로미스, setTimeout, 이벤트 핸들러, 그 외 이벤트 등&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;function handleClick() {
  setCount(c =&amp;gt; c + 1);
  setFlag(f =&amp;gt; !f);
  // React will only re-render once at the end (that's batching!)
}

setTimeout(() =&amp;gt; {
  setCount(c =&amp;gt; c + 1);
  setFlag(f =&amp;gt; !f);
  // React will render twice, once for each state update (no batching)
}, 1000);
&lt;/code&gt;&lt;/pre&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 class=&quot;reasonml&quot;&gt;&lt;code&gt;// After React 18 updates inside of timeouts, promises,
// native event handlers or any other event are batched.

function handleClick() {
  setCount(c =&amp;gt; c + 1);
  setFlag(f =&amp;gt; !f);
  // React will only re-render once at the end (that's batching!)
}

setTimeout(() =&amp;gt; {
  setCount(c =&amp;gt; c + 1);
  setFlag(f =&amp;gt; !f);
  // React will only re-render once at the end (that's batching!)
}, 1000);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Autimatic Batrching으로 인해서 두 개의 상태를 업데이트 하더라도 이 setTimeout안에 두 개의 상태가 업데이트가 될 때 리렌더링은 오직 한 번만 일어난다.&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;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import { useEffect, useState } from &quot;react&quot;;

const sleep = (ms: number) =&amp;gt; {
  return new Promise((resolve) =&amp;gt; setTimeout(resolve, ms));
}

export default function App() {
  const [count, setCount] = useState(0);
  const [flag, setFlag] = useState(false);

  useEffect(() =&amp;gt; console.log('rendered'));

  function handleClick() {
    sleep(3000).then(() =&amp;gt; {
      setCount(c =&amp;gt; c+1);
      setFlag(f =&amp;gt; !f);
    })
  }

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;button onClick={handleClick}&amp;gt;다음&amp;lt;/button&amp;gt;
      &amp;lt;h1 style={{color: flag ? 'blue' : 'black'}}&amp;gt;{count}&amp;lt;/h1&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이전에는 상태 두 개가 업데이트 될 때마다 렌더링이 진행이 되기 때문에 useEffect(() =&amp;gt; console.log('rendered')); 인해서 rendered가 두 번씩 찍히는데 18버전에서는 한 번만 찍힌다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약에 이 상태가 바뀔 때마다 리렌더링을 해주고 싶으면 flushSync 메서드를 쓰면 된다. 각각의 상태 바꿔주는 함수들마다 작성을 해 주면 상태가 바뀔 때 마다 리렌더링을 시켜줄 수 있다.&lt;/p&gt;
&lt;pre class=&quot;arcade&quot;&gt;&lt;code&gt;function handleClick() {
  sleep(3000).then(() =&amp;gt; {
    flushSync(() =&amp;gt; {
	    setCount(c =&amp;gt; c+1);
	  });
    flushSync(() =&amp;gt; {
	    setFlag(f =&amp;gt; !f);
	  });
  })
}
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Front-End/React</category>
      <category>Automatic Batching</category>
      <category>react</category>
      <category>공부</category>
      <category>리렌더링</category>
      <category>리액트</category>
      <category>복습</category>
      <category>자동 배치</category>
      <category>코딩</category>
      <author>Voyage_dev</author>
      <guid isPermaLink="true">https://voyage-dev.tistory.com/203</guid>
      <comments>https://voyage-dev.tistory.com/203#entry203comment</comments>
      <pubDate>Wed, 31 Jul 2024 14:59:47 +0900</pubDate>
    </item>
    <item>
      <title>React v18</title>
      <link>https://voyage-dev.tistory.com/202</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;React 18 버전에 대한 소개&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;✔️ React 18 버전에 대해 알아보자&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 24년 4월 25일 React 19 베타 버전이 출시되었지만 아직 정식으로는 출시되지 않았기 때문에 지금은 18버전에 대해 알아보고 추후에 19버전에 대해 알아보자!&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;React 18 공식 문서&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://react.dev/blog/2022/03/29/react-v18&quot;&gt;React v18.0 &amp;ndash; React&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1722401930361&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;React v18.0 &amp;ndash; React&quot; data-og-description=&quot;The library for web and native user interfaces&quot; data-og-host=&quot;react.dev&quot; data-og-source-url=&quot;https://react.dev/blog/2022/03/29/react-v18&quot; data-og-url=&quot;https://react.dev/blog/2022/03/29/react-v18&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dZa2ih/hyWGZpZ3CY/mUYsV81OkDkqrY0znVqiy1/img.png?width=1080&amp;amp;height=567&amp;amp;face=0_0_1080_567,https://scrap.kakaocdn.net/dn/bupvLE/hyWGW07xdX/7RPFCtQlJdsooFJfC6bk71/img.png?width=1080&amp;amp;height=567&amp;amp;face=0_0_1080_567&quot;&gt;&lt;a href=&quot;https://react.dev/blog/2022/03/29/react-v18&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://react.dev/blog/2022/03/29/react-v18&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dZa2ih/hyWGZpZ3CY/mUYsV81OkDkqrY0znVqiy1/img.png?width=1080&amp;amp;height=567&amp;amp;face=0_0_1080_567,https://scrap.kakaocdn.net/dn/bupvLE/hyWGW07xdX/7RPFCtQlJdsooFJfC6bk71/img.png?width=1080&amp;amp;height=567&amp;amp;face=0_0_1080_567');&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;React v18.0 &amp;ndash; React&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;The library for web and native user interfaces&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;react.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&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;가장 중요한 내용은 동시성을 지원한다 즉, UI에 여러가지 버전이 한꺼번에 존재를 하는데 이러한 버전들을 동시에 보여주는 것을 동시에 보여주는 것을 가능하게 하는 것이 동시성이다&lt;/li&gt;
&lt;li&gt;이러한 동시성은 외부에서는 잘 보여지지 않지만 리액트 팀에서 내부적으로 구현을 할 때 이 개념을 많이 사용을 했고 예를 들면 우선순위 큐라든지 아니면 멀티플 버퍼링 같은 개념을 구현을 하는데 사용을 한 것이다&lt;/li&gt;
&lt;li&gt;동시성에서 가장 중요한 특징은 이 렌더링이 간섭 가능하다는 점이다. 이전에는 업데이트를 할 때 한 번에 간섭할 수 없고 동기적인 트랜잭션을 통해 이루어진 것을 알 수가 있는데 이러한 방식의 문제점은 한 번 업데이트 렌더링이 일어났을 때, 어떠한 것들도 간섭을 할 수가 없어서 유저는 그 결과를 볼 때까지 계속해서 기다려야만 했다.&lt;/li&gt;
&lt;li&gt;하지만 동시성이 React 18버전에서 나온 이후에 이러한 우리가 단일에 간섭할 수 없고 동기적인 방식의 트랜잭션을 사용하는 원리가 확 바뀐 것을 알 수가 있다. React는 업데이트를 시작을 하고 중간에 멈추기도 하고 그리고 나서 다시 시작하기도 하는고 때로는 진행 중인 렌더링을 통째로 버리기도 한다. React는 이렇게 렌더링이 간섭되어도 UI에서 간섭되는 것들이 보여지지 않고 지속적으로 어떤 화면이 보여질 수 있도록 원칙을 보장한다. 이게 가능한 이유는 DOM을 조작하는 연산을 가장 마지막에 하고 모든 트리가 연산이 맞춰진 후에 조작을 하는 방식으로 진행이 되기 때문이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;18버전에서 추가된 훅 (가장 중요한 3가지)&lt;/b&gt;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;☑️&amp;nbsp;useId&lt;/b&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;/li&gt;
&lt;li&gt;만약에 SSR을 통해 컴포넌트에 유니크한 값을 부여해야 한다고 했을 때 어떻게 할 것인가?&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;export default function UniqueIdComponent() {
	return &amp;lt;div&amp;gt;{Math.random()}&amp;lt;/div&amp;gt; 
	// 서버 사이드에서 렌더링이 될 때 난수가 한 번 생성이 되는데, 클라이언트에서 하이드레이션이 될 때 
	// 값이 바뀌기 때문에 두 번의 연산에서 다른 값이 나오게 된다 =&amp;gt; 에러
}
&lt;/code&gt;&lt;/pre&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;이 때 useId 훅을 사용하면 클라이언트와 서버의 불일치를 피해서 컴포넌트 내부의 고유한 값을 생성할 수 있다&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://react.dev/reference/react/useId&quot;&gt;https://react.dev/reference/react/useId&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;figure id=&quot;og_1722401963681&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;useId &amp;ndash; React&quot; data-og-description=&quot;The library for web and native user interfaces&quot; data-og-host=&quot;react.dev&quot; data-og-source-url=&quot;https://react.dev/reference/react/useId&quot; data-og-url=&quot;https://react.dev/reference/react/useId&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/DTeg0/hyWGQzP6nO/egvkKXI5KnQMz0ZHXUXt01/img.png?width=1080&amp;amp;height=567&amp;amp;face=0_0_1080_567,https://scrap.kakaocdn.net/dn/bs4IFl/hyWGVulqrh/BDNNxxOSovWw1PvsGCCgk1/img.png?width=1080&amp;amp;height=567&amp;amp;face=0_0_1080_567&quot;&gt;&lt;a href=&quot;https://react.dev/reference/react/useId&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://react.dev/reference/react/useId&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/DTeg0/hyWGQzP6nO/egvkKXI5KnQMz0ZHXUXt01/img.png?width=1080&amp;amp;height=567&amp;amp;face=0_0_1080_567,https://scrap.kakaocdn.net/dn/bs4IFl/hyWGVulqrh/BDNNxxOSovWw1PvsGCCgk1/img.png?width=1080&amp;amp;height=567&amp;amp;face=0_0_1080_567');&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;useId &amp;ndash; React&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;The library for web and native user interfaces&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;react.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;import { useId } from 'react';

function PasswordField() {
  const passwordHintId = useId();
  return (
    &amp;lt;&amp;gt;
      &amp;lt;label&amp;gt;
        Password:
        &amp;lt;input
          type=&quot;password&quot;
          aria-describedby={passwordHintId}
        /&amp;gt;
      &amp;lt;/label&amp;gt;
      &amp;lt;p id={passwordHintId}&amp;gt;
        The password should contain at least 18 characters
      &amp;lt;/p&amp;gt;
    &amp;lt;/&amp;gt;
  );
}

export default function App() {
  return (
    &amp;lt;&amp;gt;
      &amp;lt;h2&amp;gt;Choose password&amp;lt;/h2&amp;gt;
      &amp;lt;PasswordField /&amp;gt;
      &amp;lt;h2&amp;gt;Confirm password&amp;lt;/h2&amp;gt;
      &amp;lt;PasswordField /&amp;gt;
    &amp;lt;/&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;1250&quot; data-origin-height=&quot;672&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwuC6U/btsIQdQJGmu/Aav6BPy3JYYtv9MElPsKgK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwuC6U/btsIQdQJGmu/Aav6BPy3JYYtv9MElPsKgK/img.png&quot; data-alt=&quot;https://react.dev/reference/react/useId#usage&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwuC6U/btsIQdQJGmu/Aav6BPy3JYYtv9MElPsKgK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwuC6U%2FbtsIQdQJGmu%2FAav6BPy3JYYtv9MElPsKgK%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;1250&quot; height=&quot;672&quot; data-origin-width=&quot;1250&quot; data-origin-height=&quot;672&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://react.dev/reference/react/useId#usage&lt;/figcaption&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;하지만, 위 두 개의 컴포넌트는 각각 다른 컴포넌트이기 때문에 구분을 해줘야 한다. 그래서 이 안에서 이 구분해 줄 수 있는 값을 passwordHintId라는 이름으로 useId로 부터 고유한 아이디 값을 받을 수가 있다.&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;그리고 이 아이디 값을 이렇게 HTML에 적용을 해보면&lt;/p&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;388&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bD7gO1/btsISpa67pJ/Zzz2RkzjitN7JPk83vkSjk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bD7gO1/btsISpa67pJ/Zzz2RkzjitN7JPk83vkSjk/img.png&quot; data-alt=&quot;https://react.dev/reference/react/useId#usage&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bD7gO1/btsISpa67pJ/Zzz2RkzjitN7JPk83vkSjk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbD7gO1%2FbtsISpa67pJ%2FZzz2RkzjitN7JPk83vkSjk%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;388&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;388&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://react.dev/reference/react/useId#usage&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 id 값이 R1, R3 서로 다른 id 값을 가진 것을 확인해 줄 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;☑️&amp;nbsp;useTransition&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;UI 변경을 가로막지 않고 상태를 업데이트 할 수 있는 리액트 훅&lt;/li&gt;
&lt;li&gt;이 훅을 통해 긴급하지 않은 상태 업데이트를 미룰 수 있고 무거운 렌더링 작업이 있는 경우 효율적&lt;/li&gt;
&lt;li&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;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://react.dev/reference/react/useTransition&quot;&gt;https://react.dev/reference/react/useTransition&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;figure id=&quot;og_1722402041381&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;useTransition &amp;ndash; React&quot; data-og-description=&quot;The library for web and native user interfaces&quot; data-og-host=&quot;react.dev&quot; data-og-source-url=&quot;https://react.dev/reference/react/useTransition&quot; data-og-url=&quot;https://react.dev/reference/react/useTransition&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bQvdEX/hyWGQfw8jt/gxFOlZsMI6uU2WVecADvPk/img.png?width=1080&amp;amp;height=567&amp;amp;face=0_0_1080_567,https://scrap.kakaocdn.net/dn/dRLU5F/hyWGXFJcWh/arjIHkmYULyOpCh0Hyfckk/img.png?width=1080&amp;amp;height=567&amp;amp;face=0_0_1080_567&quot;&gt;&lt;a href=&quot;https://react.dev/reference/react/useTransition&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://react.dev/reference/react/useTransition&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bQvdEX/hyWGQfw8jt/gxFOlZsMI6uU2WVecADvPk/img.png?width=1080&amp;amp;height=567&amp;amp;face=0_0_1080_567,https://scrap.kakaocdn.net/dn/dRLU5F/hyWGXFJcWh/arjIHkmYULyOpCh0Hyfckk/img.png?width=1080&amp;amp;height=567&amp;amp;face=0_0_1080_567');&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;useTransition &amp;ndash; React&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;The library for web and native user interfaces&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;react.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;pre class=&quot;actionscript&quot;&gt;&lt;code&gt;const [isPending, startTransition] = useTransition()
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;훅을 통해서 호출할 수 있고 isPending을 첫 번째 값으로 startTransition 두 번째 값으로 받는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function TabContainer() {
  const [isPending, startTransition] = useTransition();
  const [tab, setTab] = useState('about');

  function selectTab(nextTab) {
    startTransition(() =&amp;gt; {
      setTab(nextTab);
    });
  }
  // ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;isPending과 startTransition을 useTransition로 첫 번재로 먼저 받아주고 우리가 사용하는 컴포넌트 안에서 관리되는 상태 값을 startTransition을 통해서 상태를 조절해 줄 수 있다&lt;/li&gt;
&lt;li&gt;startTransition안에 콜백 함수로 이 상태를 업데이트 해주는 로직을 넣어주게 되면 상태 업데이트가 바로바로 되는 게 아니라 UI 변경을 블록킹하지 않는 선에서 약간 지연시켜서 처리를 해 줄 수가 있게 된다&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;startTransition 내부는 반드시 setState 같은 상태 업데이트 하는 함수 작업만 가능하다. 만약 props나 사용자 정의 훅에서 반환하는 값을 사용하고 싶다면, useDeferredValue를 써야한다&lt;/li&gt;
&lt;li&gt;startTransition으로 넘겨주는 상태 업데이트는 다른 동기 업데이트로 인해 지연될 수 있다&lt;/li&gt;
&lt;li&gt;startTranstiion으로 넘겨주는 함수는 반드시 동기 함수여야 한다. 만약 이 안에 비동기 함수(setTimeout)같은 함수를 넣으면 제대로 동작하지 않는다&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;☑️&amp;nbsp;useDeferredValue&lt;/b&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;/li&gt;
&lt;li&gt;디바운스랑 비슷하지만 차이점 존재 : 고정된 지연 시간이 없이 첫 번째 렌더링이 완료된 후 useDeferredValue로 지연된 렌더링을 수행한다&lt;/li&gt;
&lt;li&gt;useTransition과의 차이점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;useTransition : state 값을 업데이트 하는 함수를 감싸서 사용&lt;/li&gt;
&lt;li&gt;useDeferredValue : state 값 자체만을 감싸서 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://react.dev/reference/react/useDeferredValue&quot;&gt;https://react.dev/reference/react/useDeferredValue&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;figure id=&quot;og_1722402065531&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;useDeferredValue &amp;ndash; React&quot; data-og-description=&quot;The library for web and native user interfaces&quot; data-og-host=&quot;react.dev&quot; data-og-source-url=&quot;https://react.dev/reference/react/useDeferredValue&quot; data-og-url=&quot;https://react.dev/reference/react/useDeferredValue&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/eQqYu/hyWG2G285P/7fGmfSgPnrkBBCl4TnQumk/img.png?width=1080&amp;amp;height=567&amp;amp;face=0_0_1080_567,https://scrap.kakaocdn.net/dn/qaxJL/hyWGP8KLzn/vTUuYXsYXsN2ZX8mM4kSoK/img.png?width=1080&amp;amp;height=567&amp;amp;face=0_0_1080_567&quot;&gt;&lt;a href=&quot;https://react.dev/reference/react/useDeferredValue&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://react.dev/reference/react/useDeferredValue&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/eQqYu/hyWG2G285P/7fGmfSgPnrkBBCl4TnQumk/img.png?width=1080&amp;amp;height=567&amp;amp;face=0_0_1080_567,https://scrap.kakaocdn.net/dn/qaxJL/hyWGP8KLzn/vTUuYXsYXsN2ZX8mM4kSoK/img.png?width=1080&amp;amp;height=567&amp;amp;face=0_0_1080_567');&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;useDeferredValue &amp;ndash; React&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;The library for web and native user interfaces&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;react.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;const deferredValue = useDeferredValue(value)
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;어떤 값을 useDeferredValue 처리하고 싶을 때 훅의 첫 번째 인자로 넣어주고 선언해 줄 수 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import { useState, useDeferredValue } from 'react';

function SearchPage() {
  const [query, setQuery] = useState('');
  const deferredQuery = useDeferredValue(query);
  // ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;query가 계속해서 바뀌는 경우 즉, 검색 같은 경우에 키워드에 따라 쿼리가 계속 바뀔 수가 있는데 그럴 때마다 이 쿼리로 인해서 화면의 업데이트가 지연이 되거나 방해가 될 때 useDeferredValue로 처리를 해줄 수가 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 외에도 리액트 18 번전에서 추가가 됐는데 필요한 훅들이 있으면 공식 문서를 통해서 알아보자&lt;/p&gt;</description>
      <category>Front-End/React</category>
      <category>react</category>
      <category>react 18</category>
      <category>usedeferredvalue</category>
      <category>useId</category>
      <category>usetransition</category>
      <category>공부</category>
      <category>리액트</category>
      <category>리액트 18 버전</category>
      <category>코딩</category>
      <author>Voyage_dev</author>
      <guid isPermaLink="true">https://voyage-dev.tistory.com/202</guid>
      <comments>https://voyage-dev.tistory.com/202#entry202comment</comments>
      <pubDate>Wed, 31 Jul 2024 14:01:53 +0900</pubDate>
    </item>
    <item>
      <title>CSR과 SSR</title>
      <link>https://voyage-dev.tistory.com/201</link>
      <description>&lt;blockquote data-ke-style=&quot;style3&quot;&gt;✔️ CSR(클라이언트 사이드 렌더링)과 SSR(서버사이드 렌더링)을 비교해서 살펴보자&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;CSR&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;494&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/byULru/btsIMGSy2oM/fbZtUCTTvuJ6BbZQkw1Sw0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/byULru/btsIMGSy2oM/fbZtUCTTvuJ6BbZQkw1Sw0/img.png&quot; data-alt=&quot;https://velog.io/@cheal3/%EC%84%9C%EB%B2%84%EC%82%AC%EC%9D%B4%EB%93%9C-%EB%A0%8C%EB%8D%94%EB%A7%81-SSR-%EA%B3%BC-%ED%81%B4%EB%9D%BC%EC%9D%B4%EC%96%B8%ED%8A%B8-%EC%82%AC%EC%9D%B4%EB%93%9C-%EB%A0%8C%EB%8D%94%EB%A7%81-CSR-%EC%97%90-%EB%8C%80%ED%95%B4&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/byULru/btsIMGSy2oM/fbZtUCTTvuJ6BbZQkw1Sw0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbyULru%2FbtsIMGSy2oM%2FfbZtUCTTvuJ6BbZQkw1Sw0%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;700&quot; height=&quot;494&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;494&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://velog.io/@cheal3/%EC%84%9C%EB%B2%84%EC%82%AC%EC%9D%B4%EB%93%9C-%EB%A0%8C%EB%8D%94%EB%A7%81-SSR-%EA%B3%BC-%ED%81%B4%EB%9D%BC%EC%9D%B4%EC%96%B8%ED%8A%B8-%EC%82%AC%EC%9D%B4%EB%93%9C-%EB%A0%8C%EB%8D%94%EB%A7%81-CSR-%EC%97%90-%EB%8C%80%ED%95%B4&lt;/figcaption&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;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에 클라이언트가 서버로부터 리소스를 받아올 때 HTML 파일을 받아오는데 그때 빈 HTML 파일만 받아오게 된다. 그러면 사용자 입장에서는 화면에서 그 HTML 파일이 보여지더라도 아무것도 이제 볼 수가 없는 흰 화면만 나타나게 된다. 그 빈 뼈대를 가지고 그 다음에 브라우저가 자바스크립트 파일을 다운로드를 받는다. 그렇게 됐을 때 자바스크립트 안에서 이 HTML 안에 컨텐츠들을 그려주는 로직들이 포함이 되게 되어 있다. 그래서 그렇게 자바스크립트가 실행이 되면서 컨테츠가 만들어지고 렌더링이 되어지면서 보여지는 컨텐츠가 나타나고 인터랙터블한 요소들까지 나타내는 방식이 바로 클라이언트 렌더링이다.&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;그렇기 때문에 처음에 HTML을 받아서 자바스크립트 파일을 클라이언트에서 불러와서 기다렸다가 그 그리는 과정을 다 기다렸다가 가야 되기 때문에 초기 로딩 속도가 좀 느리다는 특징을 가지고 있다. 하지만 이렇게 브라우저를 한 번 다 그리고 나서 그 다음에 만약에 페이지에서 어떤 변경을 해야 될 때는 이 HTML을 다 다시 그릴 필요가 없이 그 해당 부분만 데이터를 받아와서 자바스크립트로 바꿔서 만들어 줄 수가 있기 때문에 이후에는 굉장히 빠르게 바꿔줄 수 있다는 장점을 가지고 있다.&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;그리고 클라이언트 사이드가 가지고 있는 또 하나의 장점은 이 HTML만 브라우저에서 받아서 그다음에 자바스크립트를 받아서 그려주는 방법이기 때문에 서버 측에서는 이 렌더링을 해줘야 되는 부담이 상대적으로 적다. 그리고 클라이언트 쪽에서 어떤 연산이라든지 라우팅 같은 작업들로 직접 다 처리하기 때문에 반응 속도가 굉장히 좀 빠르고 UX가 좋다는 장점도 있다.&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;하지만 반대로 아까 이야기 했던 것처럼 클라이언트에서 HTML 파일을 빈 파일로 받아오기 때문에 구글이나 네이버 같은 검색엔진의 입장에서는 이 HTML을 통해서 정보를 수집해서 검색엔진에서 노출시켜줘야 하는데 (SEO) 그렇게 수집을 하기가 어렵다. 즉, SEO 측면에서는 상당히 불리한 측면을 가지고 있다.&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;b&gt;SSR&lt;/b&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;913&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4Enwd/btsINZDB6Gk/I3d0Px7UWWIxR5C7mzTPy1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4Enwd/btsINZDB6Gk/I3d0Px7UWWIxR5C7mzTPy1/img.png&quot; data-alt=&quot;https://velog.io/@cheal3/%EC%84%9C%EB%B2%84%EC%82%AC%EC%9D%B4%EB%93%9C-%EB%A0%8C%EB%8D%94%EB%A7%81-SSR-%EA%B3%BC-%ED%81%B4%EB%9D%BC%EC%9D%B4%EC%96%B8%ED%8A%B8-%EC%82%AC%EC%9D%B4%EB%93%9C-%EB%A0%8C%EB%8D%94%EB%A7%81-CSR-%EC%97%90-%EB%8C%80%ED%95%B4&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4Enwd/btsINZDB6Gk/I3d0Px7UWWIxR5C7mzTPy1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4Enwd%2FbtsINZDB6Gk%2FI3d0Px7UWWIxR5C7mzTPy1%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;913&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;913&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://velog.io/@cheal3/%EC%84%9C%EB%B2%84%EC%82%AC%EC%9D%B4%EB%93%9C-%EB%A0%8C%EB%8D%94%EB%A7%81-SSR-%EA%B3%BC-%ED%81%B4%EB%9D%BC%EC%9D%B4%EC%96%B8%ED%8A%B8-%EC%82%AC%EC%9D%B4%EB%93%9C-%EB%A0%8C%EB%8D%94%EB%A7%81-CSR-%EC%97%90-%EB%8C%80%ED%95%B4&lt;/figcaption&gt;
&lt;/figure&gt;
&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;서버사이드 렌더링은 유저가 웹 사이트에 방문을 해서 그 브라우저에서 서버로 컨텐츠에 대한 요청을 보냈을 때 서버에서 그 요청을 받아서 HTML, CSS 그리고 페이지를 만들깅 위해 필요한 요소들을 다 렌더링을 해준다. 그러면 그 응답을 브라우저에서 받아서 유저에게 보여지는 것이다.&lt;/li&gt;
&lt;li&gt;즉, 사용자가 볼 수 있는 시간이 굉장히 빠르다는 특징을 가지고 있다.&lt;/li&gt;
&lt;li&gt;그 다음에 이제 자바스크립트 코드를 브라우저에서는 다운로드를 받게 되는데 그 이후에 자바스크립트 코드를 실행을 시켜 주면서 이 작업을 하이드레이션 작업이라고 한다.&lt;/li&gt;
&lt;li&gt;이러한 하이드레이션 작업이 이루어지고 나서 사용자가 이벤트를 정상적으로 수행할 수 있는 그런 인터랙터블한 화면을 마주할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 서버사이드 렌더링은 컨텐츠를 서버에서 만들어준다라는 특징을 가지고 있다. 아무래도 서버에서 만들어서 한 번에 전달을 해주기 때문에 빠르게 화면을 볼 수 있다는 가장 큰 장점을 가지고 있으며 사용자 입장에서는 바로 화면을 볼 수 있기 때문에 구글이나 네이버 같은 검색엔진에서도 검색엔진 봇이 돌면서 그 컨텐츠들을 HTML에서 읽기도 수월하다.&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;b&gt;SSR 단점&lt;/b&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;/li&gt;
&lt;li&gt;매번 서버에서 컨텐츠를 렌더링을 해줘야 하기 때문에 그 연산에 대한 부담이 서버 측에 굉장히 많이 갈 수가 있다. 즉, 시스템적으로 부하를 분산시킬 수 있는 고민도 같이 해줘야 될 수가 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;CSR vs SSR 어떤 것을 사용해야 하는가?&lt;/b&gt;&lt;/p&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;1071&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bZOPKv/btsINqPd2eL/kLs4pxNa0NfKcidCK8lqKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bZOPKv/btsINqPd2eL/kLs4pxNa0NfKcidCK8lqKK/img.png&quot; data-alt=&quot;https://www.joshwcomeau.com/react/server-components/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bZOPKv/btsINqPd2eL/kLs4pxNa0NfKcidCK8lqKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbZOPKv%2FbtsINqPd2eL%2FkLs4pxNa0NfKcidCK8lqKK%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;1071&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1071&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://www.joshwcomeau.com/react/server-components/&lt;/figcaption&gt;
&lt;/figure&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 alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1071&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bz5siu/btsIOmL8kjF/pmhGTarR0mDhGJx3TXXA8K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bz5siu/btsIOmL8kjF/pmhGTarR0mDhGJx3TXXA8K/img.png&quot; data-alt=&quot;https://www.joshwcomeau.com/react/server-components/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bz5siu/btsIOmL8kjF/pmhGTarR0mDhGJx3TXXA8K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbz5siu%2FbtsIOmL8kjF%2FpmhGTarR0mDhGJx3TXXA8K%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;1071&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1071&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://www.joshwcomeau.com/react/server-components/&lt;/figcaption&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;/li&gt;
&lt;li&gt;서버사이드 렌더링은 먼저 서버에서 렌더쉘이 일어나고 그다음에 클라이언트로 와서 자바스크립트를 다운로드 받아서 하이드레이션을 거치고 그다음에 데이터베이스 쿼리를 하고 렌더 컨텐츠를 수행을 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 112px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;&lt;b&gt;CSR&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;&lt;b&gt;SSR&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&lt;b&gt;First Contentful Paint(FCP)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;느림&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;빠름&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&lt;b&gt;상호작용(Interaction)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;부분 렌더링(자바스크립트)&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;페이지 전체 렌더링(HTML)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&lt;b&gt;검색엔진 최적화(SEO)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;불리함&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;유리함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&lt;b&gt;서버 부담&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;낮음&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;높음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&lt;b&gt;Time To First Byte(TTFB)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;빠름(빈 페이지 응답)&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;결론(&lt;/b&gt;&lt;b&gt;그럼 뭐를 써야할까?)&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h4&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;</description>
      <category>Front-End/Web &amp;amp; 표준 &amp;amp; ETC</category>
      <category>CSR</category>
      <category>SSR</category>
      <category>공부</category>
      <category>기록</category>
      <category>서버 사이드 렌더링</category>
      <category>코딩</category>
      <category>클라이언트 사이드 렌더링</category>
      <author>Voyage_dev</author>
      <guid isPermaLink="true">https://voyage-dev.tistory.com/201</guid>
      <comments>https://voyage-dev.tistory.com/201#entry201comment</comments>
      <pubDate>Thu, 25 Jul 2024 22:41:18 +0900</pubDate>
    </item>
    <item>
      <title>브라우저 렌더링의 원리</title>
      <link>https://voyage-dev.tistory.com/200</link>
      <description>&lt;blockquote data-ke-style=&quot;style3&quot;&gt;✔️ 브라우저 렌더링이 어떤 과정을 통해 이루어지는지 살펴보자&lt;/blockquote&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;613&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/y8co1/btsIMicxAEH/KTveNrC50qRiOFUKbb5mAk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/y8co1/btsIMicxAEH/KTveNrC50qRiOFUKbb5mAk/img.png&quot; data-alt=&quot;https://eunjinii.tistory.com/49&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/y8co1/btsIMicxAEH/KTveNrC50qRiOFUKbb5mAk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fy8co1%2FbtsIMicxAEH%2FKTveNrC50qRiOFUKbb5mAk%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;613&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;613&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://eunjinii.tistory.com/49&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;사용자 인터페이스&lt;/b&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;/li&gt;
&lt;li&gt;우리가 보고 있는 페이지를 제외한 나머지 브라우저의 그 프레임 부분에 해당하는 부분을 사용자 인터페이스라고 한다&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;브라우저 엔진&lt;/b&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;/li&gt;
&lt;li&gt;사용자가 주소창에 입력한 URI 값을 렌더링 엔진에게 전달해 주는 역할&lt;/li&gt;
&lt;li&gt;새로고침, 뒤로 가기 버튼 등 명령 또한 렌더링 엔진에게 전달한다&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;자료 저장소&lt;/b&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;/li&gt;
&lt;li&gt;쿠키나 세션 등 모든 종류의 자원을 하드디스크에 저장한다&lt;/li&gt;
&lt;li&gt;HTML5 명세에는 브라우저가 지원하는 웹 데이터베이스가 정의되어 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;렌더링 엔진&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용자가 URI를 입력했을 때, URI에 해당하는 데이터를 네트워크 레이어에 전달해 주고, 응답받은 리소스 또는 스토리지에 캐싱된 리소스를 가져와 인터프리터, UI 백엔드에 전달한다&lt;/li&gt;
&lt;li&gt;HTML, CSS 코드를 파싱 해서 렌더 트리를 구성해 전달해 주는 등의 총체적인 역할을 한다&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;네트워크(통신)&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;렌더링 엔진으로부터 HTTP 요청을 받아서, 서버에게 요청하고 응답 리소스를 받아 렌더링 엔진에게 돌려준다&lt;/li&gt;
&lt;li&gt;각각의 플랫폼의 독립적인 인터페이스이고 플랫폼 하부에서 실행이 된다&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;자바스크립트 인터프리터&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;브라우저가 그려지기 위해서는 HTML과 CSS만 있으면 되는데 자바스크립트는 html 태그를 동적으로 움직이는 역할을 수행한다. 렌더링 엔진은 받아온 리소스 중에서 자바스크립트는 인터프리터에 주고, 나머지(HTML, CSS, image 등)를 파싱 하고 그린다. 크롬에서는 v8이라는 엔진을 사용하여 자바스크립트를 파싱한다.&lt;/li&gt;
&lt;li&gt;즉, 자바스크립트 코드를 해석하고 실행을 하는 장치이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;UI 백엔드&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;렌더링 엔진에서 생성된 렌더 트리를 브라우저에&amp;nbsp;그리는 역할을 한다.&lt;/li&gt;
&lt;li&gt;콤보박스 같은 창을 생각할 수 있다.&lt;/li&gt;
&lt;li&gt;기본적인 장치를 그리는 도구.&lt;/li&gt;
&lt;li&gt;플랫폼에서 명시하지 않은 일반적인 인터페이스로 운영체제 사용자 인터페이스 체제를 사용하는 특징을 가지고 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;브라우저 렌더링 프로세스&lt;/b&gt;&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;사용자가 사용자 인터페이스에 주소표시줄에 URI를 입력하여 브라우저 엔진에 전달&lt;/li&gt;
&lt;li&gt;브라우저 엔진은 자료 저장소에서 URI에 해당하는 자료를 찾고, 해당 자료를 쿠키로 저장했다면 그 자료를 렌더링 엔진에 전달&lt;/li&gt;
&lt;li&gt;렌더링 엔진은 브라우저 엔진에서 가져온 자료((HTML, CSS, image 등)를 분석한다. 동시에 URI 데이터를 통신, 자바스크립트 해석기, UI 백엔드로 전파&lt;/li&gt;
&lt;li&gt;또한 렌더링 엔진은 통신 레이어에 URI에 대한 추가 데이터(있다면)를 요청하고 응답할 때까지 기다린다&lt;/li&gt;
&lt;li&gt;응답받은 데이터에서 HTML, CSS는 렌더링 엔진이 파싱한다&lt;/li&gt;
&lt;li&gt;응답받은 데이터에서 JavaScript는 JavaScript 해석기가 파싱한다&lt;/li&gt;
&lt;li&gt;JavaScript 해석기는 파싱한 결과를 렌더링 엔진에 전달하여 3번과 5번에서 파싱한 HTML의 결과인 DOM tree을 조작한다&lt;/li&gt;
&lt;li&gt;조작이 완료된 DOM node(DOM tree 구성요소)는 render object(render tree 구성요소)로 변환한다&lt;/li&gt;
&lt;li&gt;UI 백엔드는 render object를 브라우저 렌더링 화면에 띄워준다&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;렌더링 엔진의 동작 과정&lt;/b&gt;&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;✔️ 가장 중요하다고 볼 수 있는 렌더링 엔진이 어떻게 동작하는지 알아보자&lt;/blockquote&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;DOM 트리 구축을 위한 HTML 파싱 &amp;rarr; 렌더 트리 구축 &amp;rarr; 렌더 트리 배치 &amp;rarr; 랜더 트리 그리기&lt;/b&gt;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;DOM 트리 구축을 위한 HTML 파싱&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;렌더링 엔진은 HTML과 CSS 리소스를 받은 작업까지 된 이후에 렌더링을 본격적으로 시작을 하는데 가장 먼저 하는 작업은 DOM 트리를 구축을 한다. DOM은 Document Object Model의 약자인데 이 DOM 노드로 기존에 가지고 있었던 HTML 문서를 파싱해서 변환을 하는 작업을 가장 먼저 하게 된다. 이 과정에서 HTMlL의 표준을 지켜서 작업을 하는 점을 기억하자.&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;DOM 노트를 만든 다음에 똑같이 CSS 파일을 가지고 CSS SOM 이라는 노드를 또 만든다. 이 CSS SOM은 스타일링 관련된 요소들로 이루어진 노드이다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;렌더 트리 구축&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 두 가지 DOM 노드와 CSS SOM 노드를 같이 만들면 그 두 개를 가지고 렌더 Tree를 구축을 하게 된다. 이 렌더 Tree는 색상이나 면적 같은 시각적인 속성을 갖는 사각형들을 포함을 한다. 그리고 정해진 순서대로 렌더링을 진행을 하게 된다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;렌더 트리 배치&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;렌더 트리를 만드는 생성 작업이 끝났다면 그 다음에는 그 만들어진 Tree를 가지고 레이아웃에 배치를 하는 작업을 하게 된다. 배치를 할 때 정확하게 적절한 위치에 표시가 되기 위해 각 노드의 정확한 위치에 표시가 될 수 있도록 이동하는 작업을 진행을 한다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;랜더 트리 그리기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 배치가 완료되고 나면 색상이라든지 여러 가지 스타일 관련된 요소들을 페인팅하는 작업이 필요하다. 그런 그리는 작업을 렌더 Tree에서 마지막으로 진행을 하게된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;렌더링 엔진에서 렌더 Tree를 구축하고 배치하고 그리는 작업은 브라우저에서 사용자 액션을 줄 때마다, 사용자 이벤트를 부여할 때마다도 계속해서 그려진다.&lt;/p&gt;</description>
      <category>Front-End/Web &amp;amp; 표준 &amp;amp; ETC</category>
      <category>공부</category>
      <category>렌더 트리</category>
      <category>렌더링</category>
      <category>렌더링 과정</category>
      <category>렌더링 엔진</category>
      <category>브라우저</category>
      <category>브라우저 렌더링</category>
      <category>코딩</category>
      <author>Voyage_dev</author>
      <guid isPermaLink="true">https://voyage-dev.tistory.com/200</guid>
      <comments>https://voyage-dev.tistory.com/200#entry200comment</comments>
      <pubDate>Thu, 25 Jul 2024 12:22:11 +0900</pubDate>
    </item>
    <item>
      <title>유틸리티 타입</title>
      <link>https://voyage-dev.tistory.com/199</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1240&quot; data-origin-height=&quot;698&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bMF3xn/btsILkBLkjZ/egv36PSbJ8FNFchKSwpTPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bMF3xn/btsILkBLkjZ/egv36PSbJ8FNFchKSwpTPK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bMF3xn/btsILkBLkjZ/egv36PSbJ8FNFchKSwpTPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbMF3xn%2FbtsILkBLkjZ%2Fegv36PSbJ8FNFchKSwpTPK%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;1240&quot; height=&quot;698&quot; data-origin-width=&quot;1240&quot; data-origin-height=&quot;698&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;유틸리티 타입?&lt;/b&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;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;자주 사용하는 7가지 타입&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Partial&amp;lt;T&amp;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;T라는 타입의 모든 프로퍼티를 선택적으로 만드는 타입을 구성하는 유틸리티 타입이다.&lt;/li&gt;
&lt;li&gt;이 유틸리티 타입을 통해서 주어진 타입의 모든 하위 집합을 나타내는 타입을 반환할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;interface Todo {
    title: string;
    description: string;
}

function updateTodo(todo: Todo, fieldsToUpdate: Partial&amp;lt;Todo&amp;gt;) {
    return { ...todo, ...fieldsToUpdate };
} // fieldsToUpdate는 Todo으 부분집합을 선언해 줄 수 있다

const todo1 = {
    title: 'organize desk',
    description: 'clear clutter',
};

const todo2 = updateTodo(todo1, {
    description: 'throw out trash',
}); // 첫 번째 Todo가 있고 두 번째 Todo가 Todo라는 인터페이스의 부분집합으로 description만 들어왔어도 정상적으로 잘 업데이트가 된다.
&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;&lt;b&gt;ReadOnly&amp;lt;T&amp;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;T의 모든 프로퍼티를 읽기 전용으로 설정한 타입을 구성.&lt;/li&gt;
&lt;li&gt;생성된 유틸리티 타입에 대해서는 재할당을 할 수 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;interface Todo {
    title: string;
}

const todo: Readonly&amp;lt;Todo&amp;gt; = {
    title: 'Delete inactive users',
};

todo.title = 'Hello'; // 오류: 읽기 전용 프로퍼티에 재할당할 수 없음
&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;&lt;b&gt;Record&amp;lt;K,T&amp;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;타입 두 개를 제네릭으로 받는다&lt;/li&gt;
&lt;li&gt;두 번째 인자인 타입 T에 프로퍼티의 집합 K로 타입을 구성한다.&lt;/li&gt;
&lt;li&gt;이 유틸리티 타입은 타입의 프로퍼티를 다른 타입에 매핑 시키는데 사용할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;sas&quot;&gt;&lt;code&gt;interface PageInfo {
    title: string;
}

type Page = 'home' | 'about' | 'contact';

const x: Record&amp;lt;Page, PageInfo&amp;gt; = { // Page는 키 값이고 PageInfo는 이 value 값의 인터페이스가 된다
    about: { title: 'about' },
    contact: { title: 'contact' },
    home: { title: 'home' },
};
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Record 형태의 자료 구조로 객체를 가져가고 싶을 때 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Pick&amp;lt;T,K&amp;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;타입 T에서 프로퍼티 K의 집합을 선택해서 타입을 구성하는 요소이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;interface Todo {
    title: string;
    description: string;
    completed: boolean;
}

type TodoPreview = Pick&amp;lt;Todo, 'title' | 'completed'&amp;gt;; 
// Todo 안에서 title 과 completed 만을 속성으로 가지는 타입이다 

const todo: TodoPreview = { 
    title: 'Clean room',
    completed: false,
};
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Todo의 부분집합을 만드는데 이 요소들을 가져간다라고 생각하면 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Omit&amp;lt;T,K&amp;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;T에서 모든 프로퍼티를 선택한 다음 K를 제거한 타입을 구성한다.&lt;/li&gt;
&lt;li&gt;Pick 과 반대&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;interface Todo {
    title: string;
    description: string;
    completed: boolean;
}

type TodoPreview = Omit&amp;lt;Todo, 'description'&amp;gt;;

const todo: TodoPreview = {
    title: 'Clean room',
    completed: false,
};
&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;&lt;b&gt;Exclude&amp;lt;T,U&amp;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;T에서 U에 할당할 수 있는 모든 속성을 제외한 타입을 구성하는 역할이다&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;type T0 = Exclude&amp;lt;&quot;a&quot; | &quot;b&quot; | &quot;c&quot;, &quot;a&quot;&amp;gt;;  // &quot;b&quot; | &quot;c&quot;
type T1 = Exclude&amp;lt;&quot;a&quot; | &quot;b&quot; | &quot;c&quot;, &quot;a&quot; | &quot;b&quot;&amp;gt;;  // &quot;c&quot;
type T2 = Exclude&amp;lt;string | number | (() =&amp;gt; void), Function&amp;gt;;  // string | number
&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;&lt;b&gt;Extract&amp;lt;T,U&amp;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;T에서 U에 할당할 수 있는 모든 속성을 추출해서 타입을 구성한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;type T0 = Extract&amp;lt;&quot;a&quot; | &quot;b&quot; | &quot;c&quot;, &quot;a&quot; | &quot;f&quot;&amp;gt;;  // &quot;a&quot;
type T1 = Extract&amp;lt;string | number | (() =&amp;gt; void), Function&amp;gt;;  // () =&amp;gt; void
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Front-End/TypeScript</category>
      <category>TypeScript</category>
      <category>공부</category>
      <category>기록</category>
      <category>유틸리티 타입</category>
      <category>코딩</category>
      <category>타입스크립트</category>
      <author>Voyage_dev</author>
      <guid isPermaLink="true">https://voyage-dev.tistory.com/199</guid>
      <comments>https://voyage-dev.tistory.com/199#entry199comment</comments>
      <pubDate>Wed, 24 Jul 2024 23:31:29 +0900</pubDate>
    </item>
  </channel>
</rss>