<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>dev-joy.log</title>
        <link>https://velog.io/</link>
        <description>Frontend developer, interested in Data.</description>
        <lastBuildDate>Mon, 13 Apr 2026 05:15:44 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>dev-joy.log</title>
            <url>https://images.velog.io/images/dev-joy/profile/79de3c1d-be0b-4342-b120-b3e07e23f3d2/social.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. dev-joy.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/dev-joy" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[OpenAPI 스펙으로 Type 자동화]]></title>
            <link>https://velog.io/@dev-joy/OpenAPI-%EC%8A%A4%ED%8E%99%EC%9C%BC%EB%A1%9C-Type-%EC%9E%90%EB%8F%99%ED%99%94</link>
            <guid>https://velog.io/@dev-joy/OpenAPI-%EC%8A%A4%ED%8E%99%EC%9C%BC%EB%A1%9C-Type-%EC%9E%90%EB%8F%99%ED%99%94</guid>
            <pubDate>Mon, 13 Apr 2026 05:15:44 GMT</pubDate>
            <description><![CDATA[<h2 id="orval"><a href="https://www.orval.dev/">Orval</a></h2>
<p>Generate clients with appropriate type signatures
Generate, validate, cache and mock in your frontend applications, based on your OpenAPI specification.</p>
<p>Orval은 단순히 API 호출 코드만 만드는 게 아니라, 프론트엔드 개발의 전 과정을 자동화하는 데 초점을 맞춘 도구입니다.</p>
<pre><code>주요 기능:

    생성(Generate): OpenAPI 명세서를 바탕으로 TypeScript 클라이언트를 자동으로 만듭니다.

    검증(Validate): 입력값이나 응답값이 올바른지 확인하는 로직을 포함합니다.

    캐싱(Cache): **TanStack Query (React Query)**와 같은 라이브러리와 연동하여 데이터 캐싱 및 상태 관리를 쉽게 도와줍니다.

    모킹(Mock): 테스트를 위해 가짜 데이터(MSW 등)를 자동으로 생성해 줍니다.

한 줄 요약: API 호출부터 데이터 관리, 테스트용 가짜 데이터 생성까지 한 번에 해결하고 싶은 프로젝트에 적합합니다.</code></pre><h2 id="hey-api"><a href="https://heyapi.dev/">Hey API</a></h2>
<p>OpenAPI to TypeScript in seconds.
Generate production-ready SDKs and validators from your OpenAPI spec. Used by Vercel, OpenCode, and PayPal.</p>
<p>&quot;몇 초 만에 OpenAPI를 TypeScript로&quot;</p>
<p>Hey API는 빠르고 가벼우며, 현업에서 즉시 사용할 수 있는 수준의 견고한 코드를 생성하는 데 특화되어 있습니다.</p>
<pre><code>주요 기능:

    프로덕션 준비(Production-ready): Vercel, PayPal 같은 대기업에서 사용할 정도로 안정적이고 최적화된 SDK(소프트웨어 개발 키트)를 생성합니다.

    검증기(Validators): OpenAPI 명세에 정의된 규칙대로 데이터가 들어오는지 체크하는 검증 로직을 만들어 줍니다.

    속도와 편의성: 복잡한 설정 없이도 몇 초 안에 TypeScript 환경에 맞는 코드를 뽑아냅니다.</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[React] Acitivity 컴포넌트 적용]]></title>
            <link>https://velog.io/@dev-joy/React-Acitivity-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EC%A0%81%EC%9A%A9</link>
            <guid>https://velog.io/@dev-joy/React-Acitivity-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EC%A0%81%EC%9A%A9</guid>
            <pubDate>Sun, 29 Mar 2026 13:23:50 GMT</pubDate>
            <description><![CDATA[<p><Activity>와 useLayoutEffect cleanup을 조합하면 intersection observer 없이도 비디오 pause를 깔끔하게 처리할 수 있다.</p>
<h2 id="before">Before</h2>
<pre><code class="language-tsx">import { useState, useCallback } from &#39;react&#39;;
import { useOnInView } from &#39;react-intersection-observer&#39;;

const [player, setPlayer] = useState&lt;YouTubePlayer | null&gt;(null);

const pauseVideo = useCallback(() =&gt; {
  if (player) {
    player.pauseVideo();
  }
}, [player]);

const trackingRef = useOnInView(
  (inView) =&gt; {
    if (inView) {
      // Element is in view - perhaps log an impression
    } else {
      pauseVideo();
    }
  },
  { threshold: 0.5, triggerOnce: true }
);

return (
  &lt;div ref={trackingRef} className={className}&gt;
    &lt;YouTube ... /&gt;
  &lt;/div&gt;
);</code></pre>
<h2 id="after">After</h2>
<pre><code class="language-tsx">import { Activity, useState, useLayoutEffect } from &#39;react&#39;;
import { useInView } from &#39;react-intersection-observer&#39;;

const [player, setPlayer] = useState&lt;YouTubePlayer | null&gt;(null);
const { ref, inView } = useInView({ threshold: 0.5 });

useLayoutEffect(() =&gt; {
  return () =&gt; {
    player?.pauseVideo();
  };
}, [player]);

return (
  &lt;div ref={ref} className={className}&gt;
    &lt;Activity mode={inView ? &#39;visible&#39; : &#39;hidden&#39;}&gt;
      &lt;YouTube ... /&gt;
    &lt;/Activity&gt;
  &lt;/div&gt;
);</code></pre>
<h2 id="비교">비교</h2>
<table>
<thead>
<tr>
<th>항목</th>
<th>Before</th>
<th>After</th>
</tr>
</thead>
<tbody><tr>
<td>비디오 pause 방식</td>
<td><code>useCallback</code> + <code>useOnInView</code> 콜백에서 수동 호출</td>
<td><code>useLayoutEffect</code> cleanup 자동 실행</td>
</tr>
<tr>
<td>뷰포트 이탈 처리</td>
<td><code>triggerOnce: true</code> (1회만)</td>
<td><code>&lt;Activity mode&gt;</code> 토글 (반복 동작)</td>
</tr>
<tr>
<td>DOM 상태</td>
<td>항상 렌더링</td>
<td>hidden 시 <code>display: none</code>, 상태 보존</td>
</tr>
<tr>
<td>Effect 관리</td>
<td>직접 관리</td>
<td>Activity가 hidden 시 cleanup, visible 시 재생성</td>
</tr>
</tbody></table>
<h2 id="동작-흐름">동작 흐름</h2>
<pre><code>스크롤 아웃 → inView=false
  → &lt;Activity mode=&quot;hidden&quot;&gt;
  → display: none 적용
  → useLayoutEffect cleanup 실행 → player.pauseVideo()

스크롤 인 → inView=true
  → &lt;Activity mode=&quot;visible&quot;&gt;
  → display: none 제거, 이전 상태 복원
  → useLayoutEffect 재생성</code></pre><h2 id="핵심-포인트">핵심 포인트</h2>
<ul>
<li>외부 <code>&lt;div ref={ref}&gt;</code>는 <code>&lt;Activity&gt;</code> 밖에 위치하여 intersection observer 센티넬 역할 유지</li>
<li><code>useLayoutEffect</code>를 사용하는 이유: 브라우저 paint 전에 동기적으로 실행되어 hidden 전환 시 오디오 누출 방지</li>
<li><code>&lt;Activity&gt;</code>는 DOM을 제거하지 않고 <code>display: none</code>으로 숨기므로 iframe 상태(버퍼링, 재생 위치)가 보존됨</li>
</ul>
<h2 id="참고">참고</h2>
<ul>
<li><a href="https://react.dev/reference/react/Activity">React Activity API</a></li>
<li><a href="https://www.mux.com/blog/react-is-changing-the-game-for-streaming-apps-with-the-activity-component">Mux Blog - Activity with Video Players</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[TanStack Virtual: 데스크탑에서만 발생하는 '흰 화면' (ScrollMargin & Lifecycle)]]></title>
            <link>https://velog.io/@dev-joy/TanStack-Virtual-%EB%8D%B0%EC%8A%A4%ED%81%AC%ED%83%91%EC%97%90%EC%84%9C%EB%A7%8C-%EB%B0%9C%EC%83%9D%ED%95%98%EB%8A%94-%ED%9D%B0-%ED%99%94%EB%A9%B4-ScrollMargin-Lifecycle</link>
            <guid>https://velog.io/@dev-joy/TanStack-Virtual-%EB%8D%B0%EC%8A%A4%ED%81%AC%ED%83%91%EC%97%90%EC%84%9C%EB%A7%8C-%EB%B0%9C%EC%83%9D%ED%95%98%EB%8A%94-%ED%9D%B0-%ED%99%94%EB%A9%B4-ScrollMargin-Lifecycle</guid>
            <pubDate>Tue, 10 Mar 2026 09:53:16 GMT</pubDate>
            <description><![CDATA[<p>이직한지 1달동안 고객이 보는 화면 성능 최적화를 보다가
900개의 리스트를 무한스크롤로 구현하셨는데
900개의 리스트가 모두 rendering되는 걸 발견했다.</p>
<p>급하게 만드시느라 DOM 가상화 부분을 안하셨나 생각해서
찾아보니 tanstack Virtural 라이브러리가 설치되어있었고 설정이 되어있었다.</p>
<p>하지만 무엇때문인지 적용되지 않고 있었다.
<code>useWindowVirtualizer</code>이 아니라 다른것을 사용하고 계셔서
사용자가 screen한 영역만 렌더링 되는것이 아니라 그 이외에 영역도  렌더링이 진행되고 있었다.</p>
<p>해당 hook을 사용하고 나니 모든게 렌더링 되지 않아서 원인을 살펴보니
최소 height가 없어서 그랬다. 그래서 처음 list에 보이는 12개의 productCard 높이인 800px을 주었다.</p>
<p>그 결과 하얀화면이 보이고 약간이라도 스크롤해야 list가 보였다.</p>
<p>가상화 라이브러리는 대량의 데이터를 효율적으로 보여주지만, <strong>&quot;보이는 영역만 그린다&quot;</strong>는 대전제 때문에 뷰포트 계산이 1px이라도 어긋나면 사용자에게 &#39;하얀 화면&#39;을 보여주는 치명적인 단점이 있습니다.</p>
<p>최근 프로젝트에서 TanStack Virtual(v3)을 사용하며 겪은 초기 렌더링 흰 화면 이슈와 이를 해결하기 위해 6차에 걸쳐 진행한 최적화 기록을 공유합니다.</p>
<h3 id="1-문제-현상-왜-살짝-스크롤해야-나타날까">1. 문제 현상: &quot;왜 살짝 스크롤해야 나타날까?&quot;</h3>
<p>모바일에서는 멀쩡한데, 데스크탑에서만 페이지 진입 시 목록이 비어 있다가 스크롤을 1px이라도 움직여야 상품이 나타나는 현상이 발생했습니다.</p>
<h3 id="2-원인-분석-가상화-엔진의-시차와-오판">2. 원인 분석: 가상화 엔진의 &#39;시차&#39;와 &#39;오판&#39;</h3>
<p>원인은 크게 세 가지였습니다.</p>
<pre><code>- Window Scroll 인식 오류: 컨테이너가 아닌 윈도우 스크롤을 사용함에도 가상화 인스턴스가 초기 위치(scrollMargin)를 0으로 오판함.

- 레이아웃 시프트(Layout Shift): 데스크탑의 Sticky 카테고리 바가 접히거나 펼쳐질 때 컨테이너의 절대 위치가 변하지만, ResizeObserver는 이를 감지하지 못함.

- 렌더링 타이밍(Race Condition): React의 상태 업데이트와 TanStack Virtual 내부 캐시 갱신 사이에 미세한 시차가 발생하여 transform 값이 음수로 튀는 현상.</code></pre><h3 id="3-해결-과정-6단계의-최적화">3. 해결 과정: 6단계의 최적화</h3>
<h4 id="1단계-usewindowvirtualizer-전환-및-initialrect-도입">1단계: useWindowVirtualizer 전환 및 initialRect 도입</h4>
<p>윈도우 레벨 스크롤을 정확히 감지하도록 전환하고, SSR 환경에서 뷰포트 높이가 0으로 잡혀 아이템이 아예 안 그려지는 문제를 막기 위해 initialRect fallback(800px)을 설정했습니다.</p>
<h4 id="2단계-state-vs-ref의-갈등">2단계: State vs Ref의 갈등</h4>
<p>scrollMargin을 useState로 관리하려 했으나, TanStack Virtual 내부의 virtualRow.start(캐시값)와 즉시 변하는 state 간의 타이밍 불일치로 리스트가 덜덜 떨리는 현상이 발생했습니다. 결국 Ref 기반 계산 + 강제 Re-render(Tick) 전략을 택했습니다.</p>
<h4 id="3단계-uselayouteffect로-paint-전-스크롤-제어">3단계: useLayoutEffect로 Paint 전 스크롤 제어</h4>
<p>카테고리 전환 시 useEffect를 쓰면 화면이 한 번 깜빡인 뒤 스크롤이 위로 올라갑니다. 이를 useLayoutEffect로 옮겨 브라우저가 화면을 그리기(Paint) 전에 스크롤 위치와 데이터를 동기적으로 교체했습니다.</p>
<h4 id="4단계-transitionend-이벤트-활용">4단계: transitionend 이벤트 활용</h4>
<p>데스크탑 Sticky 바의 애니메이션(grid-template-rows)이 끝나는 시점을 감지하도록 리스너를 추가했습니다. 애니메이션으로 인해 컨테이너 위치가 밀려나면 scrollMargin을 재계산하고 가상화 엔진을 강제로 깨웠습니다.</p>
<h4 id="5단계-초기-마운트-tick-트리거">5단계: 초기 마운트 &#39;Tick&#39; 트리거</h4>
<p>가장 결정적인 해결책이었습니다. 초기 마운트 시 scrollMargin이 계산되어도 리렌더링이 없으면 엔진은 0을 기준으로 계산합니다. setScrollMarginTick을 통해 마운트 직후 강제로 한 번 더 그리도록 하여 &#39;선 스크롤&#39; 문제를 해결했습니다.</p>
<h4 id="6단계-브라우저-전용-initialrect-분리">6단계: 브라우저 전용 initialRect 분리</h4>
<p>브라우저 환경에서도 가짜 높이(800px)를 주입하면 첫 계산이 틀어질 수 있습니다. SSR에서만 fallback을 쓰고, 클라이언트에서는 실제 window.innerHeight를 사용하도록 수정하여 정확도를 높였습니다.</p>
<h3 id="최종-결과물-핵심-로직">최종 결과물 (핵심 로직)</h3>
<pre><code class="language-ts">// scrollMargin 갱신 후 virtualizer 강제 재계산 로직
useLayoutEffect(() =&gt; {
  if (scrollMarginTick &gt; 0) {
    // 1. 아이템 사이즈 캐시 초기화
    virtualizer.measure();
    // 2. 가상화 엔진의 calculateRange를 강제 호출하기 위해 scroll 이벤트 dispatch
    // (TanStack Virtual은 scroll/resize 이벤트에서만 계산을 수행하기 때문)
    window.dispatchEvent(new Event(&#39;scroll&#39;));
  }
}, [scrollMarginTick]);</code></pre>
<h3 id="마치며-잔존-이슈와-교훈">마치며: 잔존 이슈와 교훈</h3>
<p>아직 overscan 기본값 설정이나 itemCount 의존성 최적화 같은 과제가 남아있지만, 이번 트러블슈팅을 통해 얻은 교훈은 명확합니다.</p>
<pre><code>&quot;가상화는 단순히 라이브러리를 쓰는 것이 아니라, 브라우저의 렌더링 사이클(Layout -&gt; Paint -&gt; Composite)과 엔진의 계산 주기를 동기화시키는 작업이다.&quot;</code></pre><p>특히 TanStack Virtual처럼 성능을 위해 많은 것을 내부 캐시에 위임하는 라이브러리를 쓸 때는, &#39;지금 리액트가 아는 값&#39;과 &#39;엔진이 아는 값&#39;이 일치하는지를 항상 의심해야 합니다.</p>
<pre><code>문제: 데스크탑 레이아웃 시프트로 인한 초기 흰 화면 및 스크롤 전 미출력.

해결:

    useWindowVirtualizer + scrollMargin Ref 관리.

    useLayoutEffect를 통한 동기적 위치 보정.

    transitionend 및 마운트 시점 강제 이벤트 디스패치.

결과: 모든 환경에서 깜빡임 없는 매끄러운 가상화 그리드 구현 완료.</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[Docker에서 NextJS 환경변수 ]]></title>
            <link>https://velog.io/@dev-joy/Docker%EC%97%90%EC%84%9C-NextJS-%ED%99%98%EA%B2%BD%EB%B3%80%EC%88%98</link>
            <guid>https://velog.io/@dev-joy/Docker%EC%97%90%EC%84%9C-NextJS-%ED%99%98%EA%B2%BD%EB%B3%80%EC%88%98</guid>
            <pubDate>Tue, 10 Feb 2026 04:20:22 GMT</pubDate>
            <description><![CDATA[<ol>
<li>GitHub Actions의 front-deploy.yml에서 Docker 빌드 전에 pnpm turbo run build를 실행합니다 (라인 128<del>163)
  2. 이 빌드 시점에 .env.production 파일이 읽히면서 NEXT_PUBLIC_* 변수가 빌드 타임에 코드에 인라인됩니다
  3. Docker deploy 타겟은 이미 빌드된 결과물만 복사합니다 (Dockerfile 라인 93</del>127)</li>
</ol>
<p>  즉, 흐름이 이렇습니다:</p>
<p>  .env.production (PostHog 키 포함, 이미 커밋됨)
      ↓
  pnpm turbo run build (빌드 타임에 NEXT_PUBLIC_* 인라인)
      ↓
  Docker deploy 타겟 (빌드된 파일만 복사, 런타임 env 불필요)</p>
<p>  Reddit/Medium 글에서 말하는 문제는 런타임에 NEXT_PUBLIC_ 변수를 주입하려 할 때 발생하는 건데, 이 프로젝트는 빌드
  타임에 .env.production에서 인라인하는 방식이라 해당되지 않습니다.</p>
<p>  .env.production에 PostHog 키가 이미 들어있으므로, 그대로 배포하면 됩니다.</p>
<p>참고:</p>
<ul>
<li><a href="https://nextjs.org/docs/pages/guides/environment-variables#bundling-environment-variables-for-the-browser">https://nextjs.org/docs/pages/guides/environment-variables#bundling-environment-variables-for-the-browser</a></li>
<li><a href="https://www.reddit.com/r/nextjs/comments/1gj7lry/next_public_env_vars_not_working_in_production/?tl=ko">https://www.reddit.com/r/nextjs/comments/1gj7lry/next_public_env_vars_not_working_in_production/?tl=ko</a></li>
<li><a href="https://carbonable.medium.com/deploying-a-next-js-app-with-next-public-environment-variables-with-docker-using-fly-io-1c0bfb3d999c">https://carbonable.medium.com/deploying-a-next-js-app-with-next-public-environment-variables-with-docker-using-fly-io-1c0bfb3d999c</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Github에서 취약점 자동 검사]]></title>
            <link>https://velog.io/@dev-joy/Github%EC%97%90%EC%84%9C-%EC%B7%A8%EC%95%BD%EC%A0%90-%EC%9E%90%EB%8F%99-%EA%B2%80%EC%82%AC</link>
            <guid>https://velog.io/@dev-joy/Github%EC%97%90%EC%84%9C-%EC%B7%A8%EC%95%BD%EC%A0%90-%EC%9E%90%EB%8F%99-%EA%B2%80%EC%82%AC</guid>
            <pubDate>Mon, 02 Feb 2026 00:59:39 GMT</pubDate>
            <description><![CDATA[<p>.github/dependabot.yml</p>
<pre><code class="language-shell">version: 2
updates:
  - package-ecosystem: &quot;npm&quot;
    directory: &quot;/frontend&quot;
    target-branch: &quot;main&quot;
    schedule:
      interval: &quot;weekly&quot;
      day: &quot;monday&quot;
    open-pull-requests-limit: 10
    labels:
      - &quot;dependencies&quot;

  - package-ecosystem: &quot;gradle&quot;
    directory: &quot;/backend&quot;
    target-branch: &quot;main&quot;
    schedule:
      interval: &quot;weekly&quot;
      day: &quot;monday&quot;
    open-pull-requests-limit: 10
    labels:
      - &quot;dependencies&quot;

  - package-ecosystem: &quot;github-actions&quot;
    directory: &quot;/&quot;
    target-branch: &quot;main&quot;
    schedule:
      interval: &quot;weekly&quot;
      day: &quot;monday&quot;
    open-pull-requests-limit: 5
    labels:
      - &quot;dependencies&quot;</code></pre>
<p>설정 설명:</p>
<ul>
<li>directory: /frontend - frontend 디렉토리의 npm 의존성 모니터링</li>
<li>target-branch: main - Dependabot PR이 main 브랜치를 대상으로 생성됨</li>
<li>schedule: 매주 월요일마다 자동으로 의존성 체크</li>
<li>open-pull-requests-limit: 동시에 열 수 있는 PR의 최대 개수 10개</li>
<li>labels: PR에 &quot;dependencies&quot; 라벨 자동 추가</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[NextJS 15 Dyanamic Routes]]></title>
            <link>https://velog.io/@dev-joy/NextJS-15-Dyanamic-Routes</link>
            <guid>https://velog.io/@dev-joy/NextJS-15-Dyanamic-Routes</guid>
            <pubDate>Wed, 23 Apr 2025 05:17:28 GMT</pubDate>
            <description><![CDATA[<h1 id="nextjs-15-부터-바뀐-방식">Next.js 15 부터 바뀐 방식</h1>
<h2 id="1-asyncawait">1) <a href="https://nextjs.org/docs/app/building-your-application/routing/dynamic-routes#example">async/await</a></h2>
<p>params의 Type은 Promise이다.</p>
<pre><code class="language-ts">export default async function Page({
  params,
}: {
  params: Promise&lt;{ slug: string }&gt;
}) {
  const slug = (await params).slug
  return &lt;div&gt;My Post: {slug}&lt;/div&gt;
}</code></pre>
<h2 id="2-useparams">2) <a href="https://nextjs.org/docs/app/api-reference/functions/use-params">useParams</a></h2>
<pre><code class="language-ts">&#39;use client&#39;

import { useParams } from &#39;next/navigation&#39;

export default function ExampleClientComponent() {
  const params = useParams&lt;{ tag: string; item: string }&gt;()

  // Route -&gt; /shop/[tag]/[item]
  // URL -&gt; /shop/shoes/nike-air-max-97
  // `params` -&gt; { tag: &#39;shoes&#39;, item: &#39;nike-air-max-97&#39; }
  console.log(params)

  return &#39;...&#39;
}</code></pre>
<p>참고</p>
<ul>
<li><a href="https://nextjs.org/docs/app/guides/upgrading/version-15#params--searchparams">https://nextjs.org/docs/app/guides/upgrading/version-15#params--searchparams</a></li>
<li><a href="https://nextjs.org/docs/app/building-your-application/routing/dynamic-routes#convention">https://nextjs.org/docs/app/building-your-application/routing/dynamic-routes#convention</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Error해결] TailwindCSS v4 plugin 적용방법]]></title>
            <link>https://velog.io/@dev-joy/Error%ED%95%B4%EA%B2%B0-TailwindCSS-v4-plugin-%EC%A0%81%EC%9A%A9%EB%B0%A9%EB%B2%95</link>
            <guid>https://velog.io/@dev-joy/Error%ED%95%B4%EA%B2%B0-TailwindCSS-v4-plugin-%EC%A0%81%EC%9A%A9%EB%B0%A9%EB%B2%95</guid>
            <pubDate>Thu, 17 Apr 2025 07:46:59 GMT</pubDate>
            <description><![CDATA[<p>NextJS 블로그를 만들다가 <a href="https://github.com/remarkjs/react-markdown">react-markdown</a>에서는 <a href="https://github.com/tailwindlabs/tailwindcss-typography">tailwindcss-typography</a>가 적용되어야 하는데 적용방법을 ChatGPT한테 물어봤다.</p>
<p>수동으로 tailwind 설정파일을 만들라고 되어있었는데</p>
<pre><code class="language-ts">import type { Config } from &#39;tailwindcss&#39;

const config: Config = {
  content: [
    &#39;./pages/**/*.{js,ts,jsx,tsx}&#39;,
    &#39;./components/**/*.{js,ts,jsx,tsx}&#39;,
    &#39;./app/**/*.{js,ts,jsx,tsx}&#39;, // MDX 페이지 포함
  ],
  theme: {
    extend: {},
  },
  plugins: [
    require(&#39;@tailwindcss/typography&#39;),
  ],
}

export default config</code></pre>
<h2 id="tailwindcss-v4-부터는-tailwindconfigjsts-파일이-사용되지-않는다">Tailwindcss v4 부터는 tailwind.config.js(ts) 파일이 사용되지 않는다!!</h2>
<p>그래서 찾다가 NextJS(15.3.0) 에서 app/globals.css에서 
<code>@plugin</code>을 이용하니 작동이 되었다.</p>
<pre><code class="language-css">@import &#39;tailwindcss&#39;;
@plugin &#39;@tailwindcss/typography&#39;;</code></pre>
<p><a href="https://tailwindcss.com/blog/tailwindcss-v4#css-first-configuration">tailwindcss-v4 css-first-configuration</a></p>
<blockquote>
<p>Instead of a tailwind.config.js file, you can configure all of your customizations directly in the CSS file where you import Tailwind, giving you one less file to worry about in your project:
<br/>
tailwind.config.js 파일 대신 Tailwind를 가져오는 CSS 파일에서 모든 사용자 지정을 직접 구성할 수 있어 프로젝트에서 걱정할 파일이 하나 줄어듭니다:</p>
</blockquote>
<blockquote>
<p>The new CSS-first configuration lets you do just about everything you could do in your tailwind.config.js file, including configuring your design tokens, defining custom utilities and variants, and more 
<br/>
새로운 CSS 우선 설정을 통해 tailwind.config.js 파일에서 할 수 있는 거의 모든 작업을 수행할 수 있습니다. 여기에는 디자인 토큰 구성, 사용자 지정 유틸리티 및 변형 정의 등이 포함됩니다</p>
</blockquote>
<p>참고사이트</p>
<ul>
<li><a href="https://github.com/tailwindlabs/tailwindcss/discussions/13292">https://github.com/tailwindlabs/tailwindcss/discussions/13292</a></li>
<li><a href="https://www.reddit.com/r/tailwindcss/comments/1i1whrk/write_plugin_in_tailwind_40/">https://www.reddit.com/r/tailwindcss/comments/1i1whrk/write_plugin_in_tailwind_40/</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[통계학과생이 추천하는 책]]></title>
            <link>https://velog.io/@dev-joy/%ED%86%B5%EA%B3%84%ED%95%99%EA%B3%BC%EC%83%9D%EC%9D%B4-%EC%B6%94%EC%B2%9C%ED%95%98%EB%8A%94-%EC%B1%85</link>
            <guid>https://velog.io/@dev-joy/%ED%86%B5%EA%B3%84%ED%95%99%EA%B3%BC%EC%83%9D%EC%9D%B4-%EC%B6%94%EC%B2%9C%ED%95%98%EB%8A%94-%EC%B1%85</guid>
            <pubDate>Fri, 01 Apr 2022 05:21:35 GMT</pubDate>
            <description><![CDATA[<p>학부 시절 교수님께서 추천해주셨던 책들 중 몇 권을 소개해볼려고 한다.</p>
<h2 id="통계학의-피카소는-누구일까-20세기-과학혁명을-이끈-통계학-영웅들의-이야기"><a href="http://www.kyobobook.co.kr/product/detailViewKor.laf?ejkGb=KOR&amp;mallGb=KOR&amp;barcode=9788973388547&amp;orderClick=LAG&amp;Kc=">통계학의 피카소는 누구일까: 20세기 과학혁명을 이끈 통계학 영웅들의 이야기</a></h2>
<p><img src="https://media.vlpt.us/images/dev-joy/post/92328ea8-1c02-40f8-8c44-c10f6b418cee/%ED%86%B5%EA%B3%84%ED%95%99%EC%9D%98%20%ED%94%BC%EC%B9%B4%EC%86%8C%EB%8A%94%20%EB%88%84%EA%B5%AC%EC%9D%BC%EA%B9%8C.jpg" alt=""></p>
<p><a href="http://www.kyobobook.co.kr/product/detailViewEng.laf?ejkGb=BNT&amp;mallGb=ENG&amp;barcode=9780805071344&amp;orderClick=LAG&amp;Kc=">Lady Tasting Tea : How Statistics Revolutionized Science in the Twentieth Century</a></p>
<blockquote>
<p>칼 피어슨, 고셋, 피셔, 네이만, 이곤 피어슨, 콜모고로프, 튜키, 마할라노비스, 크레이머, 왈드, 코크란, 박스, 콕스, 튜키 등의 통계를 공부하다보면 한번 쯤은 봤을 이름들이 등장한다.</p>
<p>통계학자들이 이론을 만든 역사를 간략히 알 수 있어서 좋은 책이었다!!</p>
</blockquote>
<h2 id="불멸의-이론-베이즈-정리는-어떻게-250년-동안-불확실한-세상을-지배하였는가"><a href="http://www.kyobobook.co.kr/product/detailViewKor.laf?ejkGb=KOR&amp;mallGb=KOR&amp;barcode=9788958626190&amp;orderClick=LAG&amp;Kc=">불멸의 이론 베이즈 정리는 어떻게 250년 동안 불확실한 세상을 지배하였는가</a></h2>
<p><img src="https://media.vlpt.us/images/dev-joy/post/e19d45cc-2bc9-49af-bfc3-1341ce29dd79/%EB%B6%88%EB%A9%B8%EC%9D%98%20%EC%9D%B4%EB%A1%A0.jpg" alt=""></p>
<p><a href="http://www.kyobobook.co.kr/product/detailViewEng.laf?ejkGb=BNT&amp;mallGb=ENG&amp;barcode=9780300188226&amp;orderClick=LAG&amp;Kc=">The Theory That Would Not Die How Bayes&#39; Rule Cracked the Enigma Code, Hunted Down Russian Submarines, and Emerged Triumphant from Two Centuries of</a></p>
<blockquote>
<p>확률을 해석하는 두 가지 관점으로 빈도주의 학파 vs 베이즈학파로 나눌 수 있는 것 같다.   </p>
</blockquote>
<table>
<thead>
<tr>
<th align="left"></th>
<th align="left">빈도주의 학파</th>
<th align="center">베이즈 학파</th>
</tr>
</thead>
<tbody><tr>
<td align="left">확률</td>
<td align="left">빈도</td>
<td align="center">믿음의 정도(사전확률과 사후확률)</td>
</tr>
</tbody></table>
<blockquote>
<p>처음 베이즈 이론이 나온 것도 토마스 베이즈가 신의 존재를 증명으로 베이즈 이론이 시작되었다.
The Naive Bayesian Algorithm-based Prisoner’s Dilemma Game Model 
위의 학술자료를 읽어보면 빈도주의보다 베이즈 이론이 더 효과적이라는 생각이 든다.</p>
</blockquote>
]]></description>
        </item>
    </channel>
</rss>