<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Moving Forward</title>
        <link>https://velog.io/</link>
        <description>𝙒𝙝𝙚𝙧𝙚 𝙩𝙝𝙚𝙧𝙚 𝙞𝙨 𝙖 𝙬𝙞𝙡𝙡 𝙩𝙝𝙚𝙧𝙚 𝙞𝙨 𝙖 𝙬𝙖𝙮 ✨</description>
        <lastBuildDate>Mon, 14 Jul 2025 00:02:56 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>Moving Forward</title>
            <url>https://velog.velcdn.com/images/dev_grit/profile/001d3171-2fb9-4a4a-83da-ccfcb040e9d7/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. Moving Forward. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/dev_grit" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[네이버 부스트캠프 베이직 웹 10기 회고]]></title>
            <link>https://velog.io/@dev_grit/%EB%84%A4%EC%9D%B4%EB%B2%84-%EB%B6%80%EC%8A%A4%ED%8A%B8%EC%BA%A0%ED%94%84-%EB%B2%A0%EC%9D%B4%EC%A7%81-%EC%9B%B9-10%EA%B8%B0-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@dev_grit/%EB%84%A4%EC%9D%B4%EB%B2%84-%EB%B6%80%EC%8A%A4%ED%8A%B8%EC%BA%A0%ED%94%84-%EB%B2%A0%EC%9D%B4%EC%A7%81-%EC%9B%B9-10%EA%B8%B0-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Mon, 14 Jul 2025 00:02:56 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/dev_grit/post/16dd2848-702e-4a85-ae32-0933cc748a88/image.png" alt=""></p>
<h2 id="느낀점">느낀점</h2>
<p>전반적으로 우리가 사용하는 기술들의 이론적인 내용까지 깊게 탐구해볼 수 있는 과제가 많이 나왔고, 다양한 자료구조를 어떻게 활용할 수 있는지 고민할 수 있는 과제가 많이 나온 것 같다.
과제를 하나씩 해결하면서, 각각의 자료구조에 깊게 탐구하고, Javascript에서는 어떻게 구현되어 있었는지 깊게 리서치 해보는 시간을 가질 수 있었던 것 같다. 또한 막히는 것이 있을 때, 수료생의 관점 영상과 다른 동료분들의 코드를 보면서 실마리를 얻을 수 있었던 것 같다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TS 스터디] 타입스크립트 개론]]></title>
            <link>https://velog.io/@dev_grit/TS-%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EA%B0%9C%EB%A1%A0</link>
            <guid>https://velog.io/@dev_grit/TS-%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EA%B0%9C%EB%A1%A0</guid>
            <pubDate>Sun, 04 May 2025 16:21:22 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>🌱 해당 포스트는 <a href="https://inf.run/EvrS5">한 입 크기로 잘라먹는 타입스크립트(TypeScript)</a>을 수강하고 정리한 글입니다.</p>
</blockquote>
<h1 id="타입스크립트란">타입스크립트란?</h1>
<p>자바스크립트를 더 안전하게 사용할 수 있도록 <strong>타입 관련 기능을 추가한 언어</strong>로, 자바스크립트의 확장판이라 볼수 있다.
<img src="https://velog.velcdn.com/images/dev_grit/post/94114844-0110-468c-8a8b-31dd460d077a/image.png" alt=""></p>
<p><code>node.js</code>(자바스크립트의 런타임 즉 실행 환경)의 등장으로, 자바스크립트가 다양한 프로그램(서버, 모바일 앱, 데스크탑 앱)을 개발하는데 사용할 수 있게 되었다.</p>
<p>하지만 이런 프로그램을 개발할 때 <strong>JS의 유연함이 오히려 독</strong>이 되었고, <strong>엄격한 문법을 통해 안정성이 보장되어야할 필요성</strong>이 생겼다. 그래서 자바스크립트를 더 안전하게 사용할 수 있도록 타입에 관련된 여러가지 기능을 추가한, <code>Typescript</code>가 등장하게 되었다.</p>
<h1 id="타입-시스템">타입 시스템</h1>
<p>모든 프로그래밍 언어에는, 사용할때 <strong>타입과 관련해서 지켜야 하는 규칙</strong>들을 모아둔 체계인 <strong>타입 시스템</strong>이 갖추어져 있다. 타입 시스템은 아래와 같은 내용들을 정의한다.</p>
<blockquote>
<ul>
<li>값들을 <strong>어떤 기준으로 묶어</strong> 타입을 규정할지</li>
</ul>
</blockquote>
<ul>
<li>코드의 타입을 <strong>언제 검사</strong>할지</li>
<li><strong>어떻게</strong> 타입을 <strong>검사</strong>할지?</li>
</ul>
<p>타입 시스템은 크게 두 가지가 있다. <strong>정적 타입 시스템</strong>과 <strong>동적 타입 시스템</strong>이다.</p>
<p><img src="https://velog.velcdn.com/images/dev_grit/post/fbd2fa93-bf9c-4c00-aea7-fc4b7c817546/image.png" alt=""></p>
<h2 id="정적-타입-시스템">정적 타입 시스템</h2>
<p><strong>코드 실행 이전</strong>에 모든 변수의 <strong>타입을 고정적으로 결정</strong>한다.
<code>ex) C, Java</code>
단점은, 모든 변수에 다 타입을 정의해야 해서, 타이핑 양과 코드량이 증가한다.</p>
<h2 id="동적-타입-시스템">동적 타입 시스템</h2>
<p><strong>코드가 실행 되는 도중에 타입을 결정</strong>한다. 따라서 아래와 같은 특징을 가질 수 있다.</p>
<blockquote>
<ul>
<li>우리가 미리 변수에 타입을 설정하지 않아도 된다.</li>
</ul>
</blockquote>
<ul>
<li>변수의 타입이 <strong>어떤 하나의 타입으로 딱 고정되지 않고</strong>, 현재 변수에 담긴 값에 따라서 <strong>변수의 타입이 동적으로 달라진다.</strong>
<img src="https://velog.velcdn.com/images/dev_grit/post/8f2c0b66-bc70-4fc1-8f3e-9c3d230803e8/image.png" alt=""></li>
</ul>
<p><code>ex) Javascript, Python</code></p>
<p>단점은, 아래 보이는 것과 같이 타입이 달라 사용할 수 없는 메소드가 있는 경우, 실행전에는 에러가 나지 않지만, 실행했을 때 에러가 난다.</p>
<p><img src="https://velog.velcdn.com/images/dev_grit/post/63af8d76-5297-4ead-80d9-756a5f44dc7e/image.png" alt=""></p>
<p>실행되었을 때 에러가나면, 프로그램이 비정상적으로 종료되는데, 실제 서비스에서 이런 오류가 나면, <strong>서비스 전체가 셧다운 된다</strong>는 말과 동일하다. ⇒ <strong>매우 치명적 😦</strong></p>
<h2 id="점진적-타입-시스템-gradual-type-system">점진적 타입 시스템 (Gradual Type System)</h2>
<p><img src="https://velog.velcdn.com/images/dev_grit/post/b3c65313-606c-4f9e-a437-7e2dcb055adc/image.png" alt="">
JS의 장점을 살릴 수 있도록, 정적 타입 시스템과 같이 모든 변수의 타입을 지정할 필요는 없지만, 안정성있는 타입 시스템이 필요하다.
이에 <strong>Typescript는</strong> 동적 타입 시스템과 정적 타입 시스템을 혼합한 것 같은 <strong>점진적 타입 시스템(gradual Type System)</strong>을 채택했다.</p>
<p><img src="https://velog.velcdn.com/images/dev_grit/post/5bc11216-ff8b-4b71-85b6-7ba1f22ee620/image.png" alt=""></p>
<p>위 그림과 같이, 타입스크립트에서는 모든 변수에 타입을 지정해줄 필요가 없다. <strong>타입스크립트</strong>가 <strong>변수에 담기는 초기값을 기준으로, 변수의 타입을 알아서 추론</strong>해준다.
따라서, <strong>정적 타입 시스템의 귀찮음</strong>과 <strong>동적 타입 시스템의 타입 불안전성 문제를 해결</strong>한다.</p>
<h2 id="정리">정리</h2>
<p><img src="https://velog.velcdn.com/images/dev_grit/post/773f1bc0-dbc1-4be3-97dc-a96ee418d45b/image.png" alt=""></p>
<h1 id="타입스크립트의-동작-원리">타입스크립트의 동작 원리</h1>
<p>대부분의 프로그래밍 언어는 프로그래밍 언어로 사람이 작성한 <strong>코드</strong>를 <strong>컴파일러</strong>를 통해 <strong>기계어(바이트코드)로 변환하는 작업</strong>을 거친다.</p>
<blockquote>
<p>정확히 말하면, <strong>코드 → AST(추상 문법 트리) → 바이트 코드 (기계어)</strong> 단계를 거친다.
<strong>AST 과정?</strong> - 코드 실행에 관계없는 요소(코드의 공백이나 주석 탭 등)들은 전부 제거하고, 트리 형태의 자료구조에 코드를 쪼개서 저장해 놓는 과정</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/dev_grit/post/11192f72-3660-40f5-b1a9-b8180389aa51/image.png" alt=""></p>
<h2 id="타입스크립트-동작과정">타입스크립트 동작과정</h2>
<p>타입스크립트도 위의 컴파일 과정과 유사한 과정을 통해 컴파일 된다.</p>
<p><img src="https://velog.velcdn.com/images/dev_grit/post/98872429-0cc7-4439-a2c9-ed9e789229ea/image.png" alt=""></p>
<blockquote>
<ol>
<li>코드가 AST로 변환된다.</li>
<li>만들어진 AST를 보고, 코드 상에 타입 오류가 없는지 검사한다 <strong>(타입 검사)</strong></li>
<li>통과하면 AST를 (기계어가 아닌) Javascript로 변환한다. </li>
</ol>
</blockquote>
<p>즉, 타입스크립트 코드를 컴파일 해서 생성한, <strong>자바스크립트 코드는 타입 검사를 통과한 자바스크립트 코드</strong> 이므로, <strong>타입 오류가 발생할 가능성이 낮은 안전한 자바스크립트 코드</strong>이다.</p>
<h2 id="최종-실행-과정">최종 실행 과정</h2>
<p><img src="https://velog.velcdn.com/images/dev_grit/post/28625e92-27b5-4723-9c9e-4e14b7ee9128/image.png" alt=""></p>
<h1 id="typescript-실행-환경-설정">Typescript 실행 환경 설정</h1>
<ol>
<li>타입스크립트를 <code>Node.js</code>에서 실행할 수 있다.</li>
<li><strong><code>Node.js</code> 내장 기능들의 타입 정보를 제공하는</strong> <code>@types/node</code> 를 설치한다.</li>
<li>타입스크립트 컴파일러(<code>tsc</code>)를 설치한다. <code>typescript</code>를 <code>npm</code>으로 설치하면, 해당 패키지에 동봉되어 있다. (<code>tsc</code>를 통해 <code>ts</code> 파일을 컴파일 하면 <code>js</code> 파일이 생성된다.)</li>
<li>생성된 <code>js</code> 파일을 <code>node</code>로 실행한다.</li>
</ol>
<blockquote>
<p>❗️ <code>tsc + node</code>를 한번에 하기 위한 도구 <code>tsx</code>가 있다.</p>
</blockquote>
<h1 id="타입스크립트-컴파일-옵션-설정">타입스크립트 컴파일 옵션 설정</h1>
<blockquote>
<p>*<em><code>Typescript</code>를 컴파일하고자 할 때, 엄격한 정도, 컴파일 결과 생성되는 JS의 버전 등을 설정할 수 있게 만들어주는 옵션
*</em></p>
</blockquote>
<p>컴파일러 옵션은 패키지 루트 폴더에, <code>tsconfig.json</code>이라는 파일에 설정할 수 있다. 아래 명령어를 통해 생성할 수 있다.</p>
<pre><code class="language-bash">tsc --init</code></pre>
<h2 id="옵션-예시">옵션 예시</h2>
<h3 id="include-옵션">include 옵션</h3>
<p>tsc에게 <strong>컴파일 할 타입스크립트 파일의 범위와 위치</strong>를 알려주는 옵션이다.</p>
<pre><code class="language-js">{
  &quot;include&quot;: [&quot;src&quot;]
}</code></pre>
<p>위처럼 설정하면, src 폴더 아래 모든 타입스크립트 파일이 한번에 컴파일된다.</p>
<p><code>Typescript</code>를 <code>Javascript</code>로 <strong>변환하는 과정</strong>이나, <strong>type 검사</strong> 등에 대한 아주 상세한 옵션들을 설정할 때 사용</p>
<p><img src="https://velog.velcdn.com/images/dev_grit/post/9089da05-0beb-4de5-aa2d-ef7f4a8edddf/image.png" alt=""></p>
<h3 id="target-옵션">target 옵션</h3>
<p>컴파일 된 javascript 코드의 버전을 설정한다.</p>
<p>타입스크립트 컴파일러의 동작 방식을 세부적으로 설정하기 위해 사용하는 기본 설정 파일은 무엇인가요?
<code>tsconfig.json</code> 파일은 타입스크립트 컴파일러(tsc)에게 어떤 파일을 컴파일할지, 어떤 자바스크립트 버전으로 만들지 등을 알려주는 역할을 해요.</p>
<pre><code class="language-js">{
  &quot;compilerOptions&quot;: {
    &quot;target&quot;: &quot;ES5&quot;
  },
  &quot;include&quot;: [&quot;src&quot;]
}
</code></pre>
<h3 id="module-옵션">module 옵션</h3>
<p>변환되는 자바스크립트 코드의 <strong>모듈 시스템을 설정</strong>할 수 있는 옵션이다.</p>
<pre><code class="language-js">{
  &quot;compilerOptions&quot;: {
    &quot;target&quot;: &quot;ESNext&quot;,
        &quot;module&quot;: &quot;CommonJS&quot;
  },
  &quot;include&quot;: [&quot;src&quot;]
}</code></pre>
<p>아래 코드를 컴파일한다고 할 때, 각 설정 옵션에 따라 결과가 달라진다.</p>
<pre><code class="language-ts">// hello.ts
export const hello = () =&gt; {
  console.log(&quot;hello&quot;);
};</code></pre>
<pre><code class="language-ts">// index.ts
import { hello } from &quot;./hello&quot;;
console.log(hello);</code></pre>
<h4 id="commonjs">CommonJs</h4>
<p>아래와 유사하게 컴파일 된다.
<img src="https://velog.velcdn.com/images/dev_grit/post/2f419923-d239-4af0-8a23-b0429f51b9b2/image.png" alt=""></p>
<h4 id="es-모듈시스템">ES 모듈시스템</h4>
<p>아래와 유사하게 컴파일 된다.
<img src="https://velog.velcdn.com/images/dev_grit/post/ca244aa9-ac0f-457b-a10f-a58c794ee6d5/image.png" alt=""></p>
<h3 id="outdir-옵션">outDir 옵션</h3>
<p>컴파일 결과 생성할 <strong>자바스크립트 코드의 위치를 결정</strong>한다.</p>
<pre><code class="language-js">{
  &quot;compilerOptions&quot;: {
    &quot;target&quot;: &quot;ESNext&quot;,
    &quot;module&quot;: &quot;ESNext&quot;,
    &quot;outDir&quot;: &quot;dist&quot;
  },
  &quot;include&quot;: [&quot;src&quot;]
}</code></pre>
<p>outDir 옵션을 이용하면, <strong>컴파일 결과로 생성되는 코드</strong>를 <strong>우리가 작성하는 코드 영역에서 분리</strong>할 수 있다.</p>
<h3 id="strict-옵션">strict 옵션</h3>
<p>타입스크립트 컴파일러의 <strong>타입 검사 엄격함의 수준</strong>을 정하는 옵션이다.</p>
<pre><code class="language-js">{
  &quot;compilerOptions&quot;: {
    ...
    &quot;strict&quot;: true // 이렇게 설정하면 타입을 지정하지 않은 함수 매개변수에는 에러가 난다.
  },
  &quot;include&quot;: [&quot;src&quot;]
}</code></pre>
<pre><code class="language-ts">export const hello = (message) =&gt; {
  console.log(&quot;hello &quot; + message);
};</code></pre>
<p>위 코드는 <code>&quot;strict&quot;: true</code>일 때, 에러가 발생한다. ts는 <strong>함수 매개변수에 타입을 지정하도록 권장</strong>한다. 왜냐하면, <strong>함수 매개변수의 경우 타입을 자동으로 추론할 수 없기 때문</strong>이다.
<img src="https://velog.velcdn.com/images/dev_grit/post/1c2fdceb-f684-4f33-a8aa-1f6052c5df29/image.png" alt=""></p>
<h3 id="moduledetection-옵션">moduleDetection 옵션</h3>
<p>타입스크립트의 <strong>모든 파일은 기본적으로 전역 파일(모듈)로 취급</strong>한다. 따라서 아래 상황에서 에러가 발생한다.</p>
<pre><code class="language-ts">// a.ts

const a = 1; // ❌

// b.ts

