<?xml version="1.0" encoding="UTF-8" ?>
<rss
  version="2.0"
  xmlns:atom="http://www.w3.org/2005/Atom"
  xmlns:content="http://purl.org/rss/1.0/modules/content/"
>
  <channel>
    <title>Yanguk Blog</title>
    <link>https://yanguk.github.io</link>
    <description>Software Engineer Blog</description>
    <language>ko</language>

    <lastBuildDate>Sun, 16 Aug 2026 00:00:00 GMT</lastBuildDate>
    <atom:link
      href="https://yanguk.github.io/feed.xml"
      rel="self"
      type="application/rss+xml"
    />

    <item>
        <title>TypeScript, 타입 레벨 프로그래밍</title>
        <link>https://yanguk.github.io/blog/13-typescript-type-level-programing</link>
        <description>TypeScript, 타입 레벨 프로그래밍</description>
        <guid isPermaLink="true">https://yanguk.github.io/blog/13-typescript-type-level-programing</guid>
        <pubDate>Sun, 16 Aug 2026 00:00:00 GMT</pubDate>
        <content:encoded><![CDATA[<h1>TypeScript, 타입 레벨 프로그래밍</h1><blockquote>
<p>2년 전 회사에서 발표한 내용인데 포스팅해 본다.</p>
</blockquote>
<p>나에게는 TypeScript에서 제네릭을 활용한 완벽한 타입 추론에 대해 흥미가 있었던 시절이 있었다. 그때 공부했던 내용이다.</p>
<p>타입스크립트에서 코드 레벨이 아닌 타입 레벨에서의 코딩 방법을 알아보자.</p>
<h2 id="목차">목차<a aria-hidden="true" tabindex="-1" href="#목차"><span class="icon icon-link"></span></a></h2>
<ul>
<li><a href="#typescript%EC%97%90-%EB%8C%80%ED%95%B4%EC%84%9C-%EC%84%9C%EB%A1%A0">TypeScript에 대해서, 서론</a></li>
<li><a href="#%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EB%8A%94-%EC%99%84%EC%A0%84-%ED%8A%9C%EB%A7%81%ED%95%98%EB%8B%A4">타입스크립트는 완전 튜링하다</a></li>
<li><a href="#%EC%A0%9C%EB%84%A4%EB%A6%AD%EA%B3%BC-%EC%A1%B0%EA%B1%B4%EB%AC%B8-%EB%B0%98%EB%B3%B5%EB%AC%B8">제네릭과 조건문, 반복문</a>
<ul>
<li><a href="#%EC%A0%9C%EB%84%A4%EB%A6%AD-generics">제네릭 (Generics)</a></li>
<li><a href="#%EC%A1%B0%EA%B1%B4%EB%AC%B8">조건문</a></li>
<li><a href="#%EB%B0%98%EB%B3%B5%EB%AC%B8">반복문</a></li>
</ul>
</li>
<li><a href="#currying%EC%9D%98-%ED%83%80%EC%9E%85-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0">Currying의 타입 구현하기</a>
<ul>
<li><a href="#%EC%9D%B8%EC%9E%90%EA%B0%80-2%EA%B0%9C%EC%9D%B8-%EC%BC%80%EC%9D%B4%EC%8A%A4">인자가 2개인 케이스</a></li>
<li><a href="#%EC%9D%B8%EC%9E%90%EA%B0%80-3%EA%B0%9C%EC%9D%B8-%EC%BC%80%EC%9D%B4%EC%8A%A4">인자가 3개인 케이스</a></li>
</ul>
</li>
<li><a href="#%EB%A7%88%EB%AC%B4%EB%A6%AC">마무리</a></li>
</ul>
<h2 id="typescript에-대해서-서론">TypeScript에 대해서, 서론<a aria-hidden="true" tabindex="-1" href="#typescript에-대해서-서론"><span class="icon icon-link"></span></a></h2>
<p><code>javascript</code>의 슈퍼셋으로 나온 언어이다.<br>
다들 javascript에서 타입이 없기에 타입스크립트를 적용하게 되면 완벽한 프로그래밍을 할 수 있을 거라 생각을 할 텐데 대부분은 <code>Generics</code>를 마주하게 되면서 좌절을 겪으며 <code>any</code> 타입 도배로 코드를 작성하게 된다.</p>
<p>그러면 타입스크립트를 사용하는 이유가 없어지는 것 같이 보이게 된다.</p>
<p>사실 any가 나쁜 건 아닌데, 이와 관련된 조롱 밈도 많이 돌아다닌다.</p>
<img src="https://yanguk.github.io/type-programing/generic.png" width="400" alt="제네릭 좌절">
<h2 id="타입스크립트는-완전-튜링하다">타입스크립트는 완전 튜링하다<a aria-hidden="true" tabindex="-1" href="#타입스크립트는-완전-튜링하다"><span class="icon icon-link"></span></a></h2>
<blockquote>
<h4 id="튜링-머신이란">튜링 머신이란?<a aria-hidden="true" tabindex="-1" href="#튜링-머신이란"><span class="icon icon-link"></span></a></h4>
<p>수학자 앨런 튜링이 1936년에 제시한 개념으로 계산하는 기계의 일반적인 개념을 설명하기 위<br>
한 가상의 기계이며 오토마타의 일종이다</p>
</blockquote>
<p>튜링하다는 증명은 <a href="https://github.com/microsoft/TypeScript/issues/14833">TS_issues_14833</a>에 설명되어 있다.</p>
<p>우리가 알 수 있는 내용은 아무튼 TS의 타입 시스템은 완전 튜링하니,<br>
타입 시스템 자체를 하나의 프로그래밍 언어처럼 사용할 수 있을 정도로 강력하다고 볼 수 있다.</p>
<p>이제부터는 실제로 제네릭과 조건부 타입 등을 이용해 타입 레벨에서 어떻게 프로그래밍할 수 있는지 살펴보자.</p>
<h2 id="제네릭과-조건문-반복문">제네릭과 조건문, 반복문<a aria-hidden="true" tabindex="-1" href="#제네릭과-조건문-반복문"><span class="icon icon-link"></span></a></h2>
<h3 id="제네릭-generics">제네릭 (Generics)<a aria-hidden="true" tabindex="-1" href="#제네릭-generics"><span class="icon icon-link"></span></a></h3>
<p>제네릭은 선언 시점이 아니라 생성 시점에 타입을 명시하여 하나의 타입만이 아닌 다양한 타입을<br>
사용할 수 있도록 하는 기법이다.</p>
<pre class="language-ts"><code class="language-ts code-highlight"><span class="code-line line-number" line="1"><span class="token keyword">type</span> <span class="token class-name"><span class="token constant">A</span><span class="token operator">&#x3C;</span><span class="token constant">T</span><span class="token operator">></span></span> <span class="token operator">=</span> <span class="token constant">T</span><span class="token punctuation">;</span>
</span><span class="code-line line-number" line="2"><span class="token keyword">type</span> <span class="token class-name"><span class="token constant">B</span></span> <span class="token operator">=</span> <span class="token constant">A</span><span class="token operator">&#x3C;</span><span class="token builtin">number</span><span class="token operator">></span><span class="token punctuation">;</span> <span class="token comment">// number</span>
</span><span class="code-line line-number" line="3"><span class="token keyword">type</span> <span class="token class-name"><span class="token constant">C</span></span> <span class="token operator">=</span> <span class="token constant">A</span><span class="token operator">&#x3C;</span><span class="token builtin">string</span><span class="token operator">></span><span class="token punctuation">;</span> <span class="token comment">// string</span>
</span></code></pre>
<p>제네릭으로 선언된 A를 함수처럼 인자로 타입을 받아서 어떤 타입이 될지를 정할 수가 있다.</p>
<h3 id="조건문">조건문<a aria-hidden="true" tabindex="-1" href="#조건문"><span class="icon icon-link"></span></a></h3>
<p>그렇다면 타입 레벨에서의 조건문은 어떻게 사용하는지 보자.<br>
여기서 주요 키워드는 <code>extends</code> 이다.</p>
<pre class="language-ts"><code class="language-ts code-highlight"><span class="code-line line-number" line="1"><span class="token keyword">type</span> <span class="token class-name">User</span> <span class="token punctuation">{</span>
</span><span class="code-line line-number" line="2">  name<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span>
</span><span class="code-line line-number" line="3">  email<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span>
</span><span class="code-line line-number" line="4"><span class="token punctuation">}</span>
</span><span class="code-line line-number" line="5">
</span><span class="code-line line-number" line="6"><span class="token keyword">type</span> <span class="token class-name">IsHasField<span class="token operator">&#x3C;</span><span class="token constant">T</span><span class="token punctuation">,</span> FieldName <span class="token keyword">extends</span> <span class="token builtin">string</span><span class="token operator">></span></span> <span class="token operator">=</span> FieldName <span class="token keyword">extends</span> <span class="token keyword">keyof</span> <span class="token constant">T</span>
</span><span class="code-line line-number" line="7">  <span class="token operator">?</span> <span class="token boolean">true</span>
</span><span class="code-line line-number" line="8">  <span class="token operator">:</span> <span class="token boolean">false</span><span class="token punctuation">;</span>
</span><span class="code-line line-number" line="9">
</span><span class="code-line line-number" line="10"><span class="token keyword">type</span> <span class="token class-name">HasEmailField<span class="token operator">&#x3C;</span><span class="token constant">T</span><span class="token operator">></span></span> <span class="token operator">=</span> IsHasField<span class="token operator">&#x3C;</span><span class="token constant">T</span><span class="token punctuation">,</span><span class="token string">'email'</span><span class="token operator">></span>
</span><span class="code-line line-number" line="11"><span class="token keyword">type</span> <span class="token class-name">UseHasEmailField</span> <span class="token operator">=</span> HasEmailField<span class="token operator">&#x3C;</span>User<span class="token operator">></span> <span class="token comment">// true</span>
</span></code></pre>
<p>해당 키워드의 역할은 3가지가 있다.</p>
<ol>
<li>조건문 역할 (if)</li>
<li>타입 제한</li>
<li>타입 확장</li>
</ol>
<p>예시에서는 if 문의 조건문 역할을 수행하여서 FieldName에 있는 키일 경우 true로 타입이 추론되게 되어있다.</p>
<h3 id="반복문">반복문<a aria-hidden="true" tabindex="-1" href="#반복문"><span class="icon icon-link"></span></a></h3>
<p>타입 시스템에서는 for문이나 while문 같은 문법이 없다. 다만 재귀는 가능하여서 재귀로 반복문을 수행해야 한다.</p>
<p>여기서 중요 키워드는 <code>infer</code> (타입 변수 할당) 이다.</p>
<pre class="language-ts"><code class="language-ts code-highlight"><span class="code-line line-number" line="1"><span class="token keyword">type</span> <span class="token class-name">IsAllNumber<span class="token operator">&#x3C;</span><span class="token constant">T</span> <span class="token keyword">extends</span> <span class="token builtin">unknown</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">></span></span> <span class="token operator">=</span> <span class="token constant">T</span> <span class="token keyword">extends</span> <span class="token punctuation">[</span><span class="token keyword">infer</span> First<span class="token punctuation">,</span> <span class="token operator">...</span><span class="token keyword">infer</span> Rest<span class="token punctuation">]</span>
</span><span class="code-line line-number" line="2">  <span class="token operator">?</span> First <span class="token keyword">extends</span> <span class="token class-name"><span class="token builtin">number</span></span>
</span><span class="code-line line-number" line="3">    <span class="token operator">?</span> IsAllNumber<span class="token operator">&#x3C;</span>Rest<span class="token operator">></span>
</span><span class="code-line line-number" line="4">    <span class="token operator">:</span> <span class="token boolean">false</span>
</span><span class="code-line line-number" line="5">  <span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">;</span>
</span><span class="code-line line-number" line="6">
</span><span class="code-line line-number" line="7"><span class="token keyword">type</span> <span class="token class-name"><span class="token constant">A</span></span> <span class="token operator">=</span> IsAllNumber<span class="token operator">&#x3C;</span><span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">]</span><span class="token operator">></span><span class="token punctuation">;</span> <span class="token comment">// true</span>
</span><span class="code-line line-number" line="8"><span class="token keyword">type</span> <span class="token class-name"><span class="token constant">B</span></span> <span class="token operator">=</span> IsAllNumber<span class="token operator">&#x3C;</span><span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token string">"knowre"</span><span class="token punctuation">]</span><span class="token operator">></span><span class="token punctuation">;</span> <span class="token comment">// false</span>
</span></code></pre>
<p>위 예시를 보면 <code>extends</code>와 <code>infer</code>를 사용하여서 첫 번째 요소를 변수에 할당한 이후에 그 값이 number인지 비교를 하고, 맞으면 나머지 요소들을 다시 재귀로 돌려주면서 다음 요소를 순회하고 있다.</p>
<p><code>infer</code>로 요소 하나를 판별하고 나머지 요소로 재귀를 돌림으로써 반복문을 수행하는 것이다.</p>
<h2 id="currying의-타입-구현하기">Currying의 타입 구현하기<a aria-hidden="true" tabindex="-1" href="#currying의-타입-구현하기"><span class="icon icon-link"></span></a></h2>
<p>위 내용을 바탕으로 Currying 타입을 구현해보자.<br>
이걸 할 수 있으면 웬만한 타입은 다 만들 수 있을 것이다.</p>
<pre class="language-ts"><code class="language-ts code-highlight"><span class="code-line line-number" line="1"><span class="token comment">/** Currying 이란..? */</span>
</span><span class="code-line line-number" line="2"><span class="token keyword">function</span> <span class="token function">curry</span><span class="token punctuation">(</span>fn<span class="token punctuation">)</span> <span class="token punctuation">{</span>
</span><span class="code-line line-number" line="3">  <span class="token keyword">return</span> <span class="token keyword">function</span> <span class="token function">curried</span><span class="token punctuation">(</span><span class="token operator">...</span>params<span class="token punctuation">)</span> <span class="token punctuation">{</span>
</span><span class="code-line line-number" line="4">    <span class="token keyword">if</span> <span class="token punctuation">(</span>fn<span class="token punctuation">.</span>length <span class="token operator">===</span> params<span class="token punctuation">.</span>length<span class="token punctuation">)</span> <span class="token punctuation">{</span>
</span><span class="code-line line-number" line="5">      <span class="token keyword">return</span> <span class="token function">fn</span><span class="token punctuation">(</span><span class="token operator">...</span>params<span class="token punctuation">)</span><span class="token punctuation">;</span>
</span><span class="code-line line-number" line="6">    <span class="token punctuation">}</span>
</span><span class="code-line line-number" line="7">
</span><span class="code-line line-number" line="8">    <span class="token keyword">return</span> <span class="token function">curried</span><span class="token punctuation">.</span><span class="token function">bind</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> <span class="token operator">...</span>params<span class="token punctuation">)</span><span class="token punctuation">;</span>
</span><span class="code-line line-number" line="9">  <span class="token punctuation">}</span><span class="token punctuation">;</span>
</span><span class="code-line line-number" line="10"><span class="token punctuation">}</span>
</span><span class="code-line line-number" line="11">
</span><span class="code-line line-number" line="12"><span class="token keyword">const</span> <span class="token function-variable function">add</span> <span class="token operator">=</span> <span class="token punctuation">(</span>a<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">,</span> b<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">)</span> <span class="token operator">=></span> a <span class="token operator">+</span> b<span class="token punctuation">;</span>
</span><span class="code-line line-number" line="13">
</span><span class="code-line line-number" line="14"><span class="token keyword">const</span> curriedAdd <span class="token operator">=</span> <span class="token function">curry</span><span class="token punctuation">(</span>add<span class="token punctuation">)</span><span class="token punctuation">;</span>
</span><span class="code-line line-number" line="15">
</span><span class="code-line line-number" line="16"><span class="token keyword">const</span> addFive <span class="token operator">=</span> <span class="token function">curriedAdd</span><span class="token punctuation">(</span><span class="token number">5</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</span><span class="code-line line-number" line="17">
</span><span class="code-line line-number" line="18"><span class="token builtin">console</span><span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token function">curriedAdd</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 5</span>
</span><span class="code-line line-number" line="19"><span class="token builtin">console</span><span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token function">curriedAdd</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token number">4</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 6</span>
</span><span class="code-line line-number" line="20"><span class="token builtin">console</span><span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token function">addFive</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 8</span>
</span></code></pre>
<p>일단 커링(Currying)에 대해서 설명하자면, 함수를 받아서 함수를 리턴하는데, 그 함수는 모든 파라미터가 다 전달된 시점에서 실행이 되게 하는 함수이다. 일부 파라미터만 전달하게 되면 다시 함수를 리턴한다.</p>
<p>함수의 실행 시점을 뒤로 미룰 수가 있어서 함수형 프로그래밍에서는 필수적으로 사용하게 된다.</p>
<p>이걸 타입으로 구현한다면 <code>curriedAdd</code> 함수는 <code>함수 or number</code> 를 리턴하는 타입일 것이고, <code>addFive</code> 함수는 number를 리턴하는 함수 타입일 것이다.</p>
<p>본격적으로 구현하기 전에 다른 유틸 타입들을 먼저 살펴보자.</p>
<pre class="language-ts"><code class="language-ts code-highlight"><span class="code-line line-number" line="1"><span class="token keyword">type</span> <span class="token class-name">AnyFn</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token operator">...</span>params<span class="token operator">:</span> <span class="token builtin">any</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token builtin">any</span><span class="token punctuation">;</span>
</span><span class="code-line line-number" line="2">
</span><span class="code-line line-number" line="3"><span class="token keyword">type</span> <span class="token class-name">Parameters<span class="token operator">&#x3C;</span><span class="token constant">T</span> <span class="token keyword">extends</span> AnyFn<span class="token operator">></span></span> <span class="token operator">=</span> <span class="token constant">T</span> <span class="token keyword">extends</span> <span class="token punctuation">(</span><span class="token operator">...</span>params<span class="token operator">:</span> <span class="token keyword">infer</span> <span class="token constant">P</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token builtin">any</span>
</span><span class="code-line line-number" line="4">  <span class="token operator">?</span> <span class="token constant">P</span>
</span><span class="code-line line-number" line="5">  <span class="token operator">:</span> <span class="token builtin">never</span><span class="token punctuation">;</span>
</span><span class="code-line line-number" line="6"><span class="token comment">// type A0 = Parameters&#x3C;(a: string, b: number) => boolean></span>
</span><span class="code-line line-number" line="7"><span class="token comment">// [a: string, b: number]</span>
</span><span class="code-line line-number" line="8">
</span><span class="code-line line-number" line="9"><span class="token keyword">type</span> <span class="token class-name">ReturnType<span class="token operator">&#x3C;</span><span class="token constant">T</span> <span class="token keyword">extends</span> AnyFn<span class="token operator">></span></span> <span class="token operator">=</span> <span class="token constant">T</span> <span class="token keyword">extends</span> <span class="token punctuation">(</span><span class="token operator">...</span>params<span class="token operator">:</span> <span class="token builtin">any</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">infer</span> <span class="token constant">R</span>
</span><span class="code-line line-number" line="10">  <span class="token operator">?</span> <span class="token constant">R</span>
</span><span class="code-line line-number" line="11">  <span class="token operator">:</span> <span class="token builtin">never</span><span class="token punctuation">;</span>
</span><span class="code-line line-number" line="12"><span class="token comment">// type A1 = ReturnType&#x3C;() => 'returnType'></span>
</span><span class="code-line line-number" line="13"><span class="token comment">// 'returnType'</span>
</span><span class="code-line line-number" line="14">
</span><span class="code-line line-number" line="15"><span class="token keyword">type</span> <span class="token class-name">Head<span class="token operator">&#x3C;</span><span class="token constant">T</span> <span class="token keyword">extends</span> <span class="token builtin">any</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">></span></span> <span class="token operator">=</span> <span class="token constant">T</span> <span class="token keyword">extends</span> <span class="token punctuation">[</span><span class="token keyword">infer</span> First<span class="token punctuation">,</span> <span class="token operator">...</span><span class="token builtin">any</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">]</span> <span class="token operator">?</span> First <span class="token operator">:</span> <span class="token builtin">never</span><span class="token punctuation">;</span>
</span><span class="code-line line-number" line="16"><span class="token comment">// type A2 = Head&#x3C;[1, 2, 3, 4, 5]></span>
</span><span class="code-line line-number" line="17"><span class="token comment">// 1</span>
</span><span class="code-line line-number" line="18">
</span><span class="code-line line-number" line="19"><span class="token keyword">type</span> <span class="token class-name">Tail<span class="token operator">&#x3C;</span><span class="token constant">T</span> <span class="token keyword">extends</span> <span class="token builtin">any</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">></span></span> <span class="token operator">=</span> <span class="token constant">T</span> <span class="token keyword">extends</span> <span class="token punctuation">[</span><span class="token builtin">any</span><span class="token punctuation">,</span> <span class="token operator">...</span><span class="token keyword">infer</span> Rest<span class="token punctuation">]</span> <span class="token operator">?</span> Rest <span class="token operator">:</span> <span class="token builtin">never</span><span class="token punctuation">;</span>
</span><span class="code-line line-number" line="20"><span class="token comment">// type A3 = Tail&#x3C;[1, 2, 3, 4, 5]></span>
</span><span class="code-line line-number" line="21"><span class="token comment">// [2, 3, 4, 5]</span>
</span><span class="code-line line-number" line="22">
</span><span class="code-line line-number" line="23"><span class="token keyword">type</span> <span class="token class-name">Length<span class="token operator">&#x3C;</span><span class="token constant">T</span> <span class="token keyword">extends</span> <span class="token builtin">any</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">></span></span> <span class="token operator">=</span> <span class="token constant">T</span><span class="token punctuation">[</span><span class="token string">"length"</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
</span><span class="code-line line-number" line="24"><span class="token comment">// type A4 = Length&#x3C;[1, 2, 3, 4, 5]></span>
</span><span class="code-line line-number" line="25"><span class="token comment">// 5</span>
</span></code></pre>
<p>타입 선언부 아래 주석에서 첫 번째 줄은 사용 예시이고, 두 번째 줄은 해당 타입의 모습이다.<br>
천천히 읽어보면 이해가 될 것이다.</p>
<p>이제 본격적으로 커링을 만들어 볼 건데, 인자가 2개인 케이스부터 만들어보고 3개 이상인 케이스를 만들면서 커링을 완성 시킬 것이다.</p>
<h3 id="인자가-2개인-케이스">인자가 2개인 케이스<a aria-hidden="true" tabindex="-1" href="#인자가-2개인-케이스"><span class="icon icon-link"></span></a></h3>
<p>대강 인터페이스가 어떻게 될지 생각해보자.</p>
<pre class="language-ts"><code class="language-ts code-highlight"><span class="code-line line-number" line="1"><span class="token keyword">const</span> <span class="token function-variable function">add</span> <span class="token operator">=</span> <span class="token punctuation">(</span>a<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">,</span> b<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">)</span> <span class="token operator">=></span> a <span class="token operator">+</span> b<span class="token punctuation">;</span>
</span><span class="code-line line-number" line="2">
</span><span class="code-line line-number" line="3"><span class="token keyword">const</span> curry <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token keyword">as</span> Curry<span class="token punctuation">;</span>
</span><span class="code-line line-number" line="4">
</span><span class="code-line line-number" line="5"><span class="token comment">/**
</span></span><span class="code-line line-number" line="6"><span class="token comment"> * 기대하는 타입 값
</span></span><span class="code-line line-number" line="7"><span class="token comment"> * `((a, b) => a + b) &#x26; ((a) => (b) => a + b)`
</span></span><span class="code-line line-number" line="8"><span class="token comment"> */</span>
</span><span class="code-line line-number" line="9"><span class="token keyword">const</span> curried <span class="token operator">=</span> <span class="token function">curry</span><span class="token punctuation">(</span>add<span class="token punctuation">)</span><span class="token punctuation">;</span>
</span><span class="code-line line-number" line="10">
</span><span class="code-line line-number" line="11"><span class="token keyword">const</span> addFive <span class="token operator">=</span> <span class="token function">curried</span><span class="token punctuation">(</span><span class="token number">5</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// (b: number) => a + b</span>
</span><span class="code-line line-number" line="12"><span class="token keyword">const</span> eight <span class="token operator">=</span> <span class="token function">curried</span><span class="token punctuation">(</span><span class="token number">5</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// number</span>
</span></code></pre>
<p>curried 된 함수 값은 두 가지 타입이 같이 존재할 것이다.</p>
<blockquote>
<p>((a, b) => a + b) &#x26; ((a) => (b) => a + b)</p>
</blockquote>
<p>일단 전달 받은 함수에서 리턴값과 파라미터값을 추출해내서 새로운 타입으로 만들어야 한다.</p>
<pre class="language-ts"><code class="language-ts code-highlight"><span class="code-line line-number" line="1"><span class="token keyword">type</span> <span class="token class-name">Curry<span class="token operator">&#x3C;</span>Fn <span class="token keyword">extends</span> AnyFn<span class="token operator">></span></span> <span class="token operator">=</span> Curried<span class="token operator">&#x3C;</span>Parameters<span class="token operator">&#x3C;</span>Fn<span class="token operator">></span><span class="token punctuation">,</span> ReturnType<span class="token operator">&#x3C;</span>Fn<span class="token operator">>></span><span class="token punctuation">;</span>
</span></code></pre>
<p>제네릭으로 받은 값에서 파라미터와 리턴값을 추출해내서 새로운 타입 Curried로 넘겨준다. 이제 Curried를 만들어보자.</p>
<pre class="language-ts"><code class="language-ts code-highlight"><span class="code-line line-number" line="1"><span class="token keyword">type</span> <span class="token class-name">Curried<span class="token operator">&#x3C;</span><span class="token constant">P</span> <span class="token keyword">extends</span> <span class="token builtin">any</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token constant">R</span><span class="token operator">></span></span> <span class="token operator">=</span> <span class="token constant">P</span> <span class="token keyword">extends</span> <span class="token punctuation">[</span><span class="token keyword">infer</span> Head<span class="token punctuation">,</span> <span class="token operator">...</span><span class="token keyword">infer</span> Rest<span class="token punctuation">]</span>
</span><span class="code-line line-number" line="2">  <span class="token operator">?</span> HasTail<span class="token operator">&#x3C;</span><span class="token constant">P</span><span class="token operator">></span> <span class="token keyword">extends</span> <span class="token class-name"><span class="token boolean">true</span></span>
</span><span class="code-line line-number" line="3">    <span class="token operator">?</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>a<span class="token operator">:</span> Head<span class="token punctuation">)</span> <span class="token operator">=></span> Curried<span class="token operator">&#x3C;</span>Rest<span class="token punctuation">,</span> <span class="token constant">R</span><span class="token operator">></span><span class="token punctuation">)</span> <span class="token operator">&#x26;</span> <span class="token comment">// 커링된 함수</span>
</span><span class="code-line line-number" line="4">        <span class="token punctuation">(</span><span class="token punctuation">(</span>a<span class="token operator">:</span> Head<span class="token punctuation">,</span> <span class="token operator">...</span>b<span class="token operator">:</span> Rest<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token constant">R</span><span class="token punctuation">)</span> <span class="token comment">// 커링 없이 모든 인자 받아서 실행</span>
</span><span class="code-line line-number" line="5">    <span class="token operator">:</span> <span class="token punctuation">(</span>a<span class="token operator">:</span> Head<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token constant">R</span> <span class="token comment">// head 하나만 넘어옴</span>
</span><span class="code-line line-number" line="6">  <span class="token operator">:</span> <span class="token builtin">never</span><span class="token punctuation">;</span>
</span></code></pre>
<p>넘겨 받은 파라미터값을 순회하면서 타입을 조합해 줘야 한다. 따라서 extends와 infer를 사용해 순회할 수 있는 준비를 하고, 무한 재귀가 되면 안 되니까 마지막 조건을 정의해 준다.<br>
그것은 바로 파라미터를 모두 순회했을 때이다. 5번 줄의 <code>(a: Head) => R</code> 이 부분이다.</p>
<p>재귀는 <code>HasTail</code>로 마지막인지 판단하고, 두 개의 타입을 묶어서 만든다. (3, 4번 줄)</p>
<h3 id="인자가-3개인-케이스">인자가 3개인 케이스<a aria-hidden="true" tabindex="-1" href="#인자가-3개인-케이스"><span class="icon icon-link"></span></a></h3>
<p>여기는 좀 더 어렵다. 순회하면서 모든 파라미터를 들고 가야 하면서, 마지막 지점을 알 수 있어야 한다.<br>
인터페이스를 생각해보자.</p>
<pre class="language-ts"><code class="language-ts code-highlight"><span class="code-line line-number" line="1"><span class="token keyword">const</span> <span class="token function-variable function">foo</span> <span class="token operator">=</span> <span class="token punctuation">(</span>a<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">,</span> b<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">,</span> c<span class="token operator">:</span> <span class="token builtin">boolean</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token boolean">true</span><span class="token punctuation">;</span>
</span><span class="code-line line-number" line="2">
</span><span class="code-line line-number" line="3"><span class="token comment">/**
</span></span><span class="code-line line-number" line="4"><span class="token comment"> * 기대하는 타입값
</span></span><span class="code-line line-number" line="5"><span class="token comment"> * (
</span></span><span class="code-line line-number" line="6"><span class="token comment"> *   (a: number) => (
</span></span><span class="code-line line-number" line="7"><span class="token comment"> *     (b: string) => (c: boolean) => boolean) &#x26;
</span></span><span class="code-line line-number" line="8"><span class="token comment"> *     ((b: string, c: boolean) => boolean)
</span></span><span class="code-line line-number" line="9"><span class="token comment"> *   )
</span></span><span class="code-line line-number" line="10"><span class="token comment"> * ) &#x26;
</span></span><span class="code-line line-number" line="11"><span class="token comment"> * ((a: number, b: string) => (c: boolean) => boolean) &#x26;
</span></span><span class="code-line line-number" line="12"><span class="token comment"> * ((a: number, b: string, c: boolean) => boolean)
</span></span><span class="code-line line-number" line="13"><span class="token comment"> */</span>
</span><span class="code-line line-number" line="14"><span class="token keyword">const</span> curriedFoo <span class="token operator">=</span> <span class="token function">curry</span><span class="token punctuation">(</span>foo<span class="token punctuation">)</span><span class="token punctuation">;</span>
</span><span class="code-line line-number" line="15">
</span><span class="code-line line-number" line="16"><span class="token keyword">type</span> <span class="token class-name">Curry<span class="token operator">&#x3C;</span>Fn <span class="token keyword">extends</span> AnyFn<span class="token operator">></span></span> <span class="token operator">=</span> Curried<span class="token operator">&#x3C;</span>Parameters<span class="token operator">&#x3C;</span>Fn<span class="token operator">></span><span class="token punctuation">,</span> ReturnType<span class="token operator">&#x3C;</span>Fn<span class="token operator">>></span><span class="token punctuation">;</span>
</span></code></pre>
<p>일단 앞의 Curry와 동일하게 해주고 새로운 Curried를 구현한다.</p>
<pre class="language-ts"><code class="language-ts code-highlight"><span class="code-line line-number" line="1"><span class="token keyword">type</span> <span class="token class-name">Curried<span class="token operator">&#x3C;</span>
</span></span><span class="code-line line-number" line="2"><span class="token class-name">  <span class="token constant">P</span> <span class="token keyword">extends</span> <span class="token builtin">any</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
</span></span><span class="code-line line-number" line="3"><span class="token class-name">  <span class="token constant">R</span><span class="token punctuation">,</span>
</span></span><span class="code-line line-number" line="4"><span class="token class-name">  PrevParams <span class="token keyword">extends</span> <span class="token builtin">any</span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token comment">// 모든 params를 저장하기 위해서 앞전에 커링된 인자를 보관하기 위한 값</span>
</span></span><span class="code-line line-number" line="5"><span class="token class-name"><span class="token operator">></span></span> <span class="token operator">=</span> <span class="token constant">P</span> <span class="token keyword">extends</span> <span class="token punctuation">[</span><span class="token keyword">infer</span> Head<span class="token punctuation">,</span> <span class="token operator">...</span><span class="token keyword">infer</span> Tail<span class="token punctuation">]</span>
</span><span class="code-line line-number" line="6">  <span class="token operator">?</span> HasTail<span class="token operator">&#x3C;</span><span class="token constant">P</span><span class="token operator">></span> <span class="token keyword">extends</span> <span class="token class-name"><span class="token boolean">true</span></span>
</span><span class="code-line line-number" line="7">    <span class="token operator">?</span> <span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token operator">...</span>params<span class="token operator">:</span> <span class="token punctuation">[</span><span class="token operator">...</span>PrevParams<span class="token punctuation">,</span> Head<span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token operator">=></span> Curried<span class="token operator">&#x3C;</span>Tail<span class="token punctuation">,</span> <span class="token constant">R</span><span class="token operator">></span><span class="token punctuation">)</span> <span class="token operator">&#x26;</span> <span class="token comment">// 커링된 함수를 리턴.</span>
</span><span class="code-line line-number" line="8">        Curried<span class="token operator">&#x3C;</span>Tail<span class="token punctuation">,</span> <span class="token constant">R</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token operator">...</span>PrevParams<span class="token punctuation">,</span> Head<span class="token punctuation">]</span><span class="token operator">></span> <span class="token comment">// 커링 없이 실행한 함수 리턴</span>
</span><span class="code-line line-number" line="9">    <span class="token operator">:</span> <span class="token punctuation">(</span><span class="token operator">...</span>params<span class="token operator">:</span> <span class="token punctuation">[</span><span class="token operator">...</span>PrevParams<span class="token punctuation">,</span> Head<span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token constant">R</span> <span class="token comment">// Arg가 1개 남았을 경우 모든 Params를 넣어서 실행하는 함수</span>
</span><span class="code-line line-number" line="10">  <span class="token operator">:</span> <span class="token builtin">never</span><span class="token punctuation">;</span>
</span></code></pre>
<p>앞에 인자 2개일 때와 다르게 제네릭을 하나 더 추가하여서 (4번 줄) 순회할 때 모든 파라미터를 끌고 간다.</p>
<p>그리고 그 뒤는 비슷하다. 마지막 부분(9번 줄)에는 모든 파라미터를 넣어서 실행하는 함수 리턴으로 마무리하고, 순회 부분에서는 두 가지 타입을 만들어서 리턴한다. (7~8번 줄)</p>
<p>커링 없이 실행한 함수는 모든 파라미터를 들고 마지막 지점까지 갈 수 있도록<br>
<code>Curried&#x3C;Tail, R, [...PrevParams, Head]></code>(8번 줄)으로 만들어주고,</p>
<p>커링된 함수 부분은 값을 소비하고 나머지 파라미터값으로 새로운 커링 타입을 만든다.<br>
<code>((...params: [...PrevParams, Head]) => Curried&#x3C;Tail, R>)</code>(7번 줄)</p>
<h2 id="마무리">마무리<a aria-hidden="true" tabindex="-1" href="#마무리"><span class="icon icon-link"></span></a></h2>
<p>이해가 안 된다면 넘어가도 좋다. 타입 레벨단 코딩은 가독성이 무척 안 좋기 때문에 이해하려고 노력할 필요는 없는 것 같다.</p>
<p>그리고 제네릭 잘 쓰면 좋긴 하다만, 너무 복잡해지는 경우가 있고 가독성도 매우 떨어진다. 실제 로직과는 아무 상관이 없는데 타입 프로그래밍만 주구장창 하고 있는 모습을 보면 뭐하고 있나 싶기도 하다.</p>
<p>제네릭을 적재적소에 잘 사용하면서 단순하고 심플하게 하는 게 좋을 것 같다. 비록 any를 쓰더라도.</p>]]></content:encoded>
      </item><item>
        <title>효율적으로, 편하게 일하기 (feat. 도구)</title>
        <link>https://yanguk.github.io/blog/12-work-flow</link>
        <description>효율적으로, 편하게 일하기 (feat. 도구)</description>
        <guid isPermaLink="true">https://yanguk.github.io/blog/12-work-flow</guid>
        <pubDate>Mon, 10 Aug 2026 00:00:00 GMT</pubDate>
        <content:encoded><![CDATA[<h1>효율적으로, 편하게 일하기 (feat. 도구)</h1><p>개발 업무를 하게 되면서 내 손목도 지키고 편안하게 업무를 수행할 수 있도록 도구들을 많이 갈고 닦았다. 그 과정에 대해 공유한다.</p>
<h2 id="목차">목차<a aria-hidden="true" tabindex="-1" href="#목차"><span class="icon icon-link"></span></a></h2>
<ul>
<li><a href="#1-gui-%ED%99%98%EA%B2%BD%EC%97%90%EC%84%9C-cli-tui-%ED%99%98%EA%B2%BD-%EC%9C%84%EC%A3%BC%EC%97%90%EC%84%9C-%EC%9E%91%EC%97%85">1. GUI 환경에서 CLI, TUI 환경 위주에서 작업</a></li>
<li><a href="#2-dotfiles-%EA%B5%AC%EC%84%B1">2. Dotfiles 구성</a></li>
<li><a href="#3-keyboard-layout-%EC%A0%84%ED%99%98">3. Keyboard Layout 전환</a></li>
<li><a href="#%EB%A7%88%EB%AC%B4%EB%A6%AC">마무리</a></li>
</ul>
<h2 id="1-gui-환경에서-cli-tui-환경-위주에서-작업">1. GUI 환경에서 CLI, TUI 환경 위주에서 작업<a aria-hidden="true" tabindex="-1" href="#1-gui-환경에서-cli-tui-환경-위주에서-작업"><span class="icon icon-link"></span></a></h2>
<p>때는 취직 후 개발 일을 시작한지 3개월 되었을 무렵이였다. 당시 vscode로 코딩을 하고 있었다.</p>
<p>마우스를 사용하면서 코드와 파일을 이동했는데, 오른손이 매번 키보드와 마우스 사이를 오가는 것이 불편혔고 손목에도 부담이 갔다.</p>
<p>이 문제를 개선하고자 nvim을 사용하기 시작했고, 자연스럽게 터미널에서 주로 작업을하게 되었다. 그 과정에서 CLI환겅에도 익숙해졌다.</p>
<p>현재 AI 시대에 와서는 CLI 환경에 익숙한 작업 방식이 더 큰 장점으로 다가오고 있다.</p>
<p>GUI에서 CLI로 전환한 몇 가지 툴들을 소개하겠다.</p>
<table>
<thead>
<tr>
<th>용도</th>
<th>GUI</th>
<th>CLI or TUI</th>
</tr>
</thead>
<tbody>
<tr>
<td>에디터</td>
<td>VS Code</td>
<td>Neovim</td>
</tr>
<tr>
<td>파일 탐색기</td>
<td>GUI 파일 탐색기</td>
<td>Yazi</td>
</tr>
<tr>
<td>이력서 작성</td>
<td>Figma</td>
<td>Typst</td>
</tr>
<tr>
<td>Git</td>
<td>GUI Git 클라이언트</td>
<td>lazygit, jjui</td>
</tr>
<tr>
<td>HTTP API</td>
<td>postman</td>
<td>kulala.nvim</td>
</tr>
<tr>
<td>프로세스 관리</td>
<td>Activity Monitor</td>
<td>btom</td>
</tr>
<tr>
<td>Docker</td>
<td>Docker Desktop</td>
<td>lazydocker</td>
</tr>
</tbody>
</table>
<h2 id="2-dotfiles-구성">2. Dotfiles 구성<a aria-hidden="true" tabindex="-1" href="#2-dotfiles-구성"><span class="icon icon-link"></span></a></h2>
<p>1번을 거지게 되면 CLI 툴들의 설정 파일이 여러 개 생기게 된다.<br>
이와 더불어 개발 환경의 초기 설정 구조가 잡히게 되는데 이를 관리해 줄 패키지 매니저가 필요하게 된다.</p>
<p>필자는 nix로 패키지 관리하고, 설정 파일들을 <a href="https://github.com/Yanguk/dotfiles">dotfiles 레포</a>에 저장하고 있다.</p>
<h2 id="3-keyboard-layout-전환">3. Keyboard Layout 전환<a aria-hidden="true" tabindex="-1" href="#3-keyboard-layout-전환"><span class="icon icon-link"></span></a></h2>
<p>업무 중 타이핑을 하면서도 손목이 꺽이면서 불편함이 발생하였고, 레이아웃도 변경을 하였다.</p>
<p>일단 키보드에 손을 두는 방식을 <a href="https://colemakmods.github.io/ergonomic-mods/angle.html">Angle mod</a> 로 변경 하였다.</p>
<p>손목이 꺽이지 않아 너무 편하였고 이에 맞는 최적화된 레이아웃으로 변경하였다.</p>
<p><code>QWER -> Colemak -> Colemak-dh -> Canary</code></p>
<p>현재 영문은 <strong>Canary</strong>에 정착을 한 상태이고,</p>
<p><code>두벌식 -> 세벌식 390 -> 신 세벌식 P2</code></p>
<p>한글은 <strong>신 세벌식 P2</strong>에 정착한 상태이다.</p>
<h2 id="마무리">마무리<a aria-hidden="true" tabindex="-1" href="#마무리"><span class="icon icon-link"></span></a></h2>
<p>위 여정은 앞으로도 계속될 것 이며, 과거에 비해서 지금 개발 환경과 일하는 방식은 아주 편해졌다.</p>
<p>경험담이나 그 과정에서 겪은 힘든 점 등을 내 관점에서 좀 더 서술하고 싶지만 미니멀리스트답게 담백하게 마무리한다.</p>]]></content:encoded>
      </item><item>
        <title>markdown 블로그 개발하기</title>
        <link>https://yanguk.github.io/blog/7-markdown-blog</link>
        <description>markdown 블로그 개발하기</description>
        <guid isPermaLink="true">https://yanguk.github.io/blog/7-markdown-blog</guid>
        <pubDate>Wed, 21 Jan 2026 00:00:00 GMT</pubDate>
        <content:encoded><![CDATA[<h1>markdown 블로그 개발하기</h1><p>지금 블로그는 nextjs의 mdx지원 기능을 이용해서 구현되었고, vercel로 호스팅중 이다.<br>
블로그 리모델링할 겸 해서 개선하고 싶은 점이 있어서 구조좀 변경 할려고 한다.<br>
그런데 오랜만에 할려고 하니까 어떤 구조인지 기억이 안나서 포스팅을 해본다.</p>
<h2 id="netxjs로-블로그-만들기">Netxjs로 블로그 만들기<a aria-hidden="true" tabindex="-1" href="#netxjs로-블로그-만들기"><span class="icon icon-link"></span></a></h2>
<p><a href="https://nextjs.org/docs/app/guides/mdx">nextjs문서</a></p>
<h3 id="feature-항목">Feature 항목<a aria-hidden="true" tabindex="-1" href="#feature-항목"><span class="icon icon-link"></span></a></h3>
<ul>
<li>mdx 파싱하여 html으로 변환 해서 페이지로 만들기
<ul>
<li><code>remark</code><sup><a href="#user-content-fn-1" id="user-content-fnref-1" data-footnote-ref aria-describedby="footnote-label">1</a></sup>, <code>rehypo</code><sup><a href="#user-content-fn-2" id="user-content-fnref-2" data-footnote-ref aria-describedby="footnote-label">2</a></sup> 를 사용해서 변환 과정을 거친다.
<blockquote>
<p>mdx -> remark -> rehype -> html</p>
</blockquote>
</li>
<li>마크다운의 코드 블럭 스타일 처리 (<code>prism</code> 사용)</li>
</ul>
</li>
</ul>
<ul>
<li>sitemap<sup><a href="#user-content-fn-3" id="user-content-fnref-3" data-footnote-ref aria-describedby="footnote-label">3</a></sup> 만들기
<ul>
<li>SEO를 위해 필요함</li>
</ul>
</li>
</ul>
<ul>
<li>RSS Feed 설정</li>
</ul>
<h3 id="구현-하기">구현 하기<a aria-hidden="true" tabindex="-1" href="#구현-하기"><span class="icon icon-link"></span></a></h3>
<ol>
<li>mdx 파싱해서 -> 페이지 만들기</li>
</ol>
<p>nextjs에서 turbo pack를 사용해서 할려면은 next.config.ts에서 설정해 줘야 한다.</p>
<pre class="language-tsx"><code class="language-tsx code-highlight"><span class="code-line line-number" line="1"><span class="token comment">// next.config.ts</span>
</span><span class="code-line line-number" line="2"><span class="token keyword">import</span> <span class="token imports">createMDX</span> <span class="token keyword">from</span> <span class="token string">"@next/mdx"</span><span class="token punctuation">;</span>
</span><span class="code-line line-number" line="3"><span class="token keyword">import</span> <span class="token keyword">type</span> <span class="token punctuation">{</span> <span class="token maybe-class-name">NextConfig</span> <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"next"</span><span class="token punctuation">;</span>
</span><span class="code-line line-number" line="4">
</span><span class="code-line line-number" line="5"><span class="token keyword">const</span> nextConfig<span class="token operator">:</span> <span class="token maybe-class-name">NextConfig</span> <span class="token operator">=</span> <span class="token punctuation">{</span>
</span><span class="code-line line-number" line="6">  pageExtensions<span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">"js"</span><span class="token punctuation">,</span> <span class="token string">"jsx"</span><span class="token punctuation">,</span> <span class="token string">"md"</span><span class="token punctuation">,</span> <span class="token string">"mdx"</span><span class="token punctuation">,</span> <span class="token string">"ts"</span><span class="token punctuation">,</span> <span class="token string">"tsx"</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
</span><span class="code-line line-number" line="7"><span class="token punctuation">}</span><span class="token punctuation">;</span>
</span><span class="code-line line-number" line="8">
</span><span class="code-line line-number" line="9"><span class="token keyword">const</span> withMDX <span class="token operator">=</span> <span class="token function">createMDX</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
</span><span class="code-line line-number" line="10">  options<span class="token operator">:</span> <span class="token punctuation">{</span>
</span><span class="code-line line-number" line="11">    remarkPlugins<span class="token operator">:</span> <span class="token punctuation">[</span>
</span><span class="code-line line-number" line="12">      <span class="token string">"remark-breaks"</span><span class="token punctuation">,</span>
</span><span class="code-line line-number" line="13">      <span class="token string">"remark-gfm"</span><span class="token punctuation">,</span>
</span><span class="code-line line-number" line="14">      <span class="token punctuation">[</span><span class="token string">"remark-toc"</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> heading<span class="token operator">:</span> <span class="token string">"목차"</span> <span class="token punctuation">}</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
</span><span class="code-line line-number" line="15">    <span class="token punctuation">]</span><span class="token punctuation">,</span>
</span><span class="code-line line-number" line="16">    rehypePlugins<span class="token operator">:</span> <span class="token punctuation">[</span>
</span><span class="code-line line-number" line="17">      <span class="token punctuation">[</span><span class="token string">"rehype-slug"</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
</span><span class="code-line line-number" line="18">      <span class="token punctuation">[</span>
</span><span class="code-line line-number" line="19">        <span class="token string">"rehype-autolink-headings"</span><span class="token punctuation">,</span>
</span><span class="code-line line-number" line="20">        <span class="token punctuation">{</span>
</span><span class="code-line line-number" line="21">          behavior<span class="token operator">:</span> <span class="token string">"append"</span><span class="token punctuation">,</span>
</span><span class="code-line line-number" line="22">        <span class="token punctuation">}</span><span class="token punctuation">,</span>
</span><span class="code-line line-number" line="23">      <span class="token punctuation">]</span><span class="token punctuation">,</span>
</span><span class="code-line line-number" line="24">      <span class="token punctuation">[</span><span class="token string">"rehype-katex"</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> strict<span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> throwOnError<span class="token operator">:</span> <span class="token boolean">true</span> <span class="token punctuation">}</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
</span><span class="code-line line-number" line="25">      <span class="token string">"rehype-plugin-image-native-lazy-loading"</span><span class="token punctuation">,</span>
</span><span class="code-line line-number" line="26">      <span class="token punctuation">[</span><span class="token string">"rehype-prism-plus"</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> defaultLanguage<span class="token operator">:</span> <span class="token string">"js"</span><span class="token punctuation">,</span> ignoreMissing<span class="token operator">:</span> <span class="token boolean">true</span> <span class="token punctuation">}</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
</span><span class="code-line line-number" line="27">    <span class="token punctuation">]</span><span class="token punctuation">,</span>
</span><span class="code-line line-number" line="28">  <span class="token punctuation">}</span><span class="token punctuation">,</span>
</span><span class="code-line line-number" line="29"><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</span><span class="code-line line-number" line="30">
</span><span class="code-line line-number" line="31"><span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token function">withMDX</span><span class="token punctuation">(</span>nextConfig<span class="token punctuation">)</span><span class="token punctuation">;</span>
</span></code></pre>
<p>mdx파일 수집해서 페이지로 만들기</p>
<pre class="language-tsx"><code class="language-tsx code-highlight"><span class="code-line line-number" line="1"><span class="token comment">// mdx파일들 읽어서 객체화하기</span>
</span><span class="code-line line-number" line="2"><span class="token keyword">export</span> <span class="token keyword">function</span> <span class="token function">getBlogSlugs</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
</span><span class="code-line line-number" line="3">  <span class="token keyword">const</span> dir <span class="token operator">=</span> path<span class="token punctuation">.</span><span class="token method function property-access">join</span><span class="token punctuation">(</span>process<span class="token punctuation">.</span><span class="token method function property-access">cwd</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token string">"src"</span><span class="token punctuation">,</span> <span class="token string">"content"</span><span class="token punctuation">,</span> <span class="token string">"blog"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</span><span class="code-line line-number" line="4">
</span><span class="code-line line-number" line="5">  <span class="token keyword">return</span> fs
</span><span class="code-line line-number" line="6">    <span class="token punctuation">.</span><span class="token method function property-access">readdirSync</span><span class="token punctuation">(</span>dir<span class="token punctuation">)</span>
</span><span class="code-line line-number" line="7">    <span class="token punctuation">.</span><span class="token method function property-access">filter</span><span class="token punctuation">(</span><span class="token punctuation">(</span>file<span class="token punctuation">)</span> <span class="token arrow operator">=></span> path<span class="token punctuation">.</span><span class="token method function property-access">extname</span><span class="token punctuation">(</span>file<span class="token punctuation">)</span> <span class="token operator">===</span> <span class="token string">".mdx"</span><span class="token punctuation">)</span>
</span><span class="code-line line-number" line="8">    <span class="token punctuation">.</span><span class="token method function property-access">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span>file<span class="token punctuation">)</span> <span class="token arrow operator">=></span> path<span class="token punctuation">.</span><span class="token method function property-access">basename</span><span class="token punctuation">(</span>file<span class="token punctuation">,</span> path<span class="token punctuation">.</span><span class="token method function property-access">extname</span><span class="token punctuation">(</span>file<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</span><span class="code-line line-number" line="9"><span class="token punctuation">}</span>
</span><span class="code-line line-number" line="10">
</span><span class="code-line line-number" line="11"><span class="token keyword">export</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">importBlogContent</span><span class="token punctuation">(</span>slug<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token known-class-name class-name">Promise</span><span class="token operator">&#x3C;</span><span class="token maybe-class-name">ContentModule</span><span class="token operator">></span> <span class="token punctuation">{</span>
</span><span class="code-line line-number" line="12">  <span class="token keyword">const</span> module <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token keyword">import</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">@/content/blog/</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>slug<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">.mdx</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</span><span class="code-line line-number" line="13">
</span><span class="code-line line-number" line="14">  <span class="token keyword">return</span> <span class="token punctuation">{</span>
</span><span class="code-line line-number" line="15">    <span class="token spread operator">...</span>module<span class="token punctuation">,</span>
</span><span class="code-line line-number" line="16">    metadata<span class="token operator">:</span> <span class="token maybe-class-name">MetadataSchema</span><span class="token punctuation">.</span><span class="token method function property-access">parse</span><span class="token punctuation">(</span>module<span class="token punctuation">.</span><span class="token property-access">metadata</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
</span><span class="code-line line-number" line="17">  <span class="token punctuation">}</span><span class="token punctuation">;</span>
</span><span class="code-line line-number" line="18"><span class="token punctuation">}</span>
</span><span class="code-line line-number" line="19">
</span><span class="code-line line-number" line="20"><span class="token keyword">export</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">getAllBlogContents</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
</span><span class="code-line line-number" line="21">  <span class="token keyword">const</span> contents <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token known-class-name class-name">Promise</span><span class="token punctuation">.</span><span class="token method function property-access">all</span><span class="token punctuation">(</span>
</span><span class="code-line line-number" line="22">    <span class="token function">getBlogSlugs</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token method function property-access">map</span><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span>slug<span class="token punctuation">)</span> <span class="token arrow operator">=></span> <span class="token punctuation">{</span>
</span><span class="code-line line-number" line="23">      <span class="token keyword">const</span> <span class="token punctuation">{</span> metadata <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">importBlogContent</span><span class="token punctuation">(</span>slug<span class="token punctuation">)</span><span class="token punctuation">;</span>
</span><span class="code-line line-number" line="24">
</span><span class="code-line line-number" line="25">      <span class="token keyword">return</span> <span class="token punctuation">{</span>
</span><span class="code-line line-number" line="26">        metadata<span class="token punctuation">,</span>
</span><span class="code-line line-number" line="27">        slug<span class="token punctuation">,</span>
</span><span class="code-line line-number" line="28">      <span class="token punctuation">}</span><span class="token punctuation">;</span>
</span><span class="code-line line-number" line="29">    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
</span><span class="code-line line-number" line="30">  <span class="token punctuation">)</span><span class="token punctuation">;</span>
</span><span class="code-line line-number" line="31">
</span><span class="code-line line-number" line="32">  contents<span class="token punctuation">.</span><span class="token method function property-access">sort</span><span class="token punctuation">(</span><span class="token punctuation">(</span>a<span class="token punctuation">,</span> b<span class="token punctuation">)</span> <span class="token arrow operator">=></span>
</span><span class="code-line line-number" line="33">    b<span class="token punctuation">.</span><span class="token property-access">metadata</span><span class="token punctuation">.</span><span class="token property-access">publishedAt</span><span class="token punctuation">.</span><span class="token method function property-access">localeCompare</span><span class="token punctuation">(</span>a<span class="token punctuation">.</span><span class="token property-access">metadata</span><span class="token punctuation">.</span><span class="token property-access">publishedAt</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
</span><span class="code-line line-number" line="34">  <span class="token punctuation">)</span><span class="token punctuation">;</span>
</span><span class="code-line line-number" line="35">
</span><span class="code-line line-number" line="36">  <span class="token keyword">return</span> contents<span class="token punctuation">.</span><span class="token method function property-access">filter</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">{</span> metadata <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token arrow operator">=></span> <span class="token operator">!</span>metadata<span class="token punctuation">.</span><span class="token property-access">public</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</span><span class="code-line line-number" line="37"><span class="token punctuation">}</span>
</span></code></pre>
<pre class="language-tsx"><code class="language-tsx code-highlight"><span class="code-line line-number" line="1"><span class="token comment">// app/blog/[slug]/page.tsx</span>
</span><span class="code-line line-number" line="2"><span class="token keyword">export</span> <span class="token keyword">const</span> dynamicParams <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>
</span><span class="code-line line-number" line="3">
</span><span class="code-line line-number" line="4"><span class="token keyword">export</span> <span class="token keyword">function</span> <span class="token function">generateStaticParams</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
</span><span class="code-line line-number" line="5">  <span class="token keyword">return</span> <span class="token function">getBlogSlugs</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token method function property-access">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span>slug<span class="token punctuation">)</span> <span class="token arrow operator">=></span> <span class="token punctuation">(</span><span class="token punctuation">{</span> slug <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</span><span class="code-line line-number" line="6"><span class="token punctuation">}</span>
</span><span class="code-line line-number" line="7">
</span><span class="code-line line-number" line="8"><span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">Page</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
</span><span class="code-line line-number" line="9">  params<span class="token punctuation">,</span>
</span><span class="code-line line-number" line="10"><span class="token punctuation">}</span><span class="token operator">:</span> <span class="token punctuation">{</span>
</span><span class="code-line line-number" line="11">  params<span class="token operator">:</span> <span class="token known-class-name class-name">Promise</span><span class="token operator">&#x3C;</span><span class="token punctuation">{</span> slug<span class="token operator">:</span> <span class="token builtin">string</span> <span class="token punctuation">}</span><span class="token operator">></span><span class="token punctuation">;</span>
</span><span class="code-line line-number" line="12"><span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
</span><span class="code-line line-number" line="13">  <span class="token keyword">const</span> <span class="token punctuation">{</span> slug <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">await</span> params<span class="token punctuation">;</span>
</span><span class="code-line line-number" line="14">
</span><span class="code-line line-number" line="15">  <span class="token keyword">const</span> <span class="token punctuation">{</span> <span class="token keyword">default</span><span class="token operator">:</span> <span class="token maybe-class-name">Content</span><span class="token punctuation">,</span> metadata <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">importBlogContent</span><span class="token punctuation">(</span>slug<span class="token punctuation">)</span><span class="token punctuation">;</span>
</span><span class="code-line line-number" line="16">
</span><span class="code-line line-number" line="17">  <span class="token keyword">if</span> <span class="token punctuation">(</span>metadata<span class="token punctuation">.</span><span class="token property-access">public</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
</span><span class="code-line line-number" line="18">    <span class="token keyword">return</span> <span class="token function">notFound</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Render 404 page</span>
</span><span class="code-line line-number" line="19">  <span class="token punctuation">}</span>
</span><span class="code-line line-number" line="20">
</span><span class="code-line line-number" line="21">  <span class="token keyword">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation">&#x3C;</span><span class="token class-name">Content</span></span> <span class="token punctuation">/></span></span><span class="token punctuation">;</span>
</span><span class="code-line line-number" line="22"><span class="token punctuation">}</span>
</span></code></pre>
<ol start="2">
<li>sitemap 구성하기</li>
</ol>
<pre class="language-tsx"><code class="language-tsx code-highlight"><span class="code-line line-number" line="1"><span class="token comment">// app/sitemap.ts</span>
</span><span class="code-line line-number" line="2"><span class="token keyword">export</span> <span class="token keyword">const</span> dynamic <span class="token operator">=</span> <span class="token string">"force-static"</span><span class="token punctuation">;</span>
</span><span class="code-line line-number" line="3">
</span><span class="code-line line-number" line="4"><span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">sitemap</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token known-class-name class-name">Promise</span><span class="token operator">&#x3C;</span><span class="token maybe-class-name">MetadataRoute</span><span class="token punctuation">.</span><span class="token property-access"><span class="token maybe-class-name">Sitemap</span></span><span class="token operator">></span> <span class="token punctuation">{</span>
</span><span class="code-line line-number" line="5">  <span class="token keyword">const</span> posts <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">getAllBlogContents</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</span><span class="code-line line-number" line="6">
</span><span class="code-line line-number" line="7">  <span class="token keyword">const</span> routes <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">""</span><span class="token punctuation">,</span> <span class="token string">"blog"</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token method function property-access">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span>route<span class="token punctuation">)</span> <span class="token arrow operator">=></span> <span class="token punctuation">(</span><span class="token punctuation">{</span>
</span><span class="code-line line-number" line="8">    url<span class="token operator">:</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>siteMetadata<span class="token punctuation">.</span>siteUrl<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">/</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>route<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span>
</span><span class="code-line line-number" line="9">    lastModified<span class="token operator">:</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token method function property-access">toISOString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
</span><span class="code-line line-number" line="10">  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</span><span class="code-line line-number" line="11">
</span><span class="code-line line-number" line="12">  <span class="token keyword">const</span> blogRoutes <span class="token operator">=</span> posts
</span><span class="code-line line-number" line="13">    <span class="token punctuation">.</span><span class="token method function property-access">filter</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">{</span> metadata <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token arrow operator">=></span> <span class="token operator">!</span>metadata<span class="token punctuation">.</span><span class="token property-access">public</span><span class="token punctuation">)</span>
</span><span class="code-line line-number" line="14">    <span class="token punctuation">.</span><span class="token method function property-access">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">{</span> metadata<span class="token punctuation">,</span> slug <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token arrow operator">=></span> <span class="token punctuation">{</span>
</span><span class="code-line line-number" line="15">      <span class="token keyword">const</span> date <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span>metadata<span class="token punctuation">.</span><span class="token property-access">publishedAt</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</span><span class="code-line line-number" line="16">      <span class="token keyword">const</span> hours <span class="token operator">=</span> date<span class="token punctuation">.</span><span class="token method function property-access">getHours</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</span><span class="code-line line-number" line="17">
</span><span class="code-line line-number" line="18">      <span class="token keyword">return</span> <span class="token punctuation">{</span>
</span><span class="code-line line-number" line="19">        url<span class="token operator">:</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>siteMetadata<span class="token punctuation">.</span>siteUrl<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">/blog/</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>slug<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span>
</span><span class="code-line line-number" line="20">        lastModified<span class="token operator">:</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span>date<span class="token punctuation">.</span><span class="token method function property-access">setHours</span><span class="token punctuation">(</span>hours <span class="token operator">-</span> <span class="token number">9</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token method function property-access">toISOString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
</span><span class="code-line line-number" line="21">      <span class="token punctuation">}</span><span class="token punctuation">;</span>
</span><span class="code-line line-number" line="22">    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</span><span class="code-line line-number" line="23">
</span><span class="code-line line-number" line="24">  <span class="token keyword">return</span> <span class="token punctuation">[</span><span class="token spread operator">...</span>routes<span class="token punctuation">,</span> <span class="token spread operator">...</span>blogRoutes<span class="token punctuation">]</span><span class="token punctuation">;</span>
</span><span class="code-line line-number" line="25"><span class="token punctuation">}</span>
</span><span class="code-line line-number" line="26">
</span></code></pre>
<ol start="3">
<li>RSS Feed<br>
현재 딱히 필요하진 않아서 미구현.</li>
</ol>
<h2 id="마무리">마무리<a aria-hidden="true" tabindex="-1" href="#마무리"><span class="icon icon-link"></span></a></h2>
<p>오랜만에 코드를 다시 보니 당시에는 이해하고 작성했던 구조도 시간이 지나면서 많이 잊어버렸다.<br>
이번 글은 리모델링을 시작하기 전에 현재 블로그가 어떤 방식으로 동작하는지 다시 정리해보기 위한 기록이다.</p>
<p>이후 리모델링은 마크다운 파싱을 조금 더 유연하게 처리하기 위해 Next.js의 Turbopack 기반 MDX 처리 대신, 빌드 타임에 remark•rehype를 직접 사용하는 방식으로 구조를 변경했다.</p>
<p>또한 데이터베이스 관계도와 같은 다이어그램을 표시할 수 있도록 Mermaid 지원도 추가했다.</p>
<section data-footnotes class="footnotes"><h2 class="sr-only" id="footnote-label">Footnotes<a aria-hidden="true" tabindex="-1" href="#footnote-label"><span class="icon icon-link"></span></a></h2>
<ol>
<li id="user-content-fn-1">
<p>마크다운을 AST로 파싱하고, 내용을 가공하거나 변형하는 데 사용 <a href="#user-content-fnref-1" data-footnote-backref="" aria-label="Back to reference 1" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-2">
<p>HTML를 AST로 만들고, 내용을 가공하거나 변형하는 데 사용 <a href="#user-content-fnref-2" data-footnote-backref="" aria-label="Back to reference 2" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-3">
<p>검색 엔진용 사이트맵 <a href="#user-content-fnref-3" data-footnote-backref="" aria-label="Back to reference 3" class="data-footnote-backref">↩</a></p>
</li>
</ol>
</section>]]></content:encoded>
      </item><item>
        <title>블로그 방향성</title>
        <link>https://yanguk.github.io/blog/5-my-blog</link>
        <description>블로그 방향성</description>
        <guid isPermaLink="true">https://yanguk.github.io/blog/5-my-blog</guid>
        <pubDate>Tue, 30 Dec 2025 00:00:00 GMT</pubDate>
        <content:encoded><![CDATA[<h1>블로그 방향성</h1><p>좋은 기술 블로그란 무엇인가? 사실 나는 잘 모르겠고 긱뉴스에 포스팅된 글이 있다.<br>
<a href="https://news.hada.io/topic?id=25296">GeekNews_실무 중심 블로그</a></p>
<p>위 포스팅과 별개로 내가 생각하는 기술 블로그는<br>
기술 관련된 이야기를 올려야 한다고 생각해왔다.<br>
단순히 공식 문서에 있는 사용법을 정리하는 글보다는, 직접 개발하면서 마주했던 문제와 이를 해결해 나간 과정이 담긴 글이 더 가치 있다고 믿었다.</p>
<p>그런 생각 때문인지 글을 써놓고도 공개하지 않은 포스팅이 많아졌고, 점점 <strong>포스팅 자체가 어려워졌다.</strong> 기준이 높아질수록 글을 쓰는 부담도 함께 커졌던 것 같다. 하지만 기술 이야기만 하는 블로그보다는, 가끔은 내 생각과 경험도 함께 기록하고 싶다는 마음이 들었다.</p>
<p>그래서 앞으로는 기술과 에세이를 함께 담는, 나만의 블로그로 운영해보려고 한다.<br>
아직 정리가 덜 된 글이라도 일단 써보고 올려보자. 글을 쓰는 과정 자체가 생각을 정리하게 만들고, 결국 더 나은 글로 이어질 테니까.</p>]]></content:encoded>
      </item><item>
        <title>리액트 동작 방식과 최적화</title>
        <link>https://yanguk.github.io/blog/4-react-re-rerendering</link>
        <description>리액트 동작 방식과 최적화</description>
        <guid isPermaLink="true">https://yanguk.github.io/blog/4-react-re-rerendering</guid>
        <pubDate>Tue, 11 Nov 2025 00:00:00 GMT</pubDate>
        <content:encoded><![CDATA[<h1>리액트 동작 방식과 최적화</h1><p><a href="https://yanguk.github.io/teck-talk-react-re-render">React Re Rendering</a></p>
<p>발표하기 위해 만든 자료이며 발표 자료로 대체합니다.</p>]]></content:encoded>
      </item><item>
        <title>느려진 zsh 터미널 성능 최적화</title>
        <link>https://yanguk.github.io/blog/3-zsh-performance-update</link>
        <description>느려진 zsh 터미널 성능 최적화</description>
        <guid isPermaLink="true">https://yanguk.github.io/blog/3-zsh-performance-update</guid>
        <pubDate>Fri, 03 Oct 2025 00:00:00 GMT</pubDate>
        <content:encoded><![CDATA[<h1>느려진 zsh 터미널 성능 최적화</h1><p>개발할 때는 주로 neovim을 사용하다 보니, 터미널에서 모든 작업을 수행하는 데<br>
쉘이 켜지는 속도가 갑자기 너무 느린 게 느껴지는 것이었다.</p>
<p>느린 원인을 찾고 개선하기 위한 여정이다.</p>
<h2 id="목차">목차<a aria-hidden="true" tabindex="-1" href="#목차"><span class="icon icon-link"></span></a></h2>
<ul>
<li><a href="#%EC%9B%90%EC%9D%B8-%EC%B0%BE%EA%B8%B0">원인 찾기</a></li>
<li><a href="#%EA%B0%9C%EC%84%A0-%ED%95%98%EA%B8%B0">개선 하기</a></li>
<li><a href="#%EA%B2%B0%EA%B3%BC">결과</a></li>
<li><a href="#%EC%B6%94%EA%B0%80%EC%A0%81%EC%9C%BC%EB%A1%9C">추가적으로</a></li>
</ul>
<h2 id="원인-찾기">원인 찾기<a aria-hidden="true" tabindex="-1" href="#원인-찾기"><span class="icon icon-link"></span></a></h2>
<p><a href="https://github.com/romkatv/zsh-bench">zsh-bench</a>을 돌려보았다.</p>
<pre class="language-sh"><code class="language-sh code-highlight"><span class="code-line line-number" line="1"><span class="token comment"># 설치하고 실행</span>
</span><span class="code-line line-number" line="2"><span class="token operator">></span> ~/zsh-bench/zsh-bench
</span></code></pre>
<p><img src="https://yanguk.github.io/zsh-performance-update/zsh-bench.png" alt="zsh-bench-result"></p>
<p>눈 여겨볼건 이거다. <code>first_prompt_lag_ms=1409.696</code></p>
<p>처음 키고 프롬프트 입력할수 있을때 까지 <code>1409ms</code>가 걸린다.<br>
그러면 이제 <code>zsh/zprof</code>를 사용하여 무엇 때문에 느린지 살펴보자</p>
<pre class="language-sh"><code class="language-sh code-highlight"><span class="code-line line-number" line="1"><span class="token comment"># .zshrc 에 해당 라인 추가</span>
</span><span class="code-line line-number" line="2"><span class="token operator">></span> zmodload zsh/zprof
</span></code></pre>
<pre class="language-sh"><code class="language-sh code-highlight"><span class="code-line line-number" line="1"><span class="token comment"># 실행</span>
</span><span class="code-line line-number" line="2"><span class="token operator">></span> zprof
</span></code></pre>
<p><img src="https://yanguk.github.io/zsh-performance-update/zprof.png" alt="zprof"></p>
<p>nvm이 많이 느린편인걸 알 수 있다.</p>
<h2 id="개선-하기">개선 하기<a aria-hidden="true" tabindex="-1" href="#개선-하기"><span class="icon icon-link"></span></a></h2>
<p>nvm을 제거하고 하는김에 플러그인 매니저까지 변경하였다.</p>
<ul>
<li>nvm 대안책 찾기 => <a href="https://volta.sh/">Volta</a></li>
<li>oh-my-zsh => <a href="https://github.com/mattmc3/antidote">antidote</a></li>
</ul>
<p>본인은 nix<sup><a href="#user-content-fn-1" id="user-content-fnref-1" data-footnote-ref aria-describedby="footnote-label">1</a></sup>를 사용하기에 아래와 같이 설정을 하였다.</p>
<pre class="language-nix"><code class="language-nix code-highlight"><span class="code-line line-number" line="1"><span class="token comment"># configuration.nix</span>
</span><span class="code-line line-number" line="2"><span class="token punctuation">{</span>
</span><span class="code-line line-number" line="3">  environment<span class="token punctuation">.</span>systemPackages <span class="token operator">=</span> <span class="token keyword">with</span> pkgs<span class="token punctuation">;</span> <span class="token punctuation">[</span>
</span><span class="code-line line-number" line="4">    <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>
</span><span class="code-line line-number" line="5">    antidote
</span><span class="code-line line-number" line="6">    volta
</span><span class="code-line line-number" line="7">  <span class="token punctuation">]</span>
</span><span class="code-line line-number" line="8">
</span><span class="code-line line-number" line="9">  programs<span class="token punctuation">.</span>zsh <span class="token operator">=</span> <span class="token punctuation">{</span>
</span><span class="code-line line-number" line="10">    enable <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>
</span><span class="code-line line-number" line="11">    enableBashCompletion <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>
</span><span class="code-line line-number" line="12">    enableCompletion <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>
</span><span class="code-line line-number" line="13">    variables <span class="token operator">=</span> <span class="token punctuation">{</span>
</span><span class="code-line line-number" line="14">      ZDOTDIR <span class="token operator">=</span> <span class="token string">"$HOME/.config/zsh"</span><span class="token punctuation">;</span>
</span><span class="code-line line-number" line="15">    <span class="token punctuation">}</span><span class="token punctuation">;</span>
</span><span class="code-line line-number" line="16">    promptInit <span class="token operator">=</span> <span class="token string">''
</span></span><span class="code-line line-number" line="17"><span class="token string">      fpath=(<span class="token interpolation"><span class="token antiquotation important">$</span><span class="token punctuation">{</span>pkgs<span class="token punctuation">.</span>antidote<span class="token punctuation">}</span></span>/share/antidote/functions $fpath)
</span></span><span class="code-line line-number" line="18"><span class="token string">      autoload -Uz antidote
</span></span><span class="code-line line-number" line="19"><span class="token string">    ''</span><span class="token punctuation">;</span>
</span><span class="code-line line-number" line="20">  <span class="token punctuation">}</span><span class="token punctuation">;</span>
</span><span class="code-line line-number" line="21"><span class="token punctuation">}</span>
</span></code></pre>
<h2 id="결과">결과<a aria-hidden="true" tabindex="-1" href="#결과"><span class="icon icon-link"></span></a></h2>
<p><img src="https://yanguk.github.io/zsh-performance-update/result2.png" alt="zsh-bench-result-2"></p>
<p>first_prompt_lag_ms가 <code>39.118ms</code>로 줄었다. (97.22% 빨라짐)<br>
<code>volta</code> 도 nvm 보다 휠씬 간단하고 빠르다.</p>
<p>아주 쾌적하고 만족스럽다.🙂</p>
<h2 id="추가적으로">추가적으로<a aria-hidden="true" tabindex="-1" href="#추가적으로"><span class="icon icon-link"></span></a></h2>
<p><code>omz</code> 프레임워크를 벗어나니 <code>sub-string-search</code> 기능이 빠져있다.<br>
zsh-vi 모드를 사용하기에 해당 기능도 넣어주었다.</p>
<pre class="language-bash"><code class="language-bash code-highlight"><span class="code-line line-number" line="1"><span class="token keyword">function</span> <span class="token function-name function">zvm_before_init</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
</span><span class="code-line line-number" line="2">    <span class="token comment"># up, down 시 히스토리 substring search</span>
</span><span class="code-line line-number" line="3">    autoload <span class="token parameter variable">-U</span> history-search-end
</span><span class="code-line line-number" line="4">    zle <span class="token parameter variable">-N</span> history-beginning-search-backward-end history-search-end
</span><span class="code-line line-number" line="5">    zle <span class="token parameter variable">-N</span> history-beginning-search-forward-end history-search-end
</span><span class="code-line line-number" line="6">
</span><span class="code-line line-number" line="7">    <span class="token comment"># zsh-vi-mode</span>
</span><span class="code-line line-number" line="8">    zvm_bindkey viins <span class="token string">'^[[A'</span> history-beginning-search-backward-end
</span><span class="code-line line-number" line="9">    zvm_bindkey viins <span class="token string">'^[[B'</span> history-beginning-search-forward-end
</span><span class="code-line line-number" line="10">    zvm_bindkey vicmd <span class="token string">'^[[A'</span> history-beginning-search-backward-end
</span><span class="code-line line-number" line="11">    zvm_bindkey vicmd <span class="token string">'^[[B'</span> history-beginning-search-forward-end
</span><span class="code-line line-number" line="12"><span class="token punctuation">}</span>
</span></code></pre>
<blockquote>
<p>설정은 제<a href="https://github.com/Yanguk/dotfiles">dotfiles</a>에서 볼 수 있습니다.</p>
</blockquote>
<section data-footnotes class="footnotes"><h2 class="sr-only" id="footnote-label">Footnotes<a aria-hidden="true" tabindex="-1" href="#footnote-label"><span class="icon icon-link"></span></a></h2>
<ol>
<li id="user-content-fn-1">
<p>nix는 선언적이고 재현 가능한 방식으로 소프트웨어 패키지를 관리할 수 있는 도구입니다. 자세한 내용은 <a href="https://nixos.org/">nix 공식 웹사이트</a>를 참조하세요. <a href="#user-content-fnref-1" data-footnote-backref="" aria-label="Back to reference 1" class="data-footnote-backref">↩</a></p>
</li>
</ol>
</section>]]></content:encoded>
      </item><item>
        <title>웹에서 이미지 최적화</title>
        <link>https://yanguk.github.io/blog/2-images-optimization</link>
        <description>웹에서 이미지 최적화</description>
        <guid isPermaLink="true">https://yanguk.github.io/blog/2-images-optimization</guid>
        <pubDate>Mon, 21 Jul 2025 00:00:00 GMT</pubDate>
        <content:encoded><![CDATA[<h1>웹에서 이미지 최적화</h1><p>웹 개발을 하다보면 이미지를 다루게 될 일이 많다.<br>
여기에서 유저가 이미지를 업로드 하는 기능이 있다면 주의해야될 점이 있다.<br>
보통 핸드폰으로 찍은 사진을 업로드를 할텐데, 이 경우 용량은 <code>2~3MB</code> 정도 된다.<br>
이러한 이미지들을 그대로 사용한다면 이제 웹페이지는 느려지는 것이다.</p>
<p>웹에서 적합한 이미지 크기는 네트워크 환경에 따라 다르고, 이미지의 역할에 따라 다르긴 할텐데 ChatGPT한테 물어본 결과 <code>300kb</code>이하가 적당하다고 한다.<br>
그렇다면 이미지를 어떻게 줄일수 있을까?</p>
<h2 id="방법1-업로드-용량-제한하기">방법1: 업로드 용량 제한하기<a aria-hidden="true" tabindex="-1" href="#방법1-업로드-용량-제한하기"><span class="icon icon-link"></span></a></h2>
<pre class="language-html"><code class="language-html code-highlight"><span class="code-line line-number" line="1"><span class="token tag"><span class="token tag"><span class="token punctuation">&#x3C;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript">
</span></span></span><span class="code-line line-number" line="2"><span class="token script"><span class="token language-javascript">  <span class="token keyword">const</span> fileInput <span class="token operator">=</span> <span class="token dom variable">document</span><span class="token punctuation">.</span><span class="token method function property-access">getElementById</span><span class="token punctuation">(</span><span class="token string">"fileInput"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</span></span></span><span class="code-line line-number" line="3"><span class="token script"><span class="token language-javascript">
</span></span></span><span class="code-line line-number" line="4"><span class="token script"><span class="token language-javascript">  fileInput<span class="token punctuation">.</span><span class="token method function property-access">addEventListener</span><span class="token punctuation">(</span><span class="token string">"change"</span><span class="token punctuation">,</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
</span></span></span><span class="code-line line-number" line="5"><span class="token script"><span class="token language-javascript">    <span class="token keyword">const</span> file <span class="token operator">=</span> fileInput<span class="token punctuation">.</span><span class="token property-access">files</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
</span></span></span><span class="code-line line-number" line="6"><span class="token script"><span class="token language-javascript">
</span></span></span><span class="code-line line-number" line="7"><span class="token script"><span class="token language-javascript">    <span class="token keyword control-flow">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>file<span class="token punctuation">)</span> <span class="token keyword control-flow">return</span><span class="token punctuation">;</span>
</span></span></span><span class="code-line line-number" line="8"><span class="token script"><span class="token language-javascript">
</span></span></span><span class="code-line line-number" line="9"><span class="token script"><span class="token language-javascript">    <span class="token keyword">const</span> maxSizeMB <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> <span class="token comment">// 1MB 제한</span>
</span></span></span><span class="code-line line-number" line="10"><span class="token script"><span class="token language-javascript">    <span class="token keyword">const</span> maxSizeBytes <span class="token operator">=</span> maxSizeMB <span class="token operator">*</span> <span class="token number">1024</span> <span class="token operator">*</span> <span class="token number">1024</span><span class="token punctuation">;</span>
</span></span></span><span class="code-line line-number" line="11"><span class="token script"><span class="token language-javascript">
</span></span></span><span class="code-line line-number" line="12"><span class="token script"><span class="token language-javascript">    <span class="token keyword control-flow">if</span> <span class="token punctuation">(</span>file<span class="token punctuation">.</span><span class="token property-access">size</span> <span class="token operator">></span> maxSizeBytes<span class="token punctuation">)</span> <span class="token punctuation">{</span>
</span></span></span><span class="code-line line-number" line="13"><span class="token script"><span class="token language-javascript">      <span class="token function">toast</span><span class="token punctuation">(</span><span class="token string">"용량이 너무 커요"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</span></span></span><span class="code-line line-number" line="14"><span class="token script"><span class="token language-javascript">    <span class="token punctuation">}</span> <span class="token keyword control-flow">else</span> <span class="token punctuation">{</span>
</span></span></span><span class="code-line line-number" line="15"><span class="token script"><span class="token language-javascript">      <span class="token function">toast</span><span class="token punctuation">(</span><span class="token string">"성공!"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</span></span></span><span class="code-line line-number" line="16"><span class="token script"><span class="token language-javascript">    <span class="token punctuation">}</span>
</span></span></span><span class="code-line line-number" line="17"><span class="token script"><span class="token language-javascript">  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</span></span></span><span class="code-line line-number" line="18"><span class="token script"><span class="token language-javascript"></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&#x3C;/</span>script</span><span class="token punctuation">></span></span>
</span></code></pre>
<p>파일 인풋에서 받아온 파일에 대한 용량을 알 수가 있다.<br>
여기서 최대 용량 이하만 받도록 하는 것이다.</p>
<h2 id="방법2-클라이언트에서-리사이징하기">방법2: 클라이언트에서 리사이징하기<a aria-hidden="true" tabindex="-1" href="#방법2-클라이언트에서-리사이징하기"><span class="icon icon-link"></span></a></h2>
<p>사이즈만 줄여도 용량이 많이 줄어든다.<br>
웹에서는 <code>canvas</code> api를 사용해서 사이즈를 줄일수가 있다.</p>
<p>원하는 사이즈의 canvas를 만들고 그안에 이미지를 그려서 바이너리로 추출하는 방식이다.</p>
<pre class="language-html"><code class="language-html code-highlight"><span class="code-line line-number" line="1"><span class="token tag"><span class="token tag"><span class="token punctuation">&#x3C;</span>script</span><span class="token punctuation">></span></span><span class="token script"><span class="token language-javascript">
</span></span></span><span class="code-line line-number" line="2"><span class="token script"><span class="token language-javascript">  <span class="token keyword">const</span> upload <span class="token operator">=</span> <span class="token dom variable">document</span><span class="token punctuation">.</span><span class="token method function property-access">getElementById</span><span class="token punctuation">(</span><span class="token string">"upload"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</span></span></span><span class="code-line line-number" line="3"><span class="token script"><span class="token language-javascript">
</span></span></span><span class="code-line line-number" line="4"><span class="token script"><span class="token language-javascript">  upload<span class="token punctuation">.</span><span class="token method function property-access">addEventListener</span><span class="token punctuation">(</span><span class="token string">"change"</span><span class="token punctuation">,</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
</span></span></span><span class="code-line line-number" line="5"><span class="token script"><span class="token language-javascript">    <span class="token keyword">const</span> file <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token property-access">files</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
</span></span></span><span class="code-line line-number" line="6"><span class="token script"><span class="token language-javascript">
</span></span></span><span class="code-line line-number" line="7"><span class="token script"><span class="token language-javascript">    <span class="token keyword">const</span> img <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Image</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</span></span></span><span class="code-line line-number" line="8"><span class="token script"><span class="token language-javascript">    img<span class="token punctuation">.</span><span class="token property-access">src</span> <span class="token operator">=</span> <span class="token constant">URL</span><span class="token punctuation">.</span><span class="token method function property-access">createObjectURL</span><span class="token punctuation">(</span>file<span class="token punctuation">)</span><span class="token punctuation">;</span>
</span></span></span><span class="code-line line-number" line="9"><span class="token script"><span class="token language-javascript">
</span></span></span><span class="code-line line-number" line="10"><span class="token script"><span class="token language-javascript">    img<span class="token punctuation">.</span><span class="token method-variable function-variable method function property-access">onload</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token arrow operator">=></span> <span class="token punctuation">{</span>
</span></span></span><span class="code-line line-number" line="11"><span class="token script"><span class="token language-javascript">      <span class="token keyword">const</span> <span class="token constant">MAX_WIDTH</span> <span class="token operator">=</span> <span class="token number">800</span><span class="token punctuation">;</span>
</span></span></span><span class="code-line line-number" line="12"><span class="token script"><span class="token language-javascript">      <span class="token keyword">const</span> scale <span class="token operator">=</span> <span class="token constant">MAX_WIDTH</span> <span class="token operator">/</span> img<span class="token punctuation">.</span><span class="token property-access">width</span><span class="token punctuation">;</span>
</span></span></span><span class="code-line line-number" line="13"><span class="token script"><span class="token language-javascript">      <span class="token keyword">const</span> width <span class="token operator">=</span> <span class="token known-class-name class-name">Math</span><span class="token punctuation">.</span><span class="token method function property-access">min</span><span class="token punctuation">(</span>img<span class="token punctuation">.</span><span class="token property-access">width</span><span class="token punctuation">,</span> <span class="token constant">MAX_WIDTH</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</span></span></span><span class="code-line line-number" line="14"><span class="token script"><span class="token language-javascript">      <span class="token keyword">const</span> height <span class="token operator">=</span> img<span class="token punctuation">.</span><span class="token property-access">height</span> <span class="token operator">*</span> scale<span class="token punctuation">;</span>
</span></span></span><span class="code-line line-number" line="15"><span class="token script"><span class="token language-javascript">
</span></span></span><span class="code-line line-number" line="16"><span class="token script"><span class="token language-javascript">      <span class="token comment">// 캔버스 태그 생성</span>
</span></span></span><span class="code-line line-number" line="17"><span class="token script"><span class="token language-javascript">      <span class="token keyword">const</span> canvas <span class="token operator">=</span> <span class="token dom variable">document</span><span class="token punctuation">.</span><span class="token method function property-access">createElement</span><span class="token punctuation">(</span><span class="token string">"canvas"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</span></span></span><span class="code-line line-number" line="18"><span class="token script"><span class="token language-javascript">      canvas<span class="token punctuation">.</span><span class="token property-access">width</span> <span class="token operator">=</span> width<span class="token punctuation">;</span>
</span></span></span><span class="code-line line-number" line="19"><span class="token script"><span class="token language-javascript">      canvas<span class="token punctuation">.</span><span class="token property-access">height</span> <span class="token operator">=</span> height<span class="token punctuation">;</span>
</span></span></span><span class="code-line line-number" line="20"><span class="token script"><span class="token language-javascript">
</span></span></span><span class="code-line line-number" line="21"><span class="token script"><span class="token language-javascript">      <span class="token comment">// 이미지 그리기</span>
</span></span></span><span class="code-line line-number" line="22"><span class="token script"><span class="token language-javascript">      <span class="token keyword">const</span> ctx <span class="token operator">=</span> canvas<span class="token punctuation">.</span><span class="token method function property-access">getContext</span><span class="token punctuation">(</span><span class="token string">"2d"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</span></span></span><span class="code-line line-number" line="23"><span class="token script"><span class="token language-javascript">      ctx<span class="token punctuation">.</span><span class="token method function property-access">drawImage</span><span class="token punctuation">(</span>img<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> width<span class="token punctuation">,</span> height<span class="token punctuation">)</span><span class="token punctuation">;</span>
</span></span></span><span class="code-line line-number" line="24"><span class="token script"><span class="token language-javascript">
</span></span></span><span class="code-line line-number" line="25"><span class="token script"><span class="token language-javascript">      <span class="token comment">// 압축된 blob로 변환</span>
</span></span></span><span class="code-line line-number" line="26"><span class="token script"><span class="token language-javascript">      canvas<span class="token punctuation">.</span><span class="token method function property-access">toBlob</span><span class="token punctuation">(</span>
</span></span></span><span class="code-line line-number" line="27"><span class="token script"><span class="token language-javascript">        <span class="token punctuation">(</span><span class="token parameter">blob</span><span class="token punctuation">)</span> <span class="token arrow operator">=></span> <span class="token punctuation">{</span>
</span></span></span><span class="code-line line-number" line="28"><span class="token script"><span class="token language-javascript">          <span class="token keyword control-flow">if</span> <span class="token punctuation">(</span>blob<span class="token punctuation">)</span> <span class="token punctuation">{</span>
</span></span></span><span class="code-line line-number" line="29"><span class="token script"><span class="token language-javascript">            <span class="token comment">// 미리보기 url</span>
</span></span></span><span class="code-line line-number" line="30"><span class="token script"><span class="token language-javascript">            <span class="token keyword">const</span> resizedUrl <span class="token operator">=</span> <span class="token constant">URL</span><span class="token punctuation">.</span><span class="token method function property-access">createObjectURL</span><span class="token punctuation">(</span>blob<span class="token punctuation">)</span><span class="token punctuation">;</span>
</span></span></span><span class="code-line line-number" line="31"><span class="token script"><span class="token language-javascript">
</span></span></span><span class="code-line line-number" line="32"><span class="token script"><span class="token language-javascript">            <span class="token comment">// 서버에 업로드하려면 `blob`을 formData 등에 넣어서 전송</span>
</span></span></span><span class="code-line line-number" line="33"><span class="token script"><span class="token language-javascript">            <span class="token comment">// const formData = new FormData();</span>
</span></span></span><span class="code-line line-number" line="34"><span class="token script"><span class="token language-javascript">            <span class="token comment">// formData.append("file", blob, "resized.jpg");</span>
</span></span></span><span class="code-line line-number" line="35"><span class="token script"><span class="token language-javascript">          <span class="token punctuation">}</span>
</span></span></span><span class="code-line line-number" line="36"><span class="token script"><span class="token language-javascript">        <span class="token punctuation">}</span><span class="token punctuation">,</span>
</span></span></span><span class="code-line line-number" line="37"><span class="token script"><span class="token language-javascript">        <span class="token string">"image/jpeg"</span><span class="token punctuation">,</span>
</span></span></span><span class="code-line line-number" line="38"><span class="token script"><span class="token language-javascript">        <span class="token number">0.8</span><span class="token punctuation">,</span>
</span></span></span><span class="code-line line-number" line="39"><span class="token script"><span class="token language-javascript">      <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// JPEG 포맷, 품질 80%</span>
</span></span></span><span class="code-line line-number" line="40"><span class="token script"><span class="token language-javascript">    <span class="token punctuation">}</span><span class="token punctuation">;</span>
</span></span></span><span class="code-line line-number" line="41"><span class="token script"><span class="token language-javascript">  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</span></span></span><span class="code-line line-number" line="42"><span class="token script"><span class="token language-javascript"></span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&#x3C;/</span>script</span><span class="token punctuation">></span></span>
</span></code></pre>
<h2 id="방법3-백엔드에서-처리하기">방법3: 백엔드에서 처리하기<a aria-hidden="true" tabindex="-1" href="#방법3-백엔드에서-처리하기"><span class="icon icon-link"></span></a></h2>
<p>nodejs에서는 <code>sharp</code>가 대표적인 이미지 압축 및 변환 라이브러리다.</p>
<p>과거 실무 중에 적용 시킨 사례를 소개한다.</p>
<h3 id="적용전">적용전<a aria-hidden="true" tabindex="-1" href="#적용전"><span class="icon icon-link"></span></a></h3>
<pre class="language-tsx"><code class="language-tsx code-highlight"><span class="code-line line-number" line="1"><span class="token keyword">export</span> <span class="token keyword">const</span> parseImagesMiddleware <span class="token operator">=</span> <span class="token punctuation">[</span>
</span><span class="code-line line-number" line="2">	<span class="token function">bindMiddleware</span><span class="token punctuation">(</span><span class="token function">multer</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token method function property-access">array</span><span class="token punctuation">(</span><span class="token string">'images'</span><span class="token punctuation">,</span> <span class="token constant">STUDY_PLAN_IMAGES_UPLOAD_MAX_COUNT</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
</span><span class="code-line line-number" line="3">	<span class="token punctuation">(</span>req<span class="token punctuation">,</span> res<span class="token punctuation">,</span> next<span class="token punctuation">)</span> <span class="token arrow operator">=></span> <span class="token punctuation">{</span>
</span><span class="code-line line-number" line="4">		<span class="token keyword">const</span> files <span class="token operator">=</span> req<span class="token punctuation">.</span><span class="token property-access">files</span> <span class="token operator">??</span> <span class="token punctuation">[</span><span class="token punctuation">]</span>
</span><span class="code-line line-number" line="5">
</span><span class="code-line line-number" line="6">		req<span class="token punctuation">.</span><span class="token property-access">files</span> <span class="token operator">=</span> files<span class="token punctuation">.</span><span class="token method function property-access">map</span><span class="token punctuation">(</span>file <span class="token arrow operator">=></span> <span class="token punctuation">(</span><span class="token punctuation">{</span>
</span><span class="code-line line-number" line="7">			fileName<span class="token operator">:</span> <span class="token function">parseFileName</span><span class="token punctuation">(</span>file<span class="token punctuation">,</span> req<span class="token punctuation">.</span><span class="token property-access">params</span><span class="token punctuation">.</span><span class="token property-access">planId</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
</span><span class="code-line line-number" line="8">			buffer<span class="token operator">:</span> file<span class="token punctuation">.</span><span class="token property-access">buffer</span><span class="token punctuation">,</span>
</span><span class="code-line line-number" line="9">		<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
</span><span class="code-line line-number" line="10">
</span><span class="code-line line-number" line="11">		<span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
</span><span class="code-line line-number" line="12">	<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
</span><span class="code-line line-number" line="13"><span class="token punctuation">]</span>
</span></code></pre>
<h3 id="적용후">적용후<a aria-hidden="true" tabindex="-1" href="#적용후"><span class="icon icon-link"></span></a></h3>
<pre class="language-tsx"><code class="language-tsx code-highlight"><span class="code-line line-number" line="1"><span class="token keyword">export</span> <span class="token keyword">const</span> parseImagesMiddleware <span class="token operator">=</span> <span class="token punctuation">[</span>
</span><span class="code-line line-number" line="2">  <span class="token function">bindMiddleware</span><span class="token punctuation">(</span><span class="token function">multer</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token method function property-access">array</span><span class="token punctuation">(</span><span class="token string">"images"</span><span class="token punctuation">,</span> <span class="token constant">STUDY_PLAN_IMAGES_UPLOAD_MAX_COUNT</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
</span><span class="code-line line-number" line="3">  <span class="token function">asyncHandler</span><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span>req<span class="token operator">:</span> <span class="token builtin">any</span><span class="token punctuation">,</span> _res<span class="token punctuation">,</span> next<span class="token punctuation">)</span> <span class="token arrow operator">=></span> <span class="token punctuation">{</span>
</span><span class="code-line line-number" line="4">    <span class="token keyword">const</span> files <span class="token operator">=</span> req<span class="token punctuation">.</span><span class="token property-access">files</span> <span class="token operator">??</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
</span><span class="code-line line-number" line="5">
</span><span class="code-line line-number" line="6">    <span class="token keyword">const</span> webpFiles <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token known-class-name class-name">Promise</span><span class="token punctuation">.</span><span class="token method function property-access">all</span><span class="token punctuation">(</span>
</span><span class="code-line line-number" line="7">      files<span class="token punctuation">.</span><span class="token method function property-access">map</span><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span>file<span class="token punctuation">)</span> <span class="token arrow operator">=></span> <span class="token punctuation">(</span><span class="token punctuation">{</span>
</span><span class="code-line line-number" line="8">        fileName<span class="token operator">:</span> <span class="token function">parseFileName</span><span class="token punctuation">(</span>file<span class="token punctuation">,</span> req<span class="token punctuation">.</span><span class="token property-access">params</span><span class="token punctuation">.</span><span class="token property-access">planId</span><span class="token punctuation">,</span> <span class="token string">"webp"</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
</span><span class="code-line line-number" line="9">        buffer<span class="token operator">:</span> <span class="token keyword">await</span> <span class="token function">sharp</span><span class="token punctuation">(</span>file<span class="token punctuation">.</span><span class="token property-access">buffer</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token method function property-access">webp</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token method function property-access">toBuffer</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
</span><span class="code-line line-number" line="10">      <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
</span><span class="code-line line-number" line="11">    <span class="token punctuation">)</span><span class="token punctuation">;</span>
</span><span class="code-line line-number" line="12">
</span><span class="code-line line-number" line="13">    req<span class="token punctuation">.</span><span class="token property-access">files</span> <span class="token operator">=</span> webpFiles<span class="token punctuation">;</span>
</span><span class="code-line line-number" line="14">
</span><span class="code-line line-number" line="15">    <span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</span><span class="code-line line-number" line="16">  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
</span><span class="code-line line-number" line="17"><span class="token punctuation">]</span><span class="token punctuation">;</span>
</span></code></pre>
<p>sharp로 손실 압축을 하여 webp포멧으로 만들어 저장한다.<br>
이렇게 했을시 어느정도 압축이 되는지 살펴보면,<br>
아래는 핸드폰 에서 캡쳐사진이고 용량은 <code>978.5kb</code> 이다.</p>
<p><img
src="https://yanguk.github.io/250721/example.png"
alt="오늘의집"
width="200"
height="300"
className="my-4"
/></p>
<p>이 사진을 위같이 webp로 손실압축을 하면 <code>100.4kb</code>가 된다.<br>
<code>87.9%</code>가 압축된 거다.</p>
<p>효과는 확실한데 여기서 주의할 점이 있다.<br>
<code>sharp</code>는 cpu 자원을 많이 사용하기에 위같이 서버에서 다이렉트로 사용하긴 무리가 있다.<br>
그래서 최종적으로 적용한 방법은 서버에서는 원본을 저장하고,<br>
람다에서 sharp를 쓰는 방법으로 이미지를 최적화 하였다.</p>
<p><img src="https://yanguk.github.io/250721/image-flow.png" alt="image-flow"></p>
<h2 id="결론">결론<a aria-hidden="true" tabindex="-1" href="#결론"><span class="icon icon-link"></span></a></h2>
<p>결국엔 위 세가지 방법 모두 사용해야 되지 않나 싶은데,<br>
실용적인 관점에서는 우선 1번 부터 적용하고 필요성에 따라 2번 3번 순으로 적용하면 될 것 같다.</p>]]></content:encoded>
      </item>
  </channel>
</rss>