const a = 1; // ❌</code></pre>
<p>이 때, 두 가지 해결 방법이 있다.</p>
<h4 id="1-export-import-키워드-활용">1. <code>export</code>, <code>import</code> 키워드 활용</h4>
<p>예시) 파일에 <code>export {}</code> 구문을 넣는다.</p>
<h4 id="2-moduledetection---force">2. moduleDetection -&gt; force</h4>
<pre><code class="language-js">{
  &quot;compilerOptions&quot;: {
    &quot;target&quot;: &quot;ESNext&quot;,
    &quot;module&quot;: &quot;ESNext&quot;,
    &quot;outDir&quot;: &quot;dist&quot;,
        &quot;moduleDetection&quot;: &quot;force&quot;
  },
  &quot;include&quot;: [&quot;src&quot;]
}</code></pre>
<p><strong><code>moduleDetection</code>을 <code>force</code>로 설정</strong>하면, 자동으로 모든 타입스크립트 파일이 <strong>로컬 모듈(독립 모듈)로 취급</strong>된다. 컴파일 결과인 js 코드를 확인해보면 <code>export {}</code> 구문이 자동으로 들어간 것을 확인할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Next.js] Page Router - 렌더링 (2) SSG]]></title>
            <link>https://velog.io/@dev_grit/Next.js-Page-Router-%EB%A0%8C%EB%8D%94%EB%A7%81-2-SSG-sb6nbtll</link>
            <guid>https://velog.io/@dev_grit/Next.js-Page-Router-%EB%A0%8C%EB%8D%94%EB%A7%81-2-SSG-sb6nbtll</guid>
            <pubDate>Sun, 06 Apr 2025 13:48:24 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>🌱 해당 포스트는 <a href="https://www.inflearn.com/course/%ED%95%9C%EC%9E%85-%ED%81%AC%EA%B8%B0-nextjs/dashboard">한 입 크기로 잘라먹는 Next.js(v15)</a>을 수강하고, <a href="https://nextjs.org/docs/pages">Next.js 공식 문서 - page router</a>를 참고하여 정리한 글입니다.</p>
</blockquote>
<h2 id="들어가며">들어가며</h2>
<p>기존의 SSR(서버 사이트 렌더링)의 경우, <strong>사전 렌더링 과정에서 데이터가 필요</strong>하다면 <strong>fetching해 오는데, 시간이 오래 걸리면 응답이 느려진다</strong>는 단점이 있었다.
<img src="https://velog.velcdn.com/images/dev_grit/post/960d50dc-b380-4f18-9d1a-29105e395b22/image.png" alt="">
이런 단점을 해결할 수 있는 게 바로 SSG 렌더링 방식이다.
<img src="https://velog.velcdn.com/images/dev_grit/post/2fcbdcb8-8a11-4ff7-a103-b8523588df0c/image.png" alt="">
SSG 방식에서는 접속 요청이 들어왔을 때가 아닌, <strong>빌드 타임에 페이지를 미리 사전 렌더링 해둠</strong>으로써, 문제를 해결한다. Next.js 프로젝트에서는 <strong><code>npm run build</code> 시 사전 렌더링을 진행</strong>하게 된다. 데이터 요청도 서버가 가동되기 이전인 빌드 타임에만 일어나기 때문에, <strong>빌드 타임 이후에 발생하는 접속 요청들에 대해서는 굉장히 빠른 속도로 응답</strong>할 수 있다.</p>
<h2 id="static-site-generation-ssg-정적-사이트-생성">Static Site Generation (SSG, 정적 사이트 생성)</h2>
<h3 id="작동-방식">작동 방식</h3>
<p><img src="https://velog.velcdn.com/images/dev_grit/post/cdee15cf-78d5-4585-8523-b0f67e08719d/image.png" alt="">
SSG는 빌드 타임에 사전 렌더링을 하여 페이지를 미리 생성하고, 이후에는 더 이상 새롭게 페이지를 생성하지 않는다. 따라서, 사용자의 접속 요청이 있을 때마다, 같은 페이지를 반환한다.</p>
<h3 id="장점">장점</h3>
<p><img src="https://velog.velcdn.com/images/dev_grit/post/0864f00b-5f55-42ec-b0c0-244a4fbad105/image.png" alt="">
사전 렌더링에 많은 시간이 소요되더라도, 이후 사용자 요청에 빠르게 응답 가능하다.</p>
<h3 id="단점">단점</h3>
<p><img src="https://velog.velcdn.com/images/dev_grit/post/f8055bd9-060e-4ad0-a050-caa5e896aa88/image.png" alt=""></p>
<blockquote>
<p>SSG 방식은 빌드 타임 이후에는 <strong>사전 렌더링을 하지 않기 때문에, 매번 똑같은 페이지를 응답한다.</strong>
<strong>[상황별 적합 여부]</strong></p>
</blockquote>
<ul>
<li><strong>최신 데이터가 빠르게 반영</strong>되어야 하는 페이지 (❌)</li>
<li>데이터가 <strong>자주 업데이트 되지 않는</strong> 정적 페이지 (✅)</li>
</ul>
<h2 id="ssg-사용법">SSG 사용법</h2>
<h3 id="정적-경로에-ssg-적용하기">정적 경로에 SSG 적용하기</h3>
<pre><code class="language-tsx">// SSG 방식으로 작동
export const getStaticProps = async (context: GetStaticPropsContext) =&gt; {
  const [allBooks, recoBooks] = await Promise.all([
    fetchBooks(),
    fetchRandomBooks(),
  ]);

  return {
    props: {
      allBooks,
      recoBooks,
    },
  };
};</code></pre>
<p><strong>SSG</strong>로 데이터를 fetching 해오기 위해서, <strong><code>getStaticProps</code> 함수를 사용</strong>하면 된다. <strong><code>getStaticProps</code> 함수</strong>가 있으면, SSG로 동작한다.
<code>context</code>의 타입은 <strong><code>GetStaticPropsContext</code></strong>이다.</p>
<pre><code class="language-tsx">// props의 타입 살펴보기
export default function Home({
  allBooks,
  recoBooks,
}: InferGetStaticPropsType&lt;typeof getStaticProps&gt;) {
  return (
    &lt;div className={style.container}&gt;
      &lt;section&gt;
        &lt;h3&gt;지금 추천하는 모든 도서&lt;/h3&gt;
        {recoBooks.map((book) =&gt; (
          &lt;BookItem key={book.id} {...book} /&gt;
        ))}
      &lt;/section&gt;
      &lt;section&gt;
        &lt;h3&gt;등록된 모든 도서&lt;/h3&gt;
        {allBooks.map((book) =&gt; (
          &lt;BookItem key={book.id} {...book} /&gt;
        ))}
      &lt;/section&gt;
    &lt;/div&gt;
  );
}</code></pre>
<p><code>props</code>의 타입은 <strong><code>InferGetStaticPropsType&lt;typeof getStaticProps&gt;</code></strong>이 된다.</p>
<h4 id="ssg-적용이-불가능한-경우-쿼리-스트링이-필요한-경우">SSG 적용이 불가능한 경우) 쿼리 스트링이 필요한 경우</h4>
<p><a href="https://nextjs.org/docs/pages/api-reference/functions/get-static-props">Next.js - getStaticProps API</a> 공식문서를 살펴보면, <code>getStaticProps</code> 함수의 인자로 주어지는 <code>context</code> 안에 <code>query</code>가 없는 것을 확인할 수 있다.
<img src="https://velog.velcdn.com/images/dev_grit/post/cb81addd-4585-43ca-bacf-b084e51a6104/image.png" alt="">
사실, 빌드 타임에 query string을 알 수가 없는 것이 당연하기 때문에, <code>context</code> 안에 <code>query</code>가 없는 것은 합리적이다.
따라서 쿼리 스트링을 필요로 하는 <code>search</code> 페이지 같은 경우는 SSG 방식으로 동작시킬 수가 없다. 엄밀히 말하면, 검색 결과를 서버로부터 불러오는 동작은 수행할 수 없다.</p>
<p><strong>만약 SSG로 search 페이지를 동작시키고 싶다면, SSG+CSR로 구현해야한다.</strong></p>
<p>현재 쿼리 스트링을 꺼내와서 해당 값을 기준으로 <strong>검색 결과 데이터를 불러오는 과정을 사전 렌더링 이후</strong>에 컴포넌트 페이지 역할을 하는 컴포넌트에서 직접 진행해야 한다 (기존 react CSR). 이를 <strong>구현하는 방법은</strong> 간단하다. <strong><code>useRouter</code> 훅을 사용하여 쿼리 스트링을 받아오고, <code>useEffect</code>를 사용하여 데이터 페칭을 하며, 데이터는 <code>useState</code>를 통해 관리하면 된다..</strong>
<img src="https://velog.velcdn.com/images/dev_grit/post/9e37c145-1942-4331-a053-72d34a6ba0ce/image.png" alt=""></p>
<p>따라서 <strong>사전 렌더링 과정</strong>에서는, 결국 <strong>이 페이지의 레이아웃(div 태그 정도만) 정도만 렌더링</strong>하게 된다. <strong>그 후, 클라이언트 사이드 측</strong>에서 이 컴포넌트가 다시 실행되면서, <strong>직접 쿼리 스트링으로 검색어를 불러와서 검색 결과 데이터를 클라이언트 사이드 측에서 렌더링</strong>하게 된다.</p>
<p><img src="https://velog.velcdn.com/images/dev_grit/post/9dfc8012-8c68-440e-bdf5-ba5085c03048/image.png" alt="">
search 페이지를 들어가보면, 검색 결과 데이터는 제외하고 나머지 부분만 렌더링해서 브라우저에게 보내주는 것을 확인할 수 있다.</p>
<h3 id="동적-경로에-ssg-적용하기">동적 경로에 SSG 적용하기</h3>
<p>동적인 경로에 대해서 SSG를 적용하고 싶다면, <strong>반드시</strong> <code>getStaticPaths</code>를 통해 어떤 url 파라미터가 존재할 수 있는지, 즉 어떤 경로가 존재할 수 있는지 알아야 한다. 
<img src="https://velog.velcdn.com/images/dev_grit/post/e7850c07-d916-46a6-ad58-ca8a30312045/image.png" alt=""></p>
<p><code>getStaticPaths</code>를 사용하지 않고 동적 경로에 SSG를 적용하려고 하면 아래와 같은 에러가 뜬다.
<img src="https://velog.velcdn.com/images/dev_grit/post/705b0c7d-1ce4-42f8-9607-41ac315da68d/image.png" alt=""></p>
<p>경로를 설정하게 되면, 가능한 경로에 대한 html을 모두 생성하게 된다.
<img src="https://velog.velcdn.com/images/dev_grit/post/6cb4da98-c684-4485-992e-20707e949113/image.png" alt=""></p>
<h4 id="getstaticpaths-사용법"><code>getStaticPaths</code> 사용법</h4>
<pre><code class="language-tsx">export const getStaticPaths = () =&gt; {
  return {
    // paths라는 속성으로 어떤 경로가 존재할 수 있는지를 배열로 반환
    paths: [
      {
        // 가능한 경로를 params라는 속성에 적어주면 된다.
        params: {
          // 파라미터 이름 : 값
          // 파라미터 값은 반드시 문자열로 작성해야지 정상 작동한다.
          id: &quot;1&quot;,
        },
      },
      { params: { id: &quot;2&quot; } },
      { params: { id: &quot;3&quot; } },
    ],
    // fallback은 예외 상황에 대비하는 옵션
    // 존재하지 않는 url에 어떻게 대응할 것인지의 옵션
    fallback: false, // not found를 반환함
  };
};</code></pre>
<p>가능한 경로를 <strong>paths 속성에 배열로 반환</strong>한다.
배열에는 <strong><code>{params : {[파라미터 이름] : 문자열 경로 값}}</code></strong> 을 작성한다.
<strong><code>fallback</code> 옵션을 반드시 넣어주어야 한다.</strong> <code>fallback</code> 옵션은 존재하지 않는 url에 어떻게 대응할 것인지의 옵션으로 기본은 <code>false</code>이다. <code>false</code> 이면, <code>404.tsx(not found)</code> 페이지를 반환한다.</p>
<h3 id="fallback-옵션">fallback 옵션</h3>
<p><code>fallback</code> 옵션은 존재하지 않는 url에 대해 어떻게 대응해줄 것인지를 결정해준다.
<img src="https://velog.velcdn.com/images/dev_grit/post/490eb0dc-456d-4a6a-9a4b-70eafc6a4cc0/image.png" alt=""></p>
<blockquote>
<ul>
<li><code>fallback: false</code> : <strong>404 Not Found 페이지를 반환한다.</strong></li>
</ul>
</blockquote>
<ul>
<li><code>fallback: &quot;blocking&quot;</code> : <strong>매칭되지 않았던 경로의 페이지를 즉시 생성한다. (SSR 처럼)</strong></li>
<li><code>fallback: true</code> : <strong>매칭되지 않았던 경로의 페이지를 즉시 생성하되, 페이지만 미리 반환하고, props를 후속으로 보내준다.</strong></li>
</ul>
<h4 id="fallback-false"><code>fallback: false</code></h4>
<div style="display: flex; gap: 10px;">
    <img src="https://velog.velcdn.com/images/dev_grit/post/29466774-f6c8-43ba-a425-3dca78e722b5/image.png" width="50%"/>
    <img src="https://velog.velcdn.com/images/dev_grit/post/b0a8c97a-5348-4da4-9f7c-d91213acbced/image.png" width="50%"/>
</div>

<p>404 Not Found 페이지를 반환한다.</p>
<h4 id="fallback-blocking"><code>fallback: &quot;blocking&quot;</code></h4>
<p>SSR 방식처럼 실시간으로 요청받은 페이지를 사전 렌더링 해서 브라우저에게 반환해준다.
<img src="https://velog.velcdn.com/images/dev_grit/post/b340030f-9e34-4ec5-a179-0a598394dd3d/image.png" alt="">
따라서, blocking 옵션을 이용하면 빌드 타임에 사전에 생성해 두지 않았던 페이지까지 사용자에게 제공해줄수 있다는 장점이 있다.</p>
<p>이후 접속 요청이 들어와 생성된 페이지는 <strong>서버에 자동으로 저장</strong>된다.</p>
<div style="display: flex; gap: 10px;">
    <img src="https://velog.velcdn.com/images/dev_grit/post/c72a0f1b-8d92-4af2-b9b2-33b656c5dc97/image.png" width="50%"/>
    <img src="https://velog.velcdn.com/images/dev_grit/post/973e7e31-aaba-4f81-9740-c944cf3e0e1d/image.png" width="50%"/>
</div>

<h4 id="fallback-true"><code>fallback: true</code></h4>
<p><code>fallback: &quot;blocking&quot;</code> 방식을 사용하여, 존재하지 않았던 페이지를 SSR 방식으로 새롭게 생성할 때, 만약 백엔드 서버로의 추가적인 데이터를 요청 등으로 인해 페이지의 생성 시간(사전 렌더링)이 길어지면 서버가 응답을 아무것도 하지 않기 때문에 문제가 발생할 수 있다.
<strong>이런 문제를 해결할 수 있는 방법이 <code>fallback: true</code> 옵션이다.</strong>
<img src="https://velog.velcdn.com/images/dev_grit/post/78ed6610-63c9-4f03-8df6-3128f59fbaa1/image.png" alt="">
이 방식은, <code>fallback: &quot;blocking&quot;</code> 처럼, 존재하지 않았던 페이지를 <strong>SSR 방식으로 새롭게 생성</strong>하지만, <strong>props가 없는 상태로 페이지를 반환하고(사실상 레이아웃이 그려진다)</strong>, <strong>이후에 props를 전달</strong>해주는 방식으로 페이지를 렌더링 한다.</p>
<blockquote>
<p><strong>✨ 여기서 의미하는 Props란?</strong>
페이지에 필요한 데이터를 가져오는 <strong><code>getStaticProps</code> 함수가 페이지 컴포넌트에 전달해주는 props</strong></p>
</blockquote>
<p><strong>예시</strong></p>
<pre><code class="language-tsx">export const getStaticPaths = () =&gt; {
  return {
    // paths라는 속성으로 어떤 경로가 존재할 수 있는지를 배열로 반환
    paths: [
      { params: { id: &quot;1&quot; } },
      { params: { id: &quot;2&quot; } },
      { params: { id: &quot;3&quot; } },
    ],
    // fallback: false, // not found를 반환함
    // fallback: &quot;blocking&quot;,
    fallback: true,
  };
};

export const getStaticProps = async (context: GetStaticPropsContext) =&gt; {
  const id = context.params!.id;
  const book = await fetchOneBook(Number(id));
  return { props: { book } };
};

export default function Book({
  book,
}: InferGetStaticPropsType&lt;typeof getStaticProps&gt;) {
  if (!book) {
    return &quot;문제가 발생했습니다. 다시 시도해주세요&quot;;
  }

  const { id, title, subTitle, description, author, publisher, coverImgUrl } =
    book;

  return (
    &lt;div className={style.container}&gt;
     /* 코드 생략 */
    &lt;/div&gt;
  );
}
</code></pre>
<p>위 코드에서 보면, <code>fallback: &quot;blocking&quot;</code> 으로 옵션을 주었기 때문에, 초기 접속시 props가 전달되지 않아,</p>
<pre><code class="language-tsx"> if (!book) {
    return &quot;문제가 발생했습니다. 다시 시도해주세요&quot;;
  }</code></pre>
<p>부분이 렌더링 되었다.
<img src="https://velog.velcdn.com/images/dev_grit/post/b27bf520-6d32-4870-875a-0dd123304890/image.png" alt=""></p>
<p>그리고 이후에 props를 json파일으로 받아 렌더링한다.
<img src="https://velog.velcdn.com/images/dev_grit/post/1b420732-1af2-456f-b7c3-eb05a918cd89/image.png" alt=""></p>
<blockquote>
<p><strong>💡 내 생각</strong>
페이지 생성이 모든 초기 접속에 대해 새로 생성하는 것이 아닌 next 서버 기준 완전 초기에만 페이지를 생성하고, 그 다음부터는 .next에 페이지를 저장하고 그 페이지를 내려보내주는 것 같다.</p>
</blockquote>
<h4 id="fallback-true-에서-fallback-상태-예외-처리"><code>fallback: true</code> 에서 fallback 상태 예외 처리</h4>
<blockquote>
<p><strong>✨ fallback 상태란?</strong>
페이지 컴포넌트가 아직 서버로부터 데이터(<code>getStaticProps</code>에서의 props)를 전달 받지 못한 상태를 말한다.</p>
</blockquote>
<p>현재 예시 코드에서 fallback 상태에 있으면, <code>if(!book) 구문에 걸려 &quot;문제가 발생했습니다.&quot;</code>로 뜨게 된다. 아직 로딩 중인 건데, <em>&quot;오류가 발생해서 데이터가 안뜨는 거구나&quot;</em> 라고 사용자가 오해할 수 있다. 따라서 제대로 된 상태를 표시할 필요가 있다.</p>
<p>그렇다고, 현재 코드의 텍스트를 <code>if(!book) 구문에 걸려 &quot;로딩중입니다.&quot;</code> 라 바꿔버리면, 실제로 데이터를 가져오는데 오류가 뜬 상황에서도 <code>&quot;로딩중입니다.&quot;</code>이라고 뜨기 때문에 문제가 있다.</p>
<p><strong>따라서 fallback 상태에서만 보여주는 UI가 필요하다</strong></p>
<pre><code class="language-tsx">export default function Book({
  book,
}: InferGetStaticPropsType&lt;typeof getStaticProps&gt;) {
  const router = useRouter();

  // fallback 상태에 해당할때만 보여지는 ui
  if (router.isFallback) return &quot;로딩중&quot;;

/* ...코드 생략... */
</code></pre>
<h4 id="props-데이터가-없는-경우-not-found-반환">props 데이터가 없는 경우, <code>Not Found</code> 반환</h4>
<pre><code class="language-tsx">export const getStaticProps = async (context: GetStaticPropsContext) =&gt; {
  const id = context.params!.id;
  const book = await fetchOneBook(Number(id));

  if (!book) {
    return {
      notFound: true,
    };
  }

  return { props: { book } };
};</code></pre>
<p><code>getStaticProps</code>의 <code>props</code>가 없는 경우,<code>{notFound: true}</code>를 리턴하여, <code>Not Found</code> 페이지를 반환할 수 있다.</p>
<h2 id="빌드-결과-살펴보기">빌드 결과 살펴보기</h2>
<p><img src="https://velog.velcdn.com/images/dev_grit/post/9cd9c97e-3bf2-418b-b583-0c4d5475de52/image.png" alt="">
먼저, <code>getStaticPaths</code> 로 설정해준 것과 같이, <code>id</code>가 <code>1</code>, <code>2</code>, <code>3</code>에 대한 경로의 페이지(html)이 생성된 것을 볼 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/dev_grit/post/8a1b4346-aa68-45cc-b315-36627046c577/image.png" alt=""></p>
<h3 id="●-ssg-prerendered-as-static-html-uses-getstaticprops">● (SSG) prerendered as static HTML (uses getStaticProps)</h3>
<p>이 페이지가 SSG 방식으로 동작. HTML로 사전 렌더링된 페이지 (<code>getStaticProps</code>를 사용해서 데이터를 불러오는 static 페이지를 의미)</p>
<h3 id="ƒ-dynamic-server-rendered-on-demand">ƒ (Dynamic) server-rendered on demand</h3>
<p>동적(다이나믹) 페이지를 의미 -&gt; 요청을 받을 때마다 사전 렌더링된다. or 실행된다.
API routes도 ssr로 작동하도록 설정되어 있기 때문에 ƒ라고 쓰여있다.</p>
<h3 id="○-static-prerendered-as-static-content">○ (Static) prerendered as static content</h3>
<p>SSG와 동일한 정적 페이지지만, <code>getStaticProps</code>를 설정하지 않음. 기본 값으로 설정된 SSG를 의미한다.
=&gt; next.js 에서는 <strong>아무것도 설정하지 않은 페이지</strong>들을 <strong>기본값으로 정적인 페이지로 빌드타임에 미리 사전 렌더링</strong>한다.</p>
<h2 id="코드">코드</h2>
<ul>
<li><a href="https://github.com/Grit03/next-onebite/tree/a6b7ddd58972c865ec065a651f8adf6f1cfb5b89">2.16) SSG 4. 폴백옵션 설정하기</a></li>
</ul>
<h2 id="✨-결론">✨ 결론</h2>
<p><strong>SSG는 빌드 타임에 페이지를 미리 사전 렌더링해 둠</strong>으로써, 사용자 요청 시 빠르게 응답할 수 있는 렌더링 방식이다.</p>
<ul>
<li><strong>장점</strong>은 사전 렌더링이 오래걸려도, 실제 접속 요청엔 빠르게 응답 가능하다는 것</li>
<li><strong>단점</strong>은 매번 똑같은 페이지만 응답하기 때문에, 최신 데이터 반영은 어렵다.</li>
<li><strong>Next.js에서는  기본적으로 next.js는 SSG로 사전 렌더링한다.</strong></li>
</ul>
<h3 id="적용-방법">적용 방법</h3>
<blockquote>
<p><strong>1)</strong> 데이터 페칭이 필요한 경우 <code>getStaticProps</code> 함수를 사용하면 된다.
<strong>2)</strong> 동적 경로에서, SSG를 적용하고 싶은 경우 <code>getStaticPath</code> 함수를 사용해서, 가능한 모든 경로에 대한 정적 페이지를 생성할 수 있다.
<strong>3)</strong> 만약 대응하지 못한 동적 경로가 있다면, fallback 옵션 값을 어떻게 하느냐에 따라 다르게 대응할 수 있다.</p>
</blockquote>
<ul>
<li><code>fallback: false</code> ** : 404 not found를 반환함**</li>
<li><code>fallback: &quot;blocking&quot;</code>** : SSR 방식으로 페이지 생성**</li>
<li><code>fallback: true</code>** : SSR 방식 + 데이터가 없는 폴백 상태의 페이지부터 반환**</li>
</ul>
<h2 id="레퍼런스">레퍼런스</h2>
<ul>
<li><a href="https://nextjs.org/docs/pages/building-your-application/rendering/static-site-generation">Next.js 공식 문서 - Static Site Generation (SSG)</a></li>
<li><a href="https://nextjs.org/docs/pages/building-your-application/data-fetching/get-static-props">getStaticProps API</a></li>
<li><a href="https://nextjs.org/docs/pages/building-your-application/data-fetching/get-static-paths">getStaticPaths API</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Next.js] Page Router - 렌더링 (1) SSR]]></title>
            <link>https://velog.io/@dev_grit/Next.js-Page-Router-%EB%A0%8C%EB%8D%94%EB%A7%81-1-SSR</link>
            <guid>https://velog.io/@dev_grit/Next.js-Page-Router-%EB%A0%8C%EB%8D%94%EB%A7%81-1-SSR</guid>
            <pubDate>Mon, 31 Mar 2025 10:29:01 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>🌱 해당 포스트는 <a href="https://www.inflearn.com/course/%ED%95%9C%EC%9E%85-%ED%81%AC%EA%B8%B0-nextjs/dashboard">한 입 크기로 잘라먹는 Next.js(v15)</a>을 수강하고, <a href="https://nextjs.org/docs/pages">Next.js 공식 문서 - page router</a>를 참고하여 정리한 글입니다.</p>
</blockquote>
<h2 id="사전-렌더링-방식">사전 렌더링 방식</h2>
<p>Next.js에서는 다양한 사전 렌더링 방식을 제공한다. 이번 포스트에서는 <strong>Page Router</strong>에서 <strong>서버 사이드 렌더링(SSR)</strong> 방식을 어떻게 구현할 수 있는지에 대해 알아보려고 한다.</p>
<h2 id="서버-사이드-렌더링-ssr">서버 사이드 렌더링 (SSR)</h2>
<p>만약 페이지가 <strong>서버 사이드 렌더링(SSR)</strong> 을 사용한다면, <strong>해당 페이지의 HTML</strong>은 <strong>매 요청마다 생성</strong>된다. 페이지를 SSR로 만들고 싶다면, <code>getServerSideProps</code> 함수를 사용한다. 이는 Next.js 프레임워크의 일종의 문법으로 받아들일 수 있다.</p>
<h4 id="getserversideprops-함수">getServerSideProps 함수</h4>
<p><code>getServerSideProps</code> 함수는 <strong>컴포넌트보다 먼저 실행</strong>되어서, <strong>컴포넌트에 필요한 데이터를 불러오는 함수</strong>이다. <code>getServerSideProps</code> 함수를 만들어 주기만 하면, 해당 페이지는 <strong>SSR로 사전 렌더링</strong>을 진행한다.</p>
<blockquote>
<p><strong>❗️ <code>getServerSideProps</code>  함수는 사전 렌더링 과정에서 서버 측에서만 딱 한번 실행된다.</strong>
따라서 <code>getServerSideProps</code>  함수에서 <code>console.log()</code>를 실행하면, 해당 출력은 next 서버에서만 보이고, 브라우저에서는 보이지 않는다.</p>
</blockquote>
<pre><code class="language-tsx">export default function Page({ data }) {
  // Render data...
}

// This gets called on every request
export async function getServerSideProps() {
  // Fetch data from external API
  const res = await fetch(`https://.../data`)
  const data = await res.json()

  // Pass data to the page via props
  return { props: { data } }
}</code></pre>
<p><code>getServerSideProps</code>는 리턴 값으로 반드시 props라는 property를 포함하는 객체를 반환해야한다. 이때 props 객체가 컴포넌트의 props로 들어가게 되고, 컴포넌트에서 사용할 수 있게 된다.</p>
<h4 id="예시-코드">예시 코드</h4>
<pre><code class="language-tsx">// src/pages/index.tsx
import SearchableLayout from &quot;@/components/searchable-layout&quot;;
import { ReactElement } from &quot;react&quot;;
/* import 구문 생략 */

// ⭐️ getServerSideProps 함수를 만들어 주기만 하면, 해당 페이지는 SSR로 사전 렌더링을 진행한다.
// &quot;/&quot; 경로로 접속해 index 페이지를 요청받아 사전 렌더링을 진행할 때, Home 컴포넌트보다 먼저 실행된다.
// 그래서 index 페이지에 필요한 데이터를 불러오는 기능(ex. 다른 백엔드 서버로부터 데이터 페칭)을 제공한다.
export const getServerSideProps = () =&gt; {
  // 컴포넌트보다 먼저 실행되어서, 컴포넌트에 필요한 데이터를 불러오는 함수
  const data = &quot;hello&quot;;
  return { props: { data } };
};

export default function Home({data}: any) {
  console.log(data);
  return (
    &lt;div className={style.container}&gt;
      /* 코드 생략 */
    &lt;/div&gt;
  );
}</code></pre>
<p>아래 console의 log는 <strong>위 예시 코드를 실행</strong>한 결과이다.</p>
<div style="display: flex">
  <img style="width: 50%" src="https://velog.velcdn.com/images/dev_grit/post/1e35f19d-f688-4802-8ad1-121a8ab73c50/image.png">
  <img style="width: 50%" src="https://velog.velcdn.com/images/dev_grit/post/7f64041b-3979-4644-a4f9-273d32689669/image.png">
</div>
사전 렌더링으로 SSR이 되었기 때문에, next 서버에서도 hello가 콘솔에 출력되고, hydration할 때 브라우저에서도 hello가 출력되는 것을 볼 수 있다.

<h4 id="실행-과정">실행 과정</h4>
<p><strong><code>getServerSideProps</code> 는 사전 렌더링 과정에서 한 번</strong> 실행된다. <strong>Home 컴포넌트</strong>의 경우, <strong>사전 렌더링 시 한 번 실행</strong>되고, <strong>클라이언트에서 hydration을 위해 한 번 실행</strong>된다. (총 두 번 실행)
따라서 컴포넌트에서도 클라이언트 API(<code>window</code> 객체 등)에 아무런 조건 없이 접근하면, 아래와 같이 에러가 난다.
<img src="https://velog.velcdn.com/images/dev_grit/post/fa16ccde-822e-4c5e-85fb-09b406c0847e/image.png" alt=""></p>
<blockquote>
<p><strong>❗️ 만약 브라우저 API를 사용해야 해서, 브라우저 측에서만 실행되는 코드를 작성하고 싶다면?</strong>
여러가지 방법이 있지만, 가장 쉬운 방법은 <code>useEffect</code>를 사용하는 것이다.</p>
</blockquote>
<pre><code class="language-tsx">export default function Home({ data }: any) {
  // 컴포넌트 마운트 이후, 실행
  useEffect(() =&gt; {
    console.log(window);
  }, []);
  return (
    &lt;div className={style.container}&gt;
      /* 코드 생략 */
    &lt;/div&gt;
  );
}</code></pre>
<p><strong>코드 실행 결과 (브라우저)</strong>
<img src="https://velog.velcdn.com/images/dev_grit/post/20c930c3-5546-4d6f-baf1-4ac21bab7ce5/image.png" alt=""></p>
<h4 id="빌드-결과">빌드 결과</h4>
<p><img src="https://velog.velcdn.com/images/dev_grit/post/29a130b7-7007-4dbb-82cb-94fa1302b7b5/image.png" alt="ƒ  (Dynamic)  server-rendered on demand"></p>
<pre><code class="language-bash">ƒ  (Dynamic)  server-rendered on demand</code></pre>
<p><code>getServerSideProps</code> 함수를 사용하니까, 바로 페이지의 사전 렌더링 방식이 <strong>서버 사이드 렌더링(SSR)</strong> 으로 바뀐 것을 볼 수 있다. (꼭 getServersideProps 함수를 구현하지 않아도, 해당 함수가 있다면 SSR로 바뀐다)</p>
<h2 id="getserversideprops-함수-자세히-알아보기"><code>getServerSideProps</code> 함수 자세히 알아보기</h2>
<h3 id="-getserversideprops-로-받아오는-데이터의-type">+) <code>getServerSideProps</code> 로 받아오는 데이터의 type</h3>
<p>Next.js에서 내장 타입을 제공하고 있다. <code>InferGetServerSidePropsType&lt;typeof getServerSideProps&gt;</code> 를 사용해서, <code>props</code>의 타입을 지정해줄 수 있다.</p>
<pre><code class="language-tsx">export default function Home({
  data,
}: InferGetServerSidePropsType&lt;typeof getServerSideProps&gt;) {

  return (
    &lt;div className={style.container}&gt;
      /* 코드 생략 */
    &lt;/div&gt;
  );
}</code></pre>
<h3 id="🔍-getserversideprops의-인자-값-context">🔍 <code>getServerSideProps</code>의 인자 값: <code>context</code></h3>
<p><code>getServerSideProps(context)</code> 함수는 호출 시 <code>context</code> 객체를 인자로 받는다.
<code>context</code> 객체의 타입은 Next.js 내장 타입인 <code>GetServerSidePropsContext</code>이다.
<span style="color: rgb(250, 82, 82); font-weight: bold">⇒ query가 필요할 때 사용하면 유용하다.</span>
<img src="https://velog.velcdn.com/images/dev_grit/post/c6a9a167-e303-4867-baa0-a2749955a153/image.png" alt=""></p>
<h3 id="⚙️-getserversideprops의-동작-방식">⚙️ <code>getServerSideProps</code>의 동작 방식</h3>
<blockquote>
<ul>
<li><code>getServerSideProps</code>는 서버에서만 실행됩니다.</li>
</ul>
</blockquote>
<ul>
<li>오직 페이지 컴포넌트에서만 <code>export</code>할 수 있습니다.</li>
<li>반환값은 <code>JSON</code> 형식입니다.</li>
<li>사용자가 해당 페이지에 접근하면, <code>getServerSideProps</code>가 호출되어 데이터를 가져오고, 그 데이터를 바탕으로 초기 HTML을 렌더링합니다.</li>
<li><code>props</code>로 전달된 값은 클라이언트에서도 초기 HTML 안에 포함되므로 민감한 정보는 절대 포함시키지 말아야 합니다.</li>
<li>사용자가 <code>next/link</code>나 <code>next/router</code>를 통해 페이지에 접근할 경우, Next.js는 서버에 API 요청을 보내고, 서버는 <code>getServerSideProps</code>를 실행합니다.</li>
<li><code>getServerSideProps</code>는 서버에서 실행되기 때문에, 별도의 API Route를 만들 필요 없이 CMS, 데이터베이스, 외부 API 등을 직접 호출할 수 있습니다.</li>
<li><code>getServerSideProps</code> 안에서 사용할 모듈은 파일 최상단에서 import할 수 있습니다.</li>
<li>이때 <code>import</code>한 모듈은 클라이언트에 번들링되지 않기 때문에 서버 전용 코드도 작성할 수 있습니다. <strong>⇒ 즉, 데이터베이스 쿼리, 보안 토큰 처리 등 클라이언트에 노출되어선 안 되는 코드를 안전하게 작성할 수 있습니다.</strong></li>
</ul>
<h2 id="❓-초기-접속-이후-getserversideprops이-있는-page에-방문한-경우">❓ 초기 접속 이후, <code>getServerSideProps</code>이 있는 page에 방문한 경우?</h2>
<p>초기 접속 이후에 페이지 이동이 발생하면, Client Side Rendering (CSR)로 작동하는데, 만약 해당 페이지가 <code>getServerSideProps</code> 함수를 포함하고 있다면 어떻게 될까?
<strong>해당 페이지의 JS 코드와 함께 <code>getServerSideProps</code>을 실행한 결과 (props)를 JSON 형식으로 전달해준다.</strong> ⇒ <a href="https://www.inflearn.com/community/questions/1388383/%ED%8E%98%EC%9D%B4%EC%A7%80-%EC%9D%B4%EB%8F%99%EC%8B%9C-%EB%98%90%EB%8A%94-%EC%83%88%EB%A1%9C%EA%B3%A0%EC%B9%A8%EC%8B%9C-%EC%82%AC%EC%A0%84%EB%9E%9C%EB%8D%94%EB%A7%81-%EC%A7%88%EB%AC%B8">관련 질문 링크🔗</a></p>
<p><img src="https://velog.velcdn.com/images/dev_grit/post/9b4131d7-d783-49a3-943c-40e12d5cb5d0/image.png" alt=""></p>
<h2 id="코드">코드</h2>
<ul>
<li><a href="https://github.com/Grit03/next-onebite/tree/acebb2af72452a48263c1eda8453fc9e4e9d4458">2.12) SSR 2. 실습</a></li>
</ul>
<h2 id="레퍼런스">레퍼런스</h2>
<ul>
<li><a href="https://nextjs.org/docs/pages/building-your-application/rendering/server-side-rendering">Next.js 공식 문서 - Server-side Rendering (SSR)</a></li>
<li><a href="https://nextjs.org/docs/pages/building-your-application/data-fetching/get-server-side-props">getServerSideProps</a></li>
<li><a href="https://nextjs.org/docs/pages/api-reference/functions/get-server-side-props">getServerSideProps API</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Next.js] Page Router - 레이아웃 적용 ]]></title>
            <link>https://velog.io/@dev_grit/Next.js-Page-Router-%EB%A0%88%EC%9D%B4%EC%95%84%EC%9B%83-%EC%A0%81%EC%9A%A9</link>
            <guid>https://velog.io/@dev_grit/Next.js-Page-Router-%EB%A0%88%EC%9D%B4%EC%95%84%EC%9B%83-%EC%A0%81%EC%9A%A9</guid>
            <pubDate>Sun, 30 Mar 2025 15:56:29 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>🌱 해당 포스트는 <a href="https://www.inflearn.com/course/%ED%95%9C%EC%9E%85-%ED%81%AC%EA%B8%B0-nextjs/dashboard">한 입 크기로 잘라먹는 Next.js(v15)</a>을 수강하고, <a href="https://nextjs.org/docs/pages">Next.js 공식 문서 - page router</a>를 참고하여 정리한 글입니다.</p>
</blockquote>
<h2 id="글로벌-레이아웃">글로벌 레이아웃</h2>
<p>next.js 앱의 모든 페이지에 다 일괄적으로 적용이 되는 글로벌 레이아웃을 설정할 수 있다. <strong>_app 컴포넌트에 공통적인 레이아웃을 만들어주면 된다.</strong>
하지만, 공통적인 레이아웃 로직이 많아지면, <strong>코드 가독성이 떨어질 수 있으므로</strong>, <strong>레이아웃 파트만 별도로 분리</strong>하여 컴포넌트를 만드는 것이 좋다.</p>
<p><code>src &gt; components &gt; global-layout.tsx</code>라는 파일을 만들고, 이 파일 안에 글로벌 레이아웃을 만든다.</p>
<h3 id="📁-파일-구조-설명">📁 파일 구조 설명</h3>
<pre><code>src/
├── components/
│   └── global-layout.tsx   ← 글로벌 레이아웃 컴포넌트
├── pages/
│   └── _app.tsx            ← 앱 전체에 글로벌 레이아웃 적용
</code></pre><h4 id="글로벌-레이아웃-생성">글로벌 레이아웃 생성</h4>
<pre><code class="language-tsx">// src/components/global-layout.tsx
import Link from &quot;next/link&quot;;
import { ReactNode } from &quot;react&quot;;
import style from &quot;./global-layout.module.css&quot;;

export default function GlobalLayout({ children }: { children: ReactNode }) {
  return (
    &lt;div className={style.container}&gt;
      &lt;header className={style.header}&gt;
        &lt;Link href=&quot;/&quot;&gt;📚 ONEBITE BOOKS&lt;/Link&gt;
      &lt;/header&gt;
      &lt;main className={style.main}&gt;{children}&lt;/main&gt;
      &lt;footer className={style.footer}&gt;제작 @grit03&lt;/footer&gt;
    &lt;/div&gt;
  );
}
</code></pre>
<h4 id="_app-컴포넌트에-적용">_app 컴포넌트에 적용</h4>
<pre><code class="language-tsx">// pages/_app.tsx

import GlobalLayout from &#39;@/components/global-layout&#39;
import type { AppProps } from &#39;next/app&#39;
import &#39;@/styles/globals.css&#39; // 전역 스타일

export default function App({ Component, pageProps }: AppProps) {
  return (
    &lt;GlobalLayout&gt;
      &lt;Component {...pageProps} /&gt;
    &lt;/GlobalLayout&gt;
  )
}</code></pre>
<h2 id="특정-페이지를-위한-레이아웃">특정 페이지를 위한 레이아웃</h2>
<p>모든 페이지에 공통적으로 적용되는 레이아웃이 아닌, 특정 페이지에만 적용되는 레이아웃이 필요한 경우가 있다. 특정 페이지에만 적용할 수 있는 레이아웃은 아래와 같이 생성할 수 있다.</p>
<p>컴포넌트에 <code>getLayout</code> 함수를 추가해서, 페이지 별 레이아웃을 구현할 수 있다.</p>
<h3 id="❶-먼저-페이지에-적용할-레이아웃을-만든다">❶ 먼저, 페이지에 적용할 레이아웃을 만든다.</h3>
<pre><code class="language-tsx">// src/components/searchable-layout.tsx
import { useRouter } from &quot;next/router&quot;;
import { ReactNode, useEffect, useState } from &quot;react&quot;;
import style from &quot;@/components/searchable-layout.module.css&quot;;

export default function SearchableLayout({
  children,
}: {
  children: ReactNode;
}) {

  /*  코드 생략... */

  return (
    &lt;div&gt;
      &lt;div className={style.searchbar_container}&gt;
        &lt;input
          value={search}
          onChange={onSearchChange}
          placeholder=&quot;검색어를 입력하세요...&quot;
        /&gt;
        &lt;button onClick={onSubmit}&gt;검색&lt;/button&gt;
      &lt;/div&gt;
      {children}
    &lt;/div&gt;
  );
}
</code></pre>
<h3 id="❷-레이아웃을-적용하고-싶은-페이지에-getlayout-함수를-속성으로-추가한다">❷ 레이아웃을 적용하고 싶은 페이지에 getLayout 함수를 속성으로 추가한다.</h3>
<pre><code class="language-tsx">// src/pages/index.tsx
import SearchableLayout from &quot;@/components/searchable-layout&quot;;
import { ReactElement, ReactNode } from &quot;react&quot;;

export default function Home() {
  return (
    &lt;div&gt;
      &lt;h1&gt;메인 화면&lt;/h&gt;
    &lt;/div&gt;
  );
}

Home.getLayout = (page: ReactElement) =&gt; {
  return &lt;SearchableLayout&gt;{page}&lt;/SearchableLayout&gt;;
};</code></pre>
<blockquote>
<p><strong>❓ 어떻게 컴포넌트 함수에 속성을 추가할 수 있는 걸까?</strong>
<strong>자바스크립트의 모든 함수들은 사실 다 객체</strong>이다. 그렇기 때문에 함수에도 속성으로 또 다른 함수를 추가할 수 있다.
🔗 <strong>참고자료 :</strong>  <a href="https://reactjs.winterlood.com/0f33b159-6b19-433b-8db4-68d6b4a122e0#a6bf452103994a75a53d872e7b143434">객체 자료형 자세히 살펴보기</a></p>
</blockquote>
<h3 id="❸-_apptsx에서-component가-getlayout-함수-속성이-있는지를-확인하고-있다면-해당-함수를-실행시켜-layout을-적용한다">❸ _app.tsx에서 Component가 getLayout 함수 속성이 있는지를 확인하고, 있다면 해당 함수를 실행시켜, Layout을 적용한다.</h3>
<pre><code class="language-tsx">// src/pages/_app.tsx
import &quot;@/styles/globals.css&quot;;
import GlobalLayout from &quot;@/components/global-layout&quot;;
import type { AppProps } from &quot;next/app&quot;;
import { ReactElement, ReactNode } from &quot;react&quot;;
import { NextPage } from &quot;next&quot;;

// 기본적인 컴포넌트에는 getLayout함수가 없으므로, 타입에 추가해야한다.
type NextPageWithLayout = NextPage &amp; {
  getLayout?: (page: ReactElement) =&gt; ReactNode;
};

// 컴포넌트를 getLayout이 옵셔널로 있는 타입으로 바꾼다.
type AppPropsWithLayout = AppProps &amp; {
  Component: NextPageWithLayout;
};

export default function App({ Component, pageProps }: AppPropsWithLayout) {
  // 컴포넌트에 getLayout 함수가 있다면 사용하고, 없으면 그대로 page를 리턴
  const getLayout = Component.getLayout ?? ((page: ReactElement) =&gt; page);

  return &lt;GlobalLayout&gt;{getLayout(&lt;Component {...pageProps} /&gt;)}&lt;/GlobalLayout&gt;;
}</code></pre>
<blockquote>
<p>❓ <strong><code>ReactElement</code> 와 <code>ReactNode</code>의 차이</strong>
<img src="https://velog.velcdn.com/images/dev_grit/post/02bbbdec-2477-4b7e-9e3e-e6ceb2b6036b/image.png" alt="">
<code>ReactNode</code>는 리액트에서 렌더링 가능한 모든 값 (<code>React.createElement</code>의 리턴 값 뿐만 아니라, string, number, null 등)을 말하며, <code>ReactElement</code>보다 포괄적인 의미이다.
<img src="https://velog.velcdn.com/images/dev_grit/post/f04f3b76-cdf4-4ea2-a610-a7dbab7a3b5c/image.png" alt=""></p>
</blockquote>
<blockquote>
<p>❓ ** 타입 단언(type assertion)이란?**
타입 단언은 <strong>개발자가 해당 타입에 대해 확신이 있을 때 사용</strong>하는 타입 지정 방식으로, 타입스크립트의 <strong>타입 추론에 기대하지 않고 개발자가 직접 타입을 명시</strong>하여 <strong>해당 타입으로 강제하는 것</strong>을 의한다.</p>
</blockquote>
<h2 id="동일-구조를-layout으로-구현했을-때의-장점">동일 구조를 Layout으로 구현했을 때의 장점</h2>
<p>페이지 간 이동 시, 입력 값이나 스크롤 위치 등 페이지 상태를 유지하고 싶다면, Single-Page Application(SPA) 경험을 제공하는 것이 좋다.
Layout 패턴을 사용하면, 공통된 부분은 React 컴포넌트 트리가 동일하기 때문에, Layout에 있는 값들이 페이지 전환 중에도 유지된다. <strong>왜냐하면, React는 컴포넌트 트리가 유지되면, 상태(state) 값들을 보존해주기 때문이다.</strong></p>
<p>🔗 <a href="https://ko.react.dev/learn/preserving-and-resetting-state">관련 공식 react article</a></p>
<h2 id="레퍼런스">레퍼런스</h2>
<ul>
<li><a href="https://nextjs.org/docs/pages/building-your-application/routing/pages-and-layouts">Next.js 공식문서-Pages and Layouts</a></li>
<li><a href="https://reactjs.winterlood.com/0f33b159-6b19-433b-8db4-68d6b4a122e0#db73f08712734b6c927a7cced587c089">TS 객체 자료형</a></li>
<li><a href="https://joshua1988.github.io/ts/guide/type-assertion.html">타입 단언</a></li>
<li><a href="https://velog.io/@njt6419/TypeScript-%ED%83%80%EC%9E%85-%EC%B6%94%EB%A1%A0-%ED%83%80%EC%9E%85-%EB%8B%A8%EC%96%B8">[TypeScript] 타입 추론과 타입 단언</a></li>
<li><a href="https://velog.io/@hanei100/ReactElement-vs-ReactNode-vs-JSX.Element">ReactElement vs ReactNode vs JSX.Element</a><h2 id="코드-저장소github">코드 저장소(Github)</h2>
<a href="https://github.com/Grit03/next-onebite/tree/45c2d9e720e6a26db101c1320cf9fab30fc6c5e9">레이아웃 적용</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Next.js] App Router - Client Component]]></title>
            <link>https://velog.io/@dev_grit/Next.js-App-Router-Client-Component</link>
            <guid>https://velog.io/@dev_grit/Next.js-App-Router-Client-Component</guid>
            <pubDate>Thu, 27 Mar 2025 11:18:18 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>🌱 해당 포스트는 <a href="https://www.inflearn.com/course/%ED%95%9C%EC%9E%85-%ED%81%AC%EA%B8%B0-nextjs/dashboard">한 입 크기로 잘라먹는 Next.js(v15)</a>을 수강하고, <a href="https://nextjs.org/docs/app/building-your-application/rendering">Next.js 공식 문서 - app router</a>를 참고하여 정리한 글입니다.</p>
</blockquote>
<h2 id="client-component">Client Component</h2>
<p><img src="https://velog.velcdn.com/images/dev_grit/post/d851e87b-5f8e-449b-9a33-2d5cfcddfaa4/image.png" alt=""></p>
<p>Client Component는 클릭 이벤트 등 사용자와 상호작용할 수 있는 컴포넌트다.
Client Component는 <strong>서버에서 미리 렌더링된 후 (사전 렌더링 과정)</strong>, <strong>클라이언트에서도 JS 코드를 실행시켜</strong> 상호작용할 수 있게 만든다.</p>
<h3 id="클라이언트-렌더링의-장점">클라이언트 렌더링의 장점</h3>
<ul>
<li><p><strong>상호작용</strong>: Client Components는 상태, 효과 및 이벤트 리스너를 사용할 수 있으므로 사용자에게 즉각적인 피드백을 제공하고 UI를 업데이트할 수 있다.</p>
</li>
<li><p><strong>브라우저 API</strong>: Client Components는 <code>geolocation</code>이나 <code>localStorage</code>
와 같은 브라우저 API에 접근할 수 있다.</p>
</li>
</ul>
<h2 id="client-component-사용하기">Client Component 사용하기</h2>
<p>Next.js에서 Client Components를 사용하려면, 파일 상단에 <code>&quot;use client&quot;</code> 지시어를 <code>import</code> 위에 추가하면 된다.</p>
<pre><code class="language-tsx">// next.js 공식 문서 예시
&#39;use client&#39; // 이렇게 추가하면 된다.

import { useState } from &#39;react&#39;

export default function Counter() {
  const [count, setCount] = useState(0)

  return (
    &lt;div&gt;
      &lt;p&gt;You clicked {count} times&lt;/p&gt;
      &lt;button onClick={() =&gt; setCount(count + 1)}&gt;Click me&lt;/button&gt;
    &lt;/div&gt;
  )
}</code></pre>
<blockquote>
<h4 id="여러-use-client-진입점을-정의하는-경우">여러 <code>use client</code> 진입점을 정의하는 경우:</h4>
<p>React Component 트리에서 여러 <code>&quot;use client&quot;</code> 진입점을 정의할 수 있다. 하지만, 클라이언트에서 렌더링해야 하는 모든 컴포넌트에 <code>&quot;use client&quot;</code>를 정의할 필요는 없다.
한 번 경계를 정의하면 <strong>모든 자식 컴포넌트와 import된 모듈이 클라이언트 컴포넌트</strong>로 간주되기 때문이다.</p>
</blockquote>
<h2 id="client-component-렌더링-방식">Client Component 렌더링 방식</h2>
<p><strong>서버</strong>에서는 (<strong>사전 렌더링</strong>) :</p>
<ol>
<li><p>React는 Server Components를 React Server Component Payload (RSC Payload)라는 특별한 데이터 형식으로 렌더링하며, 여기에는 <strong>Client Components에 대한 참조가 포함</strong>되어 있다.</p>
</li>
<li><p>Next.js는 RSC Payload와 Client Component JavaScript 코드를 사용하여 서버에서 경로에 대한 <strong>HTML을 렌더링</strong>한다.</p>
</li>
</ol>
<p>그런 다음 <strong>클라이언트</strong>에서는:</p>
<ol>
<li><p>사전 렌더링 된 HTML을 사용하여 경로에 해당하는 페이지를 빠르게 보여준다. <strong>(상호작용 X)</strong></p>
</li>
<li><p>React Server Components Payload를 사용하여 Client와 Server Component 트리를 조정하고 DOM을 업데이트합니다.</p>
</li>
<li><p>JavaScript 코드를 사용하여 Client Components를 Hydration하여, UI를 인터랙티브하게 만든다.</p>
</li>
</ol>
<blockquote>
<p><strong>Hydration이란?</strong>
Hydration은 이벤트 리스너를 DOM에 연결하여 정적 HTML을 인터랙티브하게 만드는 과정입니다. 하이드레이션은 <code>hydrateRoot</code> React API를 사용하여 백그라운드에서 수행됩니다.</p>
</blockquote>
<h2 id="초기-접속-이후의-네이게이팅">초기 접속 이후의 네이게이팅</h2>
<p>초기 접속 시에만, 사전 렌더링된 페이지를 보여주고, 이후 <strong>경로 이동이 발생하는 경우</strong> <strong>Client Components가 서버 렌더링된 HTML 없이 클라이언트에서 렌더링</strong> 된다.</p>
<p>경로 이동이 발생하면, Client Component JavaScript 번들이 다운로드한다. 번들이 준비되면 React는 RSC Payload를 사용하여, Client와 Server Component 트리를 조정하고 DOM을 업데이트한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Next.js] App Router - Server Component]]></title>
            <link>https://velog.io/@dev_grit/Next.js-App-Route-Server-Component</link>
            <guid>https://velog.io/@dev_grit/Next.js-App-Route-Server-Component</guid>
            <pubDate>Thu, 27 Mar 2025 10:55:24 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>🌱 해당 포스트는 <a href="https://www.inflearn.com/course/%ED%95%9C%EC%9E%85-%ED%81%AC%EA%B8%B0-nextjs/dashboard">한 입 크기로 잘라먹는 Next.js(v15)</a>을 수강하고, <a href="https://nextjs.org/docs/app/building-your-application/rendering">Next.js 공식 문서 - app router</a>를 참고하여 정리한 글입니다.</p>
</blockquote>
<h2 id="서버-컴포넌트">서버 컴포넌트</h2>
<p>React Server Components는 UI를 서버에서 렌더링하고 선택적으로 캐싱할 수 있는 기능을 제공한다. Next.js에서는 라우트(route)기준으로 나누어 렌더링한다. 렌더링 전략은 크게 세가지가 있다.</p>
<blockquote>
<ul>
<li>Static Rendering (정적 렌더링)</li>
</ul>
</blockquote>
<ul>
<li>Dynamic Rendering (동적 렌더링)</li>
<li>Streaming (스트리밍)</li>
</ul>
<h2 id="서버-컴포넌트-렌더링-과정">서버 컴포넌트 렌더링 과정</h2>
<p>서버에서, Next.js는 React의 API를 사용하여 렌더링을 조정합니다. 렌더링 작업은 <strong>route(라우트)</strong> 세그먼트와 <strong>Suspense 경계</strong>로 나뉩니다.</p>
<p>각 청크는 두 단계로 렌더링됩니다.</p>
<h3 id="📡-서버에서">📡 서버에서</h3>
<p><img src="https://velog.velcdn.com/images/dev_grit/post/77b84200-dfc8-4515-966e-c554799d6740/image.png" alt=""></p>
<ol>
<li><p><strong>[서버 컴포넌트 실행]</strong> React는 Server Components를 <strong>React Server Component Payload (RSC Payload)</strong> 라는 특별한 데이터 형식으로 렌더링합니다. 
<img src="https://velog.velcdn.com/images/dev_grit/post/7a04e454-a5cc-49e2-a223-8d610f400cd9/image.png" alt=""></p>
</li>
<li><p><strong>[클라이언트 컴포넌트 실행]</strong> Next.js는 <strong>RSC Payload</strong>와 <strong>Client Component JavaScript 코드</strong>를 사용하여 HTML을 서버에서 생성한다. </p>
</li>
</ol>
<h3 id="👩🏻💻-클라이언트에서">👩🏻‍💻 클라이언트에서</h3>
<ol>
<li><p>전달 받은 <strong>사전 렌더링된 HTML</strong>을 보여준다. (FCP 개선)</p>
</li>
<li><p>클라이언트로 <strong>RSC Payload 전달하여 사용한다</strong>. (서버 컴포넌트와 클라이언트 컴포넌트 트리를 연결하고, DOM을 업데이트 하기 위해서 필요)
<a href="https://www.inflearn.com/community/questions/1396752/rsc-payload%EC%97%90-%EB%8C%80%ED%95%B4-%EA%B6%81%EA%B8%88%ED%95%A9%EB%8B%88%EB%8B%A4">⭐️ 더 알아보기</a></p>
</li>
</ol>
<p>3.(클라이언트 컴포넌트의 경우) <strong>JavaScript 코드를 실행</strong>시켜 Client Components를 <strong>Hydration</strong>하여 애플리케이션을 인터렉션 가능하도록 변경한다.</p>
<h2 id="서버-렌더링-전략">서버 렌더링 전략</h2>
<p><img src="https://velog.velcdn.com/images/dev_grit/post/5785d9f8-15a7-4210-aa10-3c2da1531989/image.png" alt=""></p>
<h3 id="static-rendering-정적-렌더링">Static Rendering (정적 렌더링)</h3>
<p><img src="https://velog.velcdn.com/images/dev_grit/post/59573c73-3d2d-47e2-a728-a28454755ef1/image.png" alt=""></p>
<p>Next.js의 <strong>기본적인 렌더링 방식</strong>이다. 정적 렌더링을 사용하면, 경로(route)가 <strong>빌드 시간</strong>에 렌더되거나, 데이터 재검증이 끝났을 때 렌더된다.</p>
<p>정적 렌더링은 개인화된 페이지가 아닌, 사용자 구분 없이 같은 내용을 보여줘야할 때, 유용하다.</p>
<blockquote>
<p>예시) 정적 블로그 게시물, 제품 페이지</p>
</blockquote>
<h3 id="dynamic-rendering-동적-렌더링">Dynamic Rendering (동적 렌더링)</h3>
<p><img src="https://velog.velcdn.com/images/dev_grit/post/c7096ab0-099a-4cd1-9c71-817cd82b5591/image.png" alt=""></p>
<p>동적 렌더링은 <strong>사용자로부터 요청받았을 때</strong>, 경로(route)가 렌더링 된다.</p>
<p>동적 렌더링은 사용자마다 데이터가 달라 개인적인 페이지를 보여줘야하거나, 요청 시에만 알 수 있는 정보가 페이지에서 필요한 경우 유용하다. 예를 들어, <strong>쿠키(cookie)</strong> 가 필요한 경우나 <strong>URL의 search params</strong>가 있는 경우가 있다.</p>
<h4 id="동적-렌더링으로-동작하는-경우">동적 렌더링으로 동작하는 경우</h4>
<p><code>Dynamic API</code>의 사용 여부와, <code>fetch</code> 함수를 <code>{ cache: &#39;no-store&#39; }</code> 옵션과 함께 사용했는지 여부에 따라 동적 렌더링을 할지 정적 렌더링을 할지 결정한다.
<img src="https://velog.velcdn.com/images/dev_grit/post/5ce5099d-d325-4d92-8511-f0eb3be8f55d/image.png" alt="공식문서"></p>
<p>어떤 렌더링으로 동작할지는 위 상황에 따라, Next.js가 가장 최적의 방식으로 자동 결정해주기 때문에, 이를 개발자가 고민할 필요는 없다.</p>
<blockquote>
<h4 id="동적-api란">동적 API란?</h4>
<p>동적 API는 사전 렌더링 시점에는 알 수 없고, 요청 시점에만 알 수 있는 정보에 의존하는 API를 말한다. 이러한 API를 사용하면 개발자가 해당 경로를 요청 시점에서 동적으로 렌더링하겠다는 의도를 나타내는 것이며, 해당 경로 전체가 동적 렌더링으로 전환된다.
예시) <code>cookies</code>, <code>headers</code>, <code>connection</code>, <code>draftMode</code>, <code>searchParams</code> prop, <code>unstable_noStore</code></p>
</blockquote>
<h3 id="streaming-스트리밍">Streaming (스트리밍)</h3>
<p>스트리밍은 서버에서 UI를 청크로 나누어 클라이언트에 스트리밍할 수 있게 한다. 작업은 청크 단위로 나뉘어 준비가 되면 클라이언트에 스트리밍되며, 사용자가 페이지의 일부를 즉시 볼 수 있게 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Next.js] Page Router - 경로(route) 생성]]></title>
            <link>https://velog.io/@dev_grit/Next.js-Page-Router-%EB%A0%88%EC%9D%B4%EC%95%84%EC%9B%83-%EC%84%A4%EC%A0%95</link>
            <guid>https://velog.io/@dev_grit/Next.js-Page-Router-%EB%A0%88%EC%9D%B4%EC%95%84%EC%9B%83-%EC%84%A4%EC%A0%95</guid>
            <pubDate>Wed, 26 Mar 2025 07:17:19 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>🌱 해당 포스트는 <a href="https://www.inflearn.com/course/%ED%95%9C%EC%9E%85-%ED%81%AC%EA%B8%B0-nextjs/dashboard">한 입 크기로 잘라먹는 Next.js(v15)</a>을 수강하고, <a href="https://nextjs.org/docs/pages">Next.js 공식 문서 - page router</a>를 참고하여 정리한 글입니다.</p>
</blockquote>
<h2 id="nextjs의-page-router란">Next.js의 Page Router란?</h2>
<p>2025년 기준으로 Next.js는 두 가지 라우팅 방식을 제공한다. <strong>App router</strong>와 <strong>Page Router</strong>이다. Page Router가 먼저 등장한 라우터로, 현재 많은 기업에서 사용되고 있는 안정적인 라우터이다.</p>
<blockquote>
<p><strong>🚏 Routing (라우팅) 이란?</strong>
어떤 URL 요청이 왔을 때, 어떤 페이지(컴포넌트)를 보여줄지를 결정하는 규칙</p>
</blockquote>
<p><strong>Page Router</strong> 는 <strong>파일 시스템</strong> 기반으로 한 라우터를 제공한다. <code>pages</code> directory 안에 파일이 추가되면, 자동적으로 라우트가 된다.</p>
<h2 id="경로route-만들기">경로(Route) 만들기</h2>
<h3 id="루트-경로를-위한-indextsx">루트 경로를 위한 index.tsx</h3>
<p><img src="https://velog.velcdn.com/images/dev_grit/post/100afe5a-702c-4f3d-81a7-cb5e51c071e5/image.png" alt="">
pages 폴더 아래에 있는 index.tsx 파일에 있는 컴포넌트가 <code>/</code>(루트 라우트)의 페이지로 렌더링된다.</p>
<h3 id="경로-이름tsx">경로 이름.tsx</h3>
<p><img src="https://velog.velcdn.com/images/dev_grit/post/7975f594-b6c2-41d6-88d3-289e4c91a86c/image.png" alt="">
<code>이름.tsx</code> 파일을 만들면, <code>/이름</code> 경로일 때 해당 컴포넌트를 페이지로 렌더링한다.</p>
<h3 id="경로-파일--indextsx">경로 파일 &gt; index.tsx</h3>
<p><img src="https://velog.velcdn.com/images/dev_grit/post/75900a34-6fb2-468a-9cf4-37d99731fdd8/image.png" alt=""></p>
<p>원하는 경로 이름으로 폴더를 만들고, 그 안에 <code>index.tsx</code> 파일을 만들면, 폴더 이름으로 경로가 만들어지고, 해당 경로로 접속했을 때, <code>index.tsx</code> 파일을 페이지로 렌더링한다.</p>
<h2 id="동적-경로dynamic-routes-만들기">동적 경로(Dynamic Routes) 만들기</h2>
<p><strong>동적 경로(Dynamic Routes)</strong> 란 경로에 <strong>가변적인 값</strong>을 포함하고 있는 경우를 말한다. 여기서 가변적인 값들을 <strong>url 파라미터</strong>라고 부른다.</p>
<blockquote>
<p>아래와 같이  URL의 일부가 변수처럼 바뀔 수 있는 구조를 말한다.
<code>/post/1</code>
<code>/post/2</code>
<code>/post/3</code></p>
</blockquote>
<h3 id="기본-동적-경로-생성">기본 동적 경로 생성</h3>
<h4 id="📁-파일-구조">📁 파일 구조</h4>
<pre><code>/pages
  └── blog
       └── [id].tsx</code></pre><h4 id="🧠-의미">🧠 의미</h4>
<p>[id].tsx는 id라는 변수에 따라 다른 콘텐츠를 보여줌
→ 마치 postId를 파라미터로 받는 느낌</p>
<h4 id="🌐-매칭되는-url-예시">🌐 매칭되는 URL 예시</h4>
<p><code>/blog/1</code>
<code>/blog/hello-world</code>
<code>/blog/42</code></p>
<h4 id="🧾-코드-예시">🧾 코드 예시</h4>
<pre><code class="language-tsx">// pages/blog/[id].tsx

import { useRouter } from &#39;next/router&#39;

export default function BlogPost() {
  const router = useRouter();
  const { id } = router.query;

  return &lt;h1&gt;블로그 글 ID: {id}&lt;/h1&gt;;
}</code></pre>
<p><code>const { id } = router.query;</code> 에서 <code>router</code>의 <code>query</code> 속성을 통해 값을 가져올 수 있다.</p>
<blockquote>
<p>⚠️ 단, /blog/id 경로에만 대응된다. 따라서 /blog/123은 대응되지만, /blog/123/123은 대응되지 않는다.</p>
</blockquote>
<h3 id="catch-all-segments">catch all segments</h3>
<p><code>[]</code>만 사용하게 되면, /blog/123은 대응되지만, /blog/123/123은 대응되지 않는다. /blog/123/123와 같은 경로도 대응할 수 있는 범용적인 페이지를 만들고 싶다면, [id].tsx가 아닌 [...id].tsx로 만들어주면 된다.</p>
<h4 id="📁-파일-구조-1">📁 파일 구조</h4>
<pre><code>/pages
  └── blog
       └── [...id].tsx</code></pre><h4 id="🌐-매칭되는-url-예시-1">🌐 매칭되는 URL 예시</h4>
<table>
<thead>
<tr>
<th align="center">Route</th>
<th align="center">Example URL</th>
<th align="center">params</th>
</tr>
</thead>
<tbody><tr>
<td align="center">pages/blog/[...id].tsx</td>
<td align="center">/blog/123</td>
<td align="center">{ id: [&#39;123&#39;] }</td>
</tr>
<tr>
<td align="center">pages/blog/[...id].tsx</td>
<td align="center">/blog/123/456</td>
<td align="center">{ id: [&#39;123&#39;, &#39;456&#39;] }</td>
</tr>
<tr>
<td align="center">pages/blog/[...id].tsx</td>
<td align="center">/blog/123/456/789</td>
<td align="center">{ id: [&#39;123&#39;, &#39;456&#39;, &#39;789&#39;] }</td>
</tr>
</tbody></table>
<h4 id="🧾-코드-예시-1">🧾 코드 예시</h4>
<pre><code class="language-tsx">// pages/blog/[...id].tsx

import { useRouter } from &#39;next/router&#39;

export default function BlogPost() {
  const router = useRouter();
  const { id } = router.query;
  console.log(id); // &#39;/blog/123/456&#39;의 경우 [&quot;123&quot;, &quot;456&quot;] 이렇게 배열로 출력

  return &lt;h1&gt;블로그 글&lt;/h1&gt;;
}</code></pre>
<p>배열 값으로 모든 url 파라미터 값이 나온다.</p>
<blockquote>
<p>⚠️ 단, /blog와 같이 url 파라미터가 아예 없는 경우에는 대응되지 않는다.
catch all segment는 /blog 뒤에 무슨 경로라도 나와야 매칭이 된다.</p>
</blockquote>
<h3 id="optional-catch-all-segments">optional catch all segments</h3>
<p>url 파라미터가 아예 없는 경우, catch all segment로 하면, index 경로에는 대응되지 않는다.
즉, <strong>/blog 페이지</strong>에는 대응되지 않는다는 의미이다.
만약에 정말 더 범용적으로 /blog 뒤에 어떤 경로가 나오든 다 대응하도록 하고 싶다면, 대괄호로 한번 더 감싸자!</p>
<h4 id="📁-파일-구조-2">📁 파일 구조</h4>
<pre><code>/pages
  └── blog
       └── [[...id]].tsx</code></pre><h4 id="🌐-매칭되는-url-예시-2">🌐 매칭되는 URL 예시</h4>
<table>
<thead>
<tr>
<th align="center">Route</th>
<th align="center">Example URL</th>
<th align="center">params</th>
</tr>
</thead>
<tbody><tr>
<td align="center">pages/blog/[[...id]].tsx</td>
<td align="center">/blog/</td>
<td align="center">{ id: undefined }</td>
</tr>
<tr>
<td align="center">pages/blog/[[...id]].tsx</td>
<td align="center">/blog/123/</td>
<td align="center">{ id: [&#39;123&#39;] }</td>
</tr>
<tr>
<td align="center">pages/blog/[[...id]].tsx</td>
<td align="center">/blog/123/456</td>
<td align="center">{ id: [&#39;123&#39;, &#39;456&#39;] }</td>
</tr>
</tbody></table>
<h3 id="404-페이지-만들기">404 페이지 만들기</h3>
<p>특정 url에 대응되는 페이지가 없을 때 보여주기 위한 404 페이지는 아래와 같이 만들 수 있다.</p>
<pre><code>/pages
  └── 404.tsx</code></pre><h2 id="✨-정리">✨ 정리</h2>
<blockquote>
<p><strong>정적 경로</strong> : <code>경로.tsx</code> 혹은 경로 폴더 아래 <code>index.tsx</code>
<strong>동적 경로</strong> : <code>[id].tsx</code>
<strong>범용적 경로</strong> : <code>[...id].tsx</code> , <code>[[...id]].tsx</code>
<strong>없는 경로 (404 페이지)</strong> : <code>404.tsx</code></p>
</blockquote>
<h2 id="레퍼런스">레퍼런스</h2>
<ul>
<li><a href="https://nextjs.org/docs/pages/building-your-application/routing/pages-and-layouts">Pages and Layouts</a></li>
<li><a href="https://nextjs.org/docs/pages/building-your-application/routing/dynamic-routes">Dynamic Routes</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[JS] Set 자료형]]></title>
            <link>https://velog.io/@dev_grit/JS-Set-%EC%9E%90%EB%A3%8C%ED%98%95</link>
            <guid>https://velog.io/@dev_grit/JS-Set-%EC%9E%90%EB%A3%8C%ED%98%95</guid>
            <pubDate>Tue, 18 Mar 2025 05:03:13 GMT</pubDate>
            <description><![CDATA[<h1 id="배열과의-차이점">배열과의 차이점</h1>
<ul>
<li><strong>인덱스로 접근 불가</strong></li>
<li><strong>중복 원소 없음</strong></li>
<li>특정 값을 삭제하거나 값이 있는지 확인할 때 배열보다 빠르게 연산 가능 → <code>Set</code>은 <strong>해시테이블</strong> 자료구조를 가지고 있기 때문이다.</li>
</ul>
<h1 id="set의-중요-메소드와-속성">Set의 중요 메소드와 속성</h1>
<ul>
<li><code>add()</code> (값을 추가)</li>
<li><code>delete()</code> (값을 삭제)</li>
<li><code>has()</code> (값의 존재 여부)</li>
<li><code>size</code> (값의 개수 확인)</li>
</ul>
<h1 id="set-생성">Set 생성</h1>
<p><code>set</code>은 <code>new</code> 키워드와 생성자를 이용하여 객체를 생성할 수 있다. 또한 배열을 인자로 넘기면, 배열에 담긴 값으로 <code>set</code>가 만들어진다.</p>
<pre><code class="language-jsx">const set = new Set(); // Set(0) {size: 0}

const numSet = new Set([0, 1, 2]); // Set(3) {0, 1, 2}</code></pre>
<h1 id="값-추가-add">값 추가: <code>add()</code></h1>
<p>시간 복잡도 :  <code>O(1)</code></p>
<p><code>add()</code> 메소드를 사용하여 값을 추가할 수 있다. 중복값은 추가되지 않는다.</p>
<pre><code class="language-jsx">set.add(1); // Set(1) {1}
set.add(&quot;B&quot;); // Set(2) {1, &#39;B&#39;}
set.add(false); // Set(3) {1, &#39;B&#39;, false}</code></pre>
<h1 id="값-삭제-delete">값 삭제: <code>delete()</code></h1>
<p>시간 복잡도 :  <code>O(1)</code></p>
<p>특정 값을 삭제하고자 할 때는 <code>delete()</code> 메소드를 사용하면 된다. delete 메소드의 인자값으로 삭제하고자 하는 요소를 넣어주면 된다.</p>
<ul>
<li>값이 있어서 성공적으로 삭제했다면, <code>true</code>를 반환</li>
<li>값이 없어서 삭제에 실패했다면, <code>false</code>를 반환</li>
</ul>
<pre><code class="language-jsx">set.delete(1); // true
set.delete(2); // false</code></pre>
<h1 id="값-존재-여부-확인-has">값 존재 여부 확인: <code>has()</code></h1>
<p>특정 값이 <code>set</code>에 존재하는지 <code>has()</code> 메소드로 확인할 수 있습니다.</p>
<pre><code class="language-jsx">if (set.has(&quot;B&quot;)) {
  console.log(&quot;Set에 B가 존재합니다.&quot;); // Set에 B가 존재합니다.
}</code></pre>
<h1 id="값-개수-확인-size">값 개수 확인: <code>size</code></h1>
<p><code>set</code>의 <code>size</code> 속성(메소드 아님!)을 통해 값의 개수를 확인할 수 있습니다. </p>
<pre><code class="language-jsx">console.log(set.size); // 2</code></pre>
<h1 id="모든-값-제거-clear">모든 값 제거: <code>clear()</code></h1>
<p><code>clear()</code> 메소드를 통해서 set의 모든 값을 제거할 수 있습니다.</p>
<pre><code class="language-jsx">set.clear(); // Set(0) {size: 0}</code></pre>
<h1 id="set-순회">Set 순회</h1>
<p><code>Set</code>의 원소들은 인덱스로 접근 불가하다. 만약 순회하고 싶다면, <code>for … of</code> 문을 통해 순회할 수 있다.</p>
<pre><code class="language-jsx">for (const name of nameSet) {
  console.log(name);
}

// 배열처럼 forEach도 가능
nameSet.forEach((name) =&gt; console.log(name));</code></pre>
<h1 id="배열-↔-set-변환">배열 ↔ Set 변환</h1>
<h3 id="배열-→-set">배열 → Set</h3>
<p><code>Set</code>을 생성할 때 배열을 인자로 주면 된다. 배열 안에 중복 요소가 있다면, 중복 요소를 제거해준다.</p>
<pre><code class="language-jsx">const arr = [0, 1, 1, 2, 2, 3, 3, 3];
const set = new Set(arr); // Set(3) {0, 1, 2, 3}</code></pre>
<p>따라서, <strong>배열에서 중복 값을 제거하고 싶을 때 사용하면 매우 유용</strong>하다.</p>
<pre><code class="language-jsx">const nums = [1, 2, 2, 3, 4];
const uniqueNums = [...new Set(numbers)]; // 중복 값 제거 뒤 다시 배열로
console.log(uniqueNums); // [1, 2, 3, 4]</code></pre>
<h3 id="set-→-배열">Set → 배열</h3>
<p>Set를 <code>spread 연산자(…)</code>를 사용하여 전개하면, 쉽게 배열로 바꿀 수 있다. <code>Array.from()</code> 메소드를 사용할 때는 Set를 인자로 넘겨주면 된다.</p>
<pre><code class="language-jsx">const array = [...set]; // [1, 2, 3]

const array = Array.from(set); // [1, 2, 3]</code></pre>
<h1 id="레퍼런스">레퍼런스</h1>
<ul>
<li><a href="https://velog.io/@jiwonyyy/javascipt-Set-vs-Array-%EC%8B%9C%EA%B0%84%EB%B3%B5%EC%9E%A1%EB%8F%84-%EB%B9%84%EA%B5%90-%ED%95%B4%EC%8B%9C%ED%85%8C%EC%9D%B4%EB%B8%94%EC%9D%B4%EB%9E%80">[javascipt] Set vs Array 시간복잡도 비교 (+ 해시테이블이란?)</a></li>
<li><a href="https://www.daleseo.com/js-set/#%EC%84%B8%ED%8A%B8-%EC%83%9D%EC%84%B1">자바스크립트 세트(Set) 완벽 가이드</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[React Ref 언제 사용해야할까?]]></title>
            <link>https://velog.io/@dev_grit/React-Ref-%EC%A0%9C%EB%8C%80%EB%A1%9C-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@dev_grit/React-Ref-%EC%A0%9C%EB%8C%80%EB%A1%9C-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Thu, 13 Mar 2025 13:24:47 GMT</pubDate>
            <description><![CDATA[<h1 id="💫-ref란">💫 Ref란</h1>
<p><code>React</code>에서 <code>ref</code>는 어떤 형태의 값이든 저장할 수 있는 변수로 생각할 수 있다.
React에서 useRef Hook을 가져와 컴포넌트에 <code>ref</code>를 추가할 수 있다.</p>
<pre><code class="language-jsx">import { useRef } from &#39;react&#39;;
function Component (){
  const ref = useRef(0); // 초기값을 인자로 전달

  return &lt;&gt;&lt;/&gt;
}
</code></pre>
<p>아래는 <code>ref</code>의 구조이다.</p>
<pre><code class="language-js">{
  current: 0  // &lt;- 저장하고 싶은 값을 useRef를 통해 전달한다
}</code></pre>
<p><code>ref</code>는 읽고 수정할 수 있는 <code>current</code> 프로퍼티를 가진 일반 자바스크립트 객체이다.
<code>ref</code>는 state와 달리 값이 변해도 렌더링을 트리거하지 않으며, 값을 변경하면, 바로 변경이 반영된다.</p>
<pre><code class="language-js">ref.current = 4;
console.log(ref.current); // 4</code></pre>
<h1 id="🤔-언제-사용해야할까">🤔 언제 사용해야할까?</h1>
<h2 id="1-값을-저장-싶을-때-리렌더링-없이">1) 값을 저장 싶을 때 (리렌더링 없이)</h2>
<blockquote>
<p>*<em>값을 저장할 변수가 필요한데, 컴포넌트가 이를 기억할 필요가 있을 때 *</em></p>
</blockquote>
<p>컴포넌트 내부에 일반 변수를 선언해서 사용하면, 컴포넌트가 리렌더링된 후에, 그 값을 기억하지 못합니다. 따라서 이때 <code>ref</code>를 사용하는 것이 적절하다.
<strong>다만, 주의해야할 것은 <code>ref</code>의 값이 변할 때, 렌더링을 발생시키지 않는다는 점이다.</strong>
(반면, state는 값이 변할 때마다, 리렌더링한다.)</p>
<p>!codesandbox[gs2l9n?view=editor+%2B+preview&amp;module=%2Fsrc%2FApp.js]</p>
<p>위 예시에서, <strong>컴포넌트는 <code>countRef.current</code>가 증가할 때마다 다시 렌더링 되지 않는다.</strong>
위에서 <code>ref</code> 값을 증가시키는 버튼을 누르면, <code>ref.current</code> 값은 변한다. 하지만, 리렌더링이 되지 않기 때문에, 화면에 출력되는 <code>ref.current</code>에는 변화가 없다.
실제 <code>ref.current</code>가 증가되었는지 궁금하다면, alert로 확인하기 버튼을 눌러서 확인할 수 있다. 그러면 <code>ref.current</code>가 버튼을 누른 횟수만큼 증가했다는 사실을 알 수 있다.</p>
<h3 id="예시-타이머-조작">예시) 타이머 조작</h3>
<pre><code class="language-jsx">import { useState } from &quot;react&quot;;

export default function TimerChallenge({ title, targetTime }) {
  const [timerExpired, setTimerExpired] = useState(false);
  const [timerStarted, setTimerStarted] = useState(false);

  // 게임 시작 (타이머 작동 시작 및 게임 시작)
  const handleStart = () =&gt; {
    // 상태 값으로 끝난 것만 알 수 있다.
    setTimeout(() =&gt; {
      // target time 후에 타이머가 끝난걸 표시
      setTimerExpired(true);
    }, targetTime * 1000);

    // 시작했다는 것을 표시하기 위한 상태 값
    setTimerStarted(true);
  };

  // 사용자가 타이머를 멈췄을 때
  const handleStop = () =&gt; {
    // 타이머를 끝내야 한다. (타이머를 어떻게 접근? 🤔)
  };

  return (
    &lt;section className=&quot;challenge&quot;&gt;
      &lt;h2&gt;{title}&lt;/h2&gt;
      &lt;p className=&quot;challenge-time&quot;&gt;{targetTime} 초&lt;/p&gt;
      {timerExpired &amp;&amp; &lt;p className=&quot;challenge-time&quot;&gt;타이머가 끝났어요 😢&lt;/p&gt;}
      &lt;p&gt;
        &lt;button onClick={handleStart}&gt;
          {timerStarted ? &quot;멈추기&quot; : &quot;시작하기&quot;}
        &lt;/button&gt;
      &lt;/p&gt;
      &lt;p className={timerStarted ? &quot;active&quot; : undefined}&gt;
        {timerStarted ? &quot;타이머 진행 중...&quot; : &quot;타이머 정지&quot;}
      &lt;/p&gt;
    &lt;/section&gt;
  );
}
</code></pre>
<p>지정된 시간 만큼 지났을 때를 유추해서, 사용자가 정지 버튼을 누르는 게임이다. 사용자가 &quot;정지&quot;버튼을 눌렀을 때 <strong>시작된 타이머에 접근</strong>할 수 있어야 하는데, 어떻게 해야할까?
<strong>이때 <code>ref</code>를 사용하기 적절하다.</strong></p>
<pre><code class="language-jsx">export default function TimerChallenge({ title, targetTime }) {
  const [timerExpired, setTimerExpired] = useState(false);
  const [timerStarted, setTimerStarted] = useState(false);

  const timer = useRef();

  // 게임 시작 (타이머 작동 시작 및 게임 시작)
  const handleStart = () =&gt; {
    // 상태 값으로 끝난 것만 알 수 있다.
    timer.current = setTimeout(() =&gt; {
      // target time 후에 타이머가 끝난걸 표시
      setTimerExpired(true);
    }, targetTime * 1000);

    // 시작했다는 것을 표시하기 위한 상태 값
    setTimerStarted(true);
  };

  // 사용자가 타이머를 멈췄을 때
  const handleStop = () =&gt; {
    // javascript에서 clearTimeout을 통해 타이머를 끝낼 수 있다. 하지만 인자로 ID가 필요하다
    // → 해당 값을 ref에 저장
    clearTimeout(timer.current);
  };

  // ... 생략 ...
</code></pre>
<blockquote>
<p>관리되어야하는 값이 있는데, timerID와 같이 ui와 직접적인 연관이 없어서 렌더링이 필요하지 않은 경우 ref를 쓰는 것이 좋다.</p>
</blockquote>
<h2 id="2-dom-요소에-접근하고-싶을-때">2) DOM 요소에 접근하고 싶을 때</h2>
<blockquote>
<p><strong>DOM 요소에 접근하여, DOM 요소 속성의 값(input의 value, 요소의 크기 등)을 읽어오거나, 포커싱, 스크롤링 등 브라우저 API 함수를 가져와 실행시키고 싶을 때</strong></p>
</blockquote>
<p><code>ref</code>는 문자열, 객체, 심지어 함수 등 모든 것을 가리킬 수 있다. 만약에 <code>ref</code>의 값을 html 요소와 같은 DOM 노드를 가리키도록 넣어주면, <strong>DOM 조작에 활용</strong>할 수 있다.</p>
<p><code>input</code> 입력에 대한 조작을 할 때, 일반적으로 <code>useState</code>를 활용해서 구현할 수 있다. 하지만, 이렇게 조작하면, <strong>사용자가 <code>input</code>에 값을 입력할 때마다 리렌더링된다.</strong>
만약, <strong>특정 이벤트</strong>(폼 제출, 버튼 클릭 등)**에만 값을 <code>input</code>에서 읽어오는 것으로 충분하다면 어떻게 하면 될까?</p>
<pre><code class="language-jsx">import { useRef, useState } from &quot;react&quot;;

export default function Player() {
  // 초기화를 안하면, 첫 렌더링 시에는 playerRef.current는 undifined 
  const playerRef = useRef(); // 보통, DOM 요소를 저장할거면, null로 초기화
  const [playerName, setPlayerName] = useState();

  const handleClick = (event) =&gt; {
    // 이벤트 핸들러에서 ref에서 값을 가져올 수 있다.
    setPlayerName(playerRef.current.value);
  };

  return (
    &lt;section id=&quot;player&quot;&gt;
      &lt;h2&gt;안녕하세요, {playerName ?? &quot;OOO&quot;} 님!&lt;/h2&gt;
      &lt;p&gt;
        &lt;input type=&quot;text&quot; ref={playerRef} /&gt;
        &lt;button onClick={handleClick}&gt;이름 저장&lt;/button&gt;
      &lt;/p&gt;
    &lt;/section&gt;
  );
}</code></pre>
<p>위 예제에서는 &quot;이름 저장 버튼&quot;을 눌러줄 때, <code>input</code>의 값을 읽어서 반영해줄 수 있다. 즉, 버튼을 클릭했을 때, <code>input</code> DOM 노드에 직접 접근하여 value 값을 가져온다. state와 함께 사용한 이유는, 단순히 화면에 보여주기 위함이다.</p>
<p>일반적으로, <code>React</code>가 렌더링 결과물에 맞춰 DOM을 직접 조작하도록 설계되어 있기 때문에, DOM을 직접 조작하는 일은 많이 없다.
<strong>→ 즉, 일반적인 case에서 DOM 조작은 react의 일!</strong></p>
<p>따라서 DOM 노드를 수정하거나 제거하기보단, 아래 상황일 때 <code>ref</code>를 사용하게 될 것이다.</p>
<blockquote>
<p><strong>1) DOM 노드에 접근해 값을 읽고 싶을 때</strong>(주로 이벤트 핸들러에서)
** 2) 노드에 정의된 내장 브라우저 API**를 사용하고자 할 때</p>
</blockquote>
<pre><code class="language-jsx">// 이벤트 핸들러에서 DOM 노드를 접근하기 위해 사용
function handleClick() {
  inputRef.current.focus();
}
// 예를 들어 이렇게 브라우저 API를 사용하거나,
myRef.current.scrollIntoView();</code></pre>
<h1 id="ref로-dom을-직접-조작해도-될까">Ref로 DOM을 직접 조작해도 될까?</h1>
<pre><code class="language-jsx">import { useRef, useState } from &quot;react&quot;;

export default function Player() {
  const playerRef = useRef();
  const [playerName, setPlayerName] = useState();

  const handleClick = (event) =&gt; {
    // 이벤트 핸들러에서 ref에서 값을 가져올 수 있다.
    setPlayerName(playerRef.current.value);
    playerRef.current.value = &quot;&quot;;
  };

  return (
    &lt;section id=&quot;player&quot;&gt;
      &lt;h2&gt;안녕하세요, {playerName ?? &quot;OOO&quot;} 님!&lt;/h2&gt;
      &lt;p&gt;
        &lt;input type=&quot;text&quot; ref={playerRef} /&gt;
        &lt;button onClick={handleClick}&gt;이름 설정&lt;/button&gt;
      &lt;/p&gt;
    &lt;/section&gt;
  );
}</code></pre>
<p>위 코드를 보면, 브라우저에게 <code>input</code>의 값을 빈 문자열로 바꾸라고 명령하고 있다. 직접 조작하지 않고,<code>React</code>가 DOM과의 상호 작용과 관련된 역할을 일임한다는, 개념을 위반하고 있다.</p>
<p>그럼에도 불구하고 그저 <code>input</code>을 지우고 싶은 경우에는, <code>input</code>이 state와 연결되어 있지 않으니, 위처럼 그냥 코드를 작성해봐도 된다. (코드 양을 줄일 수 있다.)
<strong>다만, 페이지의 모든 값들을 ref로 읽고 조작하는데 사용하지 않도록 유의하여야 한다.</strong>
<strong>특히 노드를 삭제하는 행위</strong>는, 충돌을 일으킬 수 있으므로 유의해야한다. 공식문서에 있는 codesandbox를 통해 직접 확인해볼 수 있다.
!codesandbox[w233t3?view=editor+%2B+preview&amp;module=%2Fsrc%2FApp.js&amp;hidenavigation=1]</p>
<h1 id="결론-ref-사용법">결론 (Ref 사용법)</h1>
<p><strong>✅ 렌더링을 유발하지 않고 값을 저장해야 할 때</strong>
<em>예: 타이머 ID, 이전 렌더에서 유지해야 하는 값 등</em></p>
<p><strong>✅ DOM 요소를 직접 제어해야 할 때</strong>
<em>예: <code>input</code> 값 가져오기, 포커스 주기, 스크롤 제어 등</em></p>
<p><strong>⚠️ 주의사항</strong>
❌ 값의 변경 시 리렌더링이 필요하면 사용하면 안 됨.
❌ DOM 노드 삭제 또는 직접 조작(값 변경 등)은 주의해야 함.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React] Styled Component의 모든 것]]></title>
            <link>https://velog.io/@dev_grit/React-Styled-Component%EC%9D%98-%EB%AA%A8%EB%93%A0-%EA%B2%83</link>
            <guid>https://velog.io/@dev_grit/React-Styled-Component%EC%9D%98-%EB%AA%A8%EB%93%A0-%EA%B2%83</guid>
            <pubDate>Tue, 08 Nov 2022 01:36:08 GMT</pubDate>
            <description><![CDATA[<h1 id="설치">설치</h1>
<pre><code class="language-bash">npm i styled-components</code></pre>
<p>vscode에서 styled component css syntax를 적용하고, 자동완성 기능을 사용해보고 싶다면, <strong>vscode-styled-components 익스텐션</strong>을 설치한다.</p>
<h1 id="기본-구조">기본 구조</h1>
<pre><code class="language-tsx">import styled from &quot;styled-components&quot;;

// const 컴포넌트이름 = styled.HTML태그종류 `css 코드 그대로`;
const Box = styled.div`
  background-color: royalblue;
  width: 100px;
  height: 100px;
  border-radius: 15px;
`;

function App() {
  return &lt;Box /&gt;;
}

export default App;</code></pre>
<h1 id="확장">확장</h1>
<h2 id="prop을-통한-확장">prop을 통한 확장</h2>
<p>css 구조가 거의 동일한데, 몇가지 속성만 다른 경우 사용한다.</p>
<pre><code class="language-tsx">const Box = styled.div`
  background-color: ${(props) =&gt; props.bgColor};
  width: 100px;
  height: 100px;
  border-radius: 15px;
`;

function App() {
  return (
    &lt;Container&gt;
      &lt;Box bgColor=&quot;royalblue&quot; /&gt;
      &lt;Box bgColor=&quot;deeppink&quot; /&gt;
    &lt;/Container&gt;
  );
}</code></pre>
<p>위의 예시는 <code>background-color</code> 속성만 다른 경우 사용하는 방법이다.</p>
<h2 id="상속을-통한-확장">상속을 통한 확장</h2>
<p>class에서 확장하는 것과 같이 상속을 통해 확장할 수도 있다. 이때는 <code>styled()</code> 함수를 사용하여 나타낸다. 상속을 하고 추가하고 싶은 코드만 넣어주면 된다.</p>
<pre><code class="language-tsx">//... 일부 생략
const Circle = styled(Box)`
  border-radius: 50px;
`;

function App() {
  return (
    &lt;Container&gt;
      &lt;Box bgColor=&quot;royalblue&quot; /&gt;
      &lt;Box bgColor=&quot;deeppink&quot; /&gt;
      &lt;Circle bgColor=&quot;gold&quot; /&gt;
    &lt;/Container&gt;
  );
}</code></pre>
<h2 id="태그-변경을-위한-확장">태그 변경을 위한 확장</h2>
<p>확장이 아닌, 특정 styled component의 style을 그대로 가져오려면 어떻게 해야할까? 즉, <strong>style 부분은 그대로 쓰고, 태그 부분</strong>을 바꾸고 싶다면, <code>as</code> 속성을 사용하면 된다.</p>
<pre><code class="language-tsx">const Circle = styled(Box)`
  border-radius: 50px;
`;

const Text = styled.span`
  color: purple;
  font-size: 2rem;
  font-weight: bold;
`;

function App() {
  return (
    &lt;&gt;
      &lt;Container&gt;
        &lt;Box bgColor=&quot;royalblue&quot; /&gt;
        &lt;Box bgColor=&quot;deeppink&quot; /&gt;
        &lt;Circle bgColor=&quot;gold&quot; /&gt;
      &lt;/Container&gt;
      &lt;Text&gt;Hello&lt;/Text&gt;
      &lt;Text as=&quot;a&quot; href=&quot;/&quot;&gt; Home&lt;/Text&gt;
    &lt;/&gt;
  );
}</code></pre>
<p>Text 컴포넌트는 원래 span 태그이지만, <code>as=&quot;a&quot;</code> 속성을 줌으로써 a 태그로 바꿔서 사용할 수 있다.</p>
<h1 id="태그-속성-설정">태그 속성 설정</h1>
<h2 id="html-기본-속성-default로-주기">HTML 기본 속성 Default로 주기</h2>
<pre><code class="language-tsx">// 형식
const styled_컴포넌트이름 =
      styled.HTML태그이름.attrs({ HTML_속성_이름: HTML_속성값 })`css 코드`;</code></pre>
<p><code>attrs()</code> 함수를 통해 인자로 <code>HTML_속성_이름</code> 과 <code>HTML_속성값</code>을 짝지어 놓은 object를 넘겨주면, 해당 <code>HTML_속성값</code>이 default로 지정된 컴포넌트를 생성할 수 있다.</p>
<pre><code class="language-tsx">const Input = styled.input.attrs({ required: true })`
  display: block;
`;

function App() {
  return (
    &lt;form&gt;
      &lt;Input placeholder=&quot;name&quot; /&gt;
      &lt;Input placeholder=&quot;phone number&quot; /&gt;
      &lt;Input placeholder=&quot;email&quot; /&gt;
      &lt;Input placeholder=&quot;birthday&quot; /&gt;
    &lt;/form&gt;
  );
}</code></pre>
<p>모두 입력을 필수로 하는 <code>input 태그</code>를 생성할 수 있다.</p>
<h1 id="애니메이션">애니메이션</h1>
<pre><code class="language-tsx">// ⚠️ keyframes을 import 해주어야 한다.
import styled, { keyframes } from &quot;styled-components&quot;;

// Animation의 정의
// animation 변수 keyframes helper function을 통해 만들기
const rotate = keyframes`
  from {
    transform: rotate(0deg);
  }
  to{
    transform:rotate(360deg);
  }
`;

// Animation이 추가된 styled component
const Elem = styled.div`
  background-color: royalblue;
  width: 100px;
  height: 100px;
  border-radius: 20px;
  animation: ${rotate} 3s linear infinite;
`;

function App() {
  return &lt;Elem /&gt;;
}</code></pre>
<h1 id="selector-선택자">Selector (선택자)</h1>
<h2 id="pseudo-selector">Pseudo Selector</h2>
<h3 id="일반-html-태그인-자식-element-선택하기">일반 HTML 태그인 자식 element 선택하기</h3>
<p>만약 아래 코드에서 스타일 컴포넌트가 아닌 자식 태그인 li를 선택해서 스타일을 주고 싶다면? 아래와 같이 li에 대한 css를 추가하면 된다.</p>
<pre><code class="language-tsx">const StyledUl = styled.ul`
  /*...css code*/
  li {
    color: whitesmoke;
  }
`;

function App() {
  return (
    &lt;StyledUl&gt;
      &lt;li&gt;this&lt;/li&gt;
      &lt;li&gt;is&lt;/li&gt;
      &lt;li&gt;the&lt;/li&gt;
      &lt;li&gt;list&lt;/li&gt;
    &lt;/StyledUl&gt;
  );
}</code></pre>
<p>위와 같이 부모인 sytled-component 자식 일반 엘리먼트들을 선택할 수 있다.</p>
<h3 id="sytled-component인-자식-element-선택하기">sytled-component인 자식 element 선택하기</h3>
<p>자식도 컴포넌트가 일반 태그가 아니고, <strong>sytled-component</strong>인 경우도 선택자를 사용할 수 있다. 사용법은 아래와 같다.</p>
<pre><code class="language-tsx">// 커스텀 컴포넌트를 만들고
const List = styled.li`
  color: black;
`;

// 커스텀 컴포넌트를 선택자로 사용한다.
const StyledUl = styled.ul`
  /* ... css 코드 */
  ${List} {
    color: whitesmoke;
  }
`;

function App() {
  return (
    &lt;StyledUl&gt;
      &lt;List as=&quot;span&quot;&gt;this&lt;/List&gt;
      &lt;List&gt;is&lt;/List&gt;
      &lt;List&gt;the&lt;/List&gt;
      &lt;List&gt;list&lt;/List&gt;
    &lt;/StyledUl&gt;
  );
}</code></pre>
<h2 id="state-selector">state selector</h2>
<p>&amp;를 통해 자기자신에 대한 css를 추가적으로 정의할 수 있다.(:hover 과 같은 속성을 쉽게 정의해줄 수 있다.)</p>
<pre><code class="language-tsx">const List = styled.li`
  color: black;
  &amp;:hover{
    color: white;
  }
`;
</code></pre>
<h1 id="타입스크립트와의-사용법">타입스크립트와의 사용법</h1>
<p>Styled Components에도 타입을 지정해주어야 타입스크립트와 사용할 수 있다.</p>
<h2 id="type-definition-설치">Type Definition 설치</h2>
<p>Styled Component는 <strong>DefinitelyTyped</strong>에서 설치할 수 있다.</p>
<pre><code class="language-bash">npm i --save-dev @types/styled-components</code></pre>
<p><strong>DefinitelyTyped</strong> 는 유명한 npm 라이브러리의 타입 definition을 담은 레퍼지토리이다. @types/의 파일 안에 정의되어 있다.</p>
<h2 id="interface를-통한-타입">Interface를 통한 타입</h2>
<p>필수가 아닌 스타일은 <code>?</code> 를 통해 optional로 줄 수 있다.</p>
<pre><code class="language-tsx">interface ContainerProps {
    width: string;
      bgColor?: string;
}

const Container = styled.div&lt;ContainerProps&gt;`
    width: 200px;
`;</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[메타버스 콘테스트 일지] ZEP 개발 환경 구축]]></title>
            <link>https://velog.io/@dev_grit/%EB%A9%94%ED%83%80%EB%B2%84%EC%8A%A4-%EC%BD%98%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%9D%BC%EC%A7%80-ZEP-%EA%B0%9C%EB%B0%9C-%ED%99%98%EA%B2%BD-%EA%B5%AC%EC%B6%95</link>
            <guid>https://velog.io/@dev_grit/%EB%A9%94%ED%83%80%EB%B2%84%EC%8A%A4-%EC%BD%98%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%9D%BC%EC%A7%80-ZEP-%EA%B0%9C%EB%B0%9C-%ED%99%98%EA%B2%BD-%EA%B5%AC%EC%B6%95</guid>
            <pubDate>Sun, 23 Oct 2022 09:49:55 GMT</pubDate>
            <description><![CDATA[<h1 id="zep-맵-개발을-시작하면서">ZEP 맵 개발을 시작하면서...</h1>
<p><img src="https://velog.velcdn.com/images/dev_grit/post/97ea2009-ab4a-4e64-b398-f39d6ee91f52/image.png" alt=""></p>
<p>좋은 기회로 메타버스 콘테스트라는 대회에 참여하게 되었다. 감사하게도 기획서가 예선을 통과하게 되었고, 이제 본격적으로 개발에 들어가려고 한다. 우리 팀은 많은 동시 접속을 수용하고, 한글을 지원하며 다양한 기능을 지원하는 메타버스 플랫폼인 &quot;ZEP&quot;을 사용해 개발하기로 하였다.</p>
<p>아직 메타버스라는 개념이 익숙하지 않고, 개발 관련 내용도 그리 많지 않은 것 같다. ZEP에 들어가면, ZEP 개발 문서가 생각보다 정말 자세하게 한글로 작성되어 있다. 그리고 부족하지만 ZEP 개발을 시작하려는 누군가에게 도움이 되지 않을까 하여 개발 과정을 글로 정리해 보려 한다. 개발 환경 구축과 개발 과정 그리고 시행착오를 정리하여 담을 예정이다.</p>
<h1 id="hello-zep">HELLO, ZEP!</h1>
<p><a href="https://zep.us/">🌐 ZEP 사이트 방문하기</a>
ZEP은 네이버에서 개발한 메타버스 플랫폼으로 게더타운과 유사한 UI/UX를 제공한다. 주로 2D로 된 맵에서 미니 게임, 온라인 회의 등의 기능을 사용할 수 있다. 유저가 쉽게 이동하여 타인과 대화할 수 있는 가상공간을 제공한다. ZEP을 통해 실제 맵을 구현할 수도 있고 다양한 기본 가상 공간(사무실, 교실, 사교 모임 및 비즈니스에 맞춰 나온 공간)을 자유롭게 활용할 수 있다.</p>
<h1 id="개발-환경-구축">개발 환경 구축</h1>
<p>ZEP은 ZEP script라고 Javascript 언어를 사용한다. Javascript로도 충분히 개발할 수 있지만, 이번 대회에서는 Typescript로 개발하는 것을 도전해보려고한다. Typescript를 통해 안정성있는 개발을 할 수 있을 것으로 기대된다. 그리고 ZEP 맵 공간을 개발하면서 Typescript를 연습할 수 있는 기회도 될 수 있을 것 같다.</p>
<h2 id="zep-script-설치">ZEP script 설치</h2>
<pre><code class="language-bash">npx zep-script init {폴더 이름} --npm</code></pre>
<p>위의 커맨드로 ZEP Script를 개발할 수 있는 환경을 구축할 수 있다. 설정한 폴더 이름으로 아래와 같은 폴더와 파일이 생기게 된다.
<img src ="https://velog.velcdn.com/images/dev_grit/post/ffba6d30-828d-4e19-85cf-ced87bbfc130/image.png" width="240"></p>
<p>개발할 때 실질적으로 사용하는 폴더 및 파일은 위의 res 폴더와 main.ts 파일이다.</p>
<h3 id="res-폴더">res 폴더</h3>
<p>res 폴더는 ‘resources’의 약자로, 앱에 필요한 이미지나 html 파일 등을 넣을 경로이다.</p>
<h3 id="maints">main.ts</h3>
<p><strong>main.ts</strong> 파일은 실질적인 앱개발 코드를 적는 파일이다.</p>
<h3 id="packagejson">package.json</h3>
<pre><code class="language-javascript">&quot;scripts&quot;: {
    &quot;build&quot;: &quot;zep-script build&quot;,
    &quot;archive&quot;: &quot;zep-script archive&quot;,
    &quot;deploy&quot;: &quot;zep-script publish&quot;
  },</code></pre>
<h2 id="앱-빌드">앱 빌드</h2>
<p>zep script는 javascript로 구동되기 때문에 typescript 코드를 컴파일해야 한다. 컴파일을 한 뒤 빌드해주는 커맨드는 아래와 같다.</p>
<pre><code class="language-bash">npx zep-script build</code></pre>
<p>그런데 <code>package.json</code> 폴더에 <code>&quot;build&quot;: &quot;zep-script build&quot;</code> 라고 되어 있어서 아래 커맨드로 해도 괜찮은 것 같다. (혹시 잘못된 방법이라면, 댓글로 알려주세요!)</p>
<pre><code class="language-bash">npm run build</code></pre>
<p>빌드에 성공하면, dist 라는 폴더가 생기게 된다.
<img src="https://velog.velcdn.com/images/dev_grit/post/45e8e41a-a156-4374-96c8-7f243f291ea8/image.png" alt="">
dist 파일안에는 <code>main.ts</code> 파일이 javascript로 컴파일된 파일이 존재하게 된다.</p>
<h2 id="앱-압축하기">앱 압축하기</h2>
<p>만든 앱을 배포하기 위해서는 먼저 main.js, res안의 이미지, html 파일 등을 함께 압축한 .zip 파일을 만들어야 한다. 실제 배포될 파일은 바로 이 zip 파일이다. 명령어는 아래와 같다.</p>
<pre><code class="language-bash">npx zep-script archive</code></pre>
<p>마찬가지로 <code>package.json</code> 폴더에 <code>&quot;archive&quot;: &quot;zep-script archive&quot;</code> 라 명시되어 있기 때문에, 아래 명령어로 실행해도 된다.</p>
<pre><code class="language-bash">npm run build</code></pre>
<h2 id="앱-배포">앱 배포</h2>
<p>사실 배포 가이드는 ZEP Script 가이드에 잘 나와있다. 웹페이지에 직접 파일을 올려 배포하는 방법은 아래 링크를 참조하면 좋을 것 같다.
<a href="https://teamzep.notion.site/ZEP-Script-1fe02b2640aa4e74b676b84a074c537f#bb3d2a2adf3d463b94faea9daddc7168">↗️ ZEP Script 배포 가이드</a></p>
<h3 id="cli-배포">CLI 배포</h3>
<p>배포를 하기 전에 먼저 앱을 만들어야 한다. ZEP 로그인 &gt; 나의 앱(Beta) &gt; + 앱 업로드를 눌러 앱을 생성한다. 버튼을 눌렀을 때 아래와 같은 창이 뜨게 된다. 빈칸의 필수 항목을 입력해주면 된다.
<img src="https://velog.velcdn.com/images/dev_grit/post/61e87afd-808e-4aab-8895-d6bb26b3f269/image.png" alt=""></p>
<p>이제 프로젝트 폴더에서 <code>zep-script.json</code> 파일을 수정해주어야 한다. 앱을 생성해준뒤 URL을 확인해보면 아래와 같이 <code>apps/</code> 다음에 생긴 문자열이 바로 앱 아이디이다. Json 파일에 복사해서 쓰면 된다. 
<img src="https://velog.velcdn.com/images/dev_grit/post/92f2ae06-e06b-4c83-aaaa-81d7f0346fac/image.png" alt=""></p>
<p><code>zep-script.json</code> 파일 내용은 아래와 같다.</p>
<pre><code class="language-javascript">{
    &quot;appId&quot;: &quot;&quot;,
    &quot;name&quot;: &quot;Template&quot;,
    &quot;description&quot;: &quot;Template application&quot;,
    &quot;type&quot;: &quot;normal&quot;
}</code></pre>
<p>처음엔 <code>appId</code> 키 값이 빈 문자열일텐데, 여기에 앱 아이디 값을 붙여넣기 하면 된다. 그리고 앱 이름과 앱 설명에 대한 항목은 각각 name, description 값으로 주면 그 값으로 다시 설정된다. type도 설정해야하는데, type의 종류에 대해서는 다음 장에서 설명하겠다.</p>
<p>실제 배포를 위한 명령어는 아래와 같다.</p>
<pre><code class="language-bash">npx zep-script publish</code></pre>
<p>또는</p>
<pre><code class="language-bash">npm run deploy</code></pre>
<h2 id="앱의-타입">앱의 타입</h2>
<p>ZEP의 앱에는 총 세가지의 타입이 존재한다. 노말 앱, 미니게임, 사이드바 앱이다.</p>
<h3 id="노말-앱">노말 앱</h3>
<p>노말 앱은 특정 맵에서만 작동하는 스크립트로, 해당 맵이 실행됨과 동시에 적용된다. 설정은 먼저 앱을 나의 앱에 등록한 다음, 적용하고 싶은 맵의 에디터에서 [맵 에디터] &gt; [좌측 하단 맵 관리자 메뉴] &gt; [맵 설정]으로 들어가 설정할 수 있다.</p>
<h3 id="미니게임">미니게임</h3>
<p>미니게임은 어느 맵에서나 사용할 수 있는 설치형 앱이다.
<img src="https://velog.velcdn.com/images/dev_grit/post/7e25b908-4c1b-4efc-8305-813f19650a98/image.png" alt="">
사용자가 사이드 바의 미니게임 아이콘을 클릭한 다음에 미니게임을 클릭해서 사용할 수 있다. 한 사용자가 미니게임을 시작하면, 맵 전체에 있는 사용자에게 모두 게임이 시작된다. 게임을 시작하게 되면 시작한 자리에 게임 박스가 생기게 되는데, 실행한 사용자가 스페이스 바를 눌러 점프하게 되면, 게임 도중이라도 바로 종료할 수 있다.</p>
<h3 id="사이드바-앱">사이드바 앱</h3>
<p>사이드바 앱은 PC 좌측 사이드바에 아이콘으로 표시되는 앱이다.
<img src="https://velog.velcdn.com/images/dev_grit/post/bd48ffbe-43f5-47a3-848b-a1b037c74c94/image.png" alt="">
사이드바 앱을 개발하고 업로드한 뒤, Owner 권한을 가진 맵의 플레이 화면에서 [사이드바] &gt; [앱 스토어 아이콘] &gt; [나의 앱] 리스트에서 추가해 사용할 수 있다. Owner가 한번 추가해놓으면, 그 스페이스에서는 입장한 모든 사람에게 사이드바 앱이 노출된다.</p>
<h3 id="앱의-종류에-따른-배포-설정">앱의 종류에 따른 배포 설정</h3>
<p>앱의 종류는 배포시 <code>zep-script.json</code> 파일의 type 값으로 명시할 수 있으며 값의 종류에는 <code>&quot;normal&quot;</code>, <code>&quot;minigame&quot;</code> 그리고 <code>&quot;sidebar&quot;</code>가 있다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[
[1] Typescript 설치 환경 구축]]></title>
            <link>https://velog.io/@dev_grit/1-Typescript-%EC%84%A4%EC%B9%98-%ED%99%98%EA%B2%BD-%EA%B5%AC%EC%B6%95</link>
            <guid>https://velog.io/@dev_grit/1-Typescript-%EC%84%A4%EC%B9%98-%ED%99%98%EA%B2%BD-%EA%B5%AC%EC%B6%95</guid>
            <pubDate>Sun, 09 Oct 2022 05:57:59 GMT</pubDate>
            <description><![CDATA[<h1 id="packagejson-생성">package.json 생성</h1>
<p>node.js와 vscode가 설치 되어 있다고 가정하고 시작한다.
새 프로젝트를 만들고 싶은 폴더를 vscode로 열고 터미널을 켜 아래 코드를 실행한다.</p>
<pre><code class="language-bash">npm init -y</code></pre>
<p><code>npm init</code> 을 통해 새 프로젝트의 npm package를 초기화할 수 있다. <code>-y</code> 옵션은 생성할 때 질문을 묻는 경우가 있는 데 이에 대해 모두 yes로 대답하고 실행시킨다는 것이다. 그러면 아래와 같은 파일이 생성된다.
<img src="https://velog.velcdn.com/images/everglow/post/49cbd590-e4ec-4dc3-8f54-529d320e37a5/image.png" alt=""></p>
<h1 id="typescript-설치">typescript 설치</h1>
<p>아래 커맨드를 터미널에 실행시켜주면, typescript를 다운받을 수 있다.</p>
<pre><code class="language-bash">npm i -D typescript</code></pre>
<p><code>i</code> 명령어는 <code>install</code> 과 같은 의미이고, <code>-D</code> 는 <code>--save-dev</code>의 의미와 같은 의미로 패키지가 devDependencies로 등록된다는 의미이다. 이것을 모두 <code>package.json</code> 파일에서 확인할 수 있다.</p>
<h2 id="dependencies-vs-devdependencies">dependencies vs devDependencies</h2>
<h3 id="dependencies">dependencies</h3>
<p>배포용 라이브러리의 목록을 나타낸 곳이다. 일반적으로 그냥 <code>npm install packagename</code> 의 명령어를 통해 설치를 하면 배포용 라이브러리 목록에 뜨게 된다. 배포용 라이브러리는 <code>npm run build</code>로 빌드를 하면 최종 애플리케이션 코드 안에 포함된다.</p>
<pre><code class="language-javascript">// package.json
{
  &quot;dependencies&quot;: {
    &quot;packageName&quot;: &quot;^1.0.0&quot;
  }
}</code></pre>
<h3 id="devdependencies">devDependencies</h3>
<p>개발용 라이브러리의 목록을 나타낸 곳이다. <code>-D</code> 는 <code>--save-dev</code>의 옵션으로 라이브러리를 설치한 경우 여기 목록에 나타난다. 배포용 라이브러리와는 달리 개발용 라이브러리는 빌드하고 배포할 때 애플리케이션 코드에서 빠지게 된다. 따라서 최종 빌드된 어플리케이션에도 라이브러리 기능이 적용되야 하는 경우에는 devDependencies로 설치하면 안된다.</p>
<pre><code class="language-javascript">// package.json
{
  &quot;devDependencies&quot;: {
    &quot;packageName&quot;: &quot;^1.0.0&quot;
  }
}</code></pre>
<p>우리가 typescript를 devDependencies로 설치한 이유는 우리가 build할 때 typescript 자체를 쓸 것이 아니라 코드를 작성한 뒤 자바스크립트로 다시 바꿔 사용할 것이기 대문이다.</p>
<p>만약 배포용 혹은 개발용의 설정을 잘못하여 설치한 경우에는, 그 패키지 이름과 버전명이 나와있는 코드줄을 옮겨주기만 하면 된다. 명령어를 통해 다시 설치를 할 필요는 없다.</p>
<h2 id="설치-완료-시-packagejson-파일">설치 완료 시 package.json 파일</h2>
<p><img src="https://velog.velcdn.com/images/everglow/post/c8175b4b-9a12-450c-b595-bfc2ff92bd2b/image.png" alt=""></p>
<h2 id="소스파일-폴더-생성">소스파일 폴더 생성</h2>
<p>소스파일을 저장할 <code>src</code> 폴더를 하나 생성해준다.</p>
<h1 id="컴파일">컴파일</h1>
<p>우리가 타입스크립트를 사용하여 프로그래밍할 것이긴 하지만, 실제 브라우저에서는 타입스크립트를 알아듣지 못한다. 우리가 개발할 때만 타입스크립트를 사용하고 다 작성한 파일은 일반 자바스크립트로 다시 바꾸어서 사용한다. 이렇게 <strong>타입스크립트에서 자바스크립트 파일로 바꾸는 작업</strong>을 <strong>컴파일</strong>이라고 한다.
컴파일할 때 사용하는 명령어는 <code>tsc</code> 로 터미널에서 아래와 같이 쓰면 컴파일된다.</p>
<pre><code class="language-bash">tsc filename.ts</code></pre>
<p>위의 명령어를 실행시켜주면, <code>filename.ts</code>의 내용을 <code>javascript</code>로 변환한 <code>filename.js</code> 파일이 생성된다. 다만, 우리는 typescript를 전역으로 설치한 것이 아니기 때문에, tsc라는 명령어를 npm script로 만들어줘야 한다.</p>
<h2 id="npm-script-만들기">npm script 만들기</h2>
<p>npm script에 커스텀 명령어를 추가해줌으로써, 우리만의 명령어를 만들수 있다. package.json 파일에 들어가 script옵션을 아래와 같이 수정해준다.</p>
<pre><code class="language-javascript">&quot;scripts&quot;: {
    &quot;build&quot;: &quot;tsc&quot;
},</code></pre>
<p>이렇게 하면 build라는 명령어를 커스텀으로 만들고 실제 실행 명령어는 tsc로 설정할 수 있다. 이런 사용자지정 명령어를 실행하기 위해선 <code>npm run 명령어 이름</code> 을 터미널에 작성하면 된다. 위의 예시에서는 <code>npm run build</code>로 하면 된다.
<img src="https://velog.velcdn.com/images/everglow/post/744a85e7-b283-456a-9efe-b3b7eae727c1/image.gif" alt=""></p>
<p>이 밖에 컴파일된 파일의 저장위치나 변환된 자바스크립트의 버전 등 우리는 다양한 컴파일 규칙을 정해야한다. 이렇게 타입스크립트의 컴파일 방식과 규칙을 작성해줄 수 있는 파일이<code>tsconfig.json</code> 이다.</p>
<h1 id="tscofingjson">tscofing.json</h1>
<h2 id="파일-생성">파일 생성</h2>
<pre><code class="language-bash">touch tsconfig.json</code></pre>
<p>CLI 환경에서 파일을 생성하는 명령어인 <code>touch</code>를 이용하여 tscofig.json 파일을 생성하였다. 꼭 이렇게 하지 않아도 vscode에서 파일 생성 버튼 이름을 눌러 tscofig.json의 이름으로 파일을 생성해줘도 상관없다.</p>
<h2 id="파일-설정">파일 설정</h2>
<p><code>tsconfig.json</code> 파일이 있는 디렉터리가 TypeScript 프로젝트의 루트가 된다. 다시한번 파일의 역할을 설명하자면, <code>tsconfig.json</code> 파일은 프로젝트를 컴파일하는 데 필요한 루트 파일과 컴파일러 옵션을 지정하기 위한 파일이다. 파일 안의 내용은 object 형식으로 적어주면 된다.
<img src="https://velog.velcdn.com/images/everglow/post/06c8d500-f9c7-4de4-b974-51b2ba6218c7/image.png" alt="">
vscode를 사용하면 <code>tsconfig.json</code> 파일에서 사용할 수 있는 다양한 옵션을 자동완성으로 보여준다.</p>
<h2 id="파일에서-사용가능한-옵션">파일에서 사용가능한 옵션</h2>
<p><a href="https://joshua1988.github.io/ts/config/tsconfig.html#%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%84%A4%EC%A0%95-%ED%8C%8C%EC%9D%BC-tsconfig-json">각 옵션별 상세한 의미 (by 캡틴판교)</a></p>
<pre><code class="language-javascript">{
  &quot;include&quot;: [&quot;src&quot;],
  &quot;compilerOptions&quot;: {
    &quot;outDir&quot;: &quot;build&quot;,
    &quot;target&quot;: &quot;ES6&quot;,
    &quot;lib&quot;: [&quot;ES6&quot;, &quot;DOM&quot;]
  }
}</code></pre>
<p>기본 옵션은 위와 같다. <strong>컴파일할 파일의 위치</strong>와 <strong>컴파일된 파일이 저장될 폴더</strong>, <strong>어떤 버전의 자바스크립트로 변환</strong>할 것인지, <strong>컴파일할 때 어떤 라이브러리를 포함</strong>시킬 것인지에 대한 <strong>옵션</strong>을 설정한 것이다. 이 밖에도 자주 쓰는 <code>compilerOptions</code> 의 옵션에는 <code>allowjs</code>, <code>checkJS</code>, <code>noImplicitAny</code> 등이 있다.</p>
<h3 id="-오류-no-inputs-were-found-in-config-file">+) 오류: No inputs were found in config file</h3>
<p><a href="https://stackoverflow.com/questions/41211566/tsconfig-json-buildno-inputs-were-found-in-config-file">stackoverflow 질문글 참고</a>
<code>include</code>로 지정해준 디렉토리 안에 타입스크립트가 없어서 생기는 오류이다. 해당 디렉토리에 컴파일하고 싶은 타입스크립트 파일을 넣어주자.</p>
]]></description>
        </item>
    </channel>
</rss>