<?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/" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>frontend.run</title>
    <link>https://tech-blog-six-phi.vercel.app</link>
    <description>코드를 짜다 마주친 궁금한 것들을 하나씩 따라가며 정리하는 프론트엔드 기록. React, CSS, 웹 표준, 성능과 접근성 노트를 다룹니다.</description>
    <language>ko</language>
    <dc:creator>Zero-1016</dc:creator>
    <lastBuildDate>Sun, 17 May 2026 00:00:00 GMT</lastBuildDate>
    <atom:link href="https://tech-blog-six-phi.vercel.app/feed.xml" rel="self" type="application/rss+xml" />
    <item>
      <title><![CDATA[use 는 상태 훅이 아니에요]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/react-use-resource-not-state</link>
      <description><![CDATA[useContext 가 if 안에서 막힐 때 use 가 풀어주는 자리예요]]></description>
      <content:encoded><![CDATA[처음 React 19 의 use 를 봤을 때는 "또 새 훅이구나" 정도로 흘려봤어요. 그러다 어떤 분이 "use 는 상태 공유가 아니라 의존성 공유를 위한 도구" 라는 말을 해줬는데, 묘하게 정확한 표현이더라고요. use 는 useState 의 친척이 아니라 await 의 React 판이에요. use 는 상태가 아니라 자원을 읽어요 React 공식 19 발표는 use 를 한 줄로 소개해요. 렌더 중에 자원을 읽는 새 API 라고요. "In React 19 we're introducing a]]></content:encoded>
      <pubDate>Sun, 17 May 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/react-use-resource-not-state</guid>
      <category>React</category>
      <category>Hooks</category>
      <category>Suspense</category>
      <category>RSC</category>
    </item>
    <item>
      <title><![CDATA[aria-live 가 안 들리는 진짜 이유]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/aria-live-regions-when-silent</link>
      <description><![CDATA[빈 영역을 먼저 두고 텍스트만 갈아 끼워야 토스트가 들리기 시작해요.]]></description>
      <content:encoded><![CDATA[폼 검증에 실패해서 빨간 토스트가 떴어요. 시각 사용자에게는 메시지가 보이지만, 스크린 리더 사용자에게는 아무 일도 일어나지 않은 것처럼 조용하죠. 라이브 리전은 포커스를 빼앗지 않으면서 변화를 알리는 채널이고, 토스트가 안 들리는 대부분의 이유는 안내 메시지를 채운 채로 한 번에 DOM 에 끼워 넣었기 때문이에요. 토스트가 안 읽혀요 폼 제출 후 "비밀번호가 일치하지 않습니다" 같은 빨간 박스를 화면 오른쪽 위에 페이드인하는 패턴, 익숙하시죠. 시각 사용자에게는 알림이 잘 도착해요. 스크린]]></content:encoded>
      <pubDate>Fri, 15 May 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/aria-live-regions-when-silent</guid>
      <category>접근성</category>
      <category>ARIA</category>
      <category>스크린 리더</category>
      <category>웹 표준</category>
    </item>
    <item>
      <title><![CDATA[useEffect vs useLayoutEffect]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/useeffect-vs-uselayouteffect-decision</link>
      <description><![CDATA[useLayoutEffect 를 언제 꺼내야 할지 결정 트리로 정리해봐요.]]></description>
      <content:encoded><![CDATA[버튼 옆에 떠야 할 툴팁이 화면 끝에 닿는 순간을 본 적 있어요. 위치를 다시 재서 위로 올라가긴 하는데, 그 사이에 한 프레임 정도 잘못된 자리에서 깜빡이고 들어가요. 두 훅 중 어느 쪽을 쓰는지로 결정되는 장면이에요. React 의 useEffect 가 기본값이고, useLayoutEffect 는 페인트 전 측정이 화면에 보일 때만 꺼냅니다. 툴팁이 화면 가장자리에서 잘릴 때 툴팁 컴포넌트를 만들 때는 보통 트리거 버튼 옆에 띄워요. 근데 버튼이 화면 오른쪽 끝에 있으면 그대로 띄울 수]]></content:encoded>
      <pubDate>Wed, 13 May 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/useeffect-vs-uselayouteffect-decision</guid>
      <category>React</category>
      <category>Hooks</category>
      <category>렌더링</category>
    </item>
    <item>
      <title><![CDATA[한 도메인이 갈라지는 자리들]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/frontend-load-balancing-layers</link>
      <description><![CDATA[같은 주소를 쳐도 사람마다 다른 서버에 닿을 수 있어요. DNS와 CDN 거점까지, 어디서 갈라지는지만 따라가요.]]></description>
      <content:encoded><![CDATA[이 글의 한 가지 축만 기억해도 돼요. 같은 도메인인데도 사용자마다 다른 서버가 응답할 수 있다는 것. 나머지 용어는 전부 그걸 설명하는 재료예요. 먼저 장면을 떠올려 볼게요 한국에 있는 사람과 독일에 있는 사람이 똑같이 example.com 을 열었다고 해볼게요. 둘 다 주소창에는 같은 글자가 찍혀 있어요. 그런데 첫 패킷이 향한 거점은 다를 수 있어요. DNS 가 돌려준 힌트가 다를 수도 있고, 같은 주소를 여러 곳에서 동시에 받아들이는 방식(이전 글에서 다룬 Anycast 흐름) 때문에]]></content:encoded>
      <pubDate>Sun, 10 May 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/frontend-load-balancing-layers</guid>
      <category>네트워크</category>
      <category>CDN</category>
      <category>DNS</category>
      <category>성능</category>
      <category>Edge</category>
    </item>
    <item>
      <title><![CDATA[children 으로 받을까, prop 으로 받을까]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/children-vs-render-props</link>
      <description><![CDATA[두 패턴이 비슷해 보일 때, 어디서 갈리는지 짚어볼게요.]]></description>
      <content:encoded><![CDATA[컴포넌트를 만들다 보면 매번 한 번씩 멈추는 자리가 있어요. "이건 children 으로 받지, 아니 prop 으로 받지." 손가락이 키보드 위에서 잠깐 머무르는 그 자리요. 모달 같은 컴포넌트를 만들다가도, List 같은 컴포넌트를 만들다가도 똑같이 막혀요. 비슷한 일을 하는 것 같은데 막상 고르려면 헷갈리거든요. children 은 구멍을 만든다 children 은 사실 prop 의 한 종류예요. 다만 전달 방식이 좀 달라요. JSX 태그 안에 끼워 넣은 콘텐츠가 부모 컴포넌트에게]]></content:encoded>
      <pubDate>Sat, 09 May 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/children-vs-render-props</guid>
      <category>React</category>
      <category>컴포넌트 설계</category>
      <category>합성</category>
      <category>패턴</category>
    </item>
    <item>
      <title><![CDATA[cloneElement 가 자꾸 as any 를 부르던 이유]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/clonelement-typescript-pitfalls</link>
      <description><![CDATA[children 에 props 를 꽂으려는 순간, React 19 의 unknown 이 막아서요.]]></description>
      <content:encoded><![CDATA[처음 Tabs 컴포넌트를 만들 때, 자식 Tab 마다 활성 여부를 어떻게 내려줄지 고민했어요. children 으로 받았으니 각각에 active prop 을 슬쩍 꽂아주면 될 것 같았죠. 그래서 손이 갔던 게 cloneElement 였어요. 한동안 잘 굴러갔는데, React 19 로 올린 어느 날 갑자기 빨간 줄이 떴거든요. as any 로 덮으면서 "왜 이게 이제 와서 안 되지" 싶었던 그 자리부터 다시 봐요. 익숙한 패턴, children 에 props 를 주입하기 cloneElement 를]]></content:encoded>
      <pubDate>Sat, 09 May 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/clonelement-typescript-pitfalls</guid>
      <category>React</category>
      <category>TypeScript</category>
      <category>cloneElement</category>
      <category>children</category>
      <category>타입 시스템</category>
    </item>
    <item>
      <title><![CDATA[쿠키는 왜 매 요청마다 따라다닐까]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/why-cookies-follow-every-request</link>
      <description><![CDATA[HTTP 가 망각하기로 한 자리에 쿠키가 자동으로 따라붙는 이유예요]]></description>
      <content:encoded><![CDATA[브라우저 DevTools 의 Network 탭을 띄워두고 같은 사이트 안에서 이리저리 클릭해보면, 매 요청 헤더 자리에 똑같은 Cookie: 줄이 따라붙어요. 큰 HTML 응답을 받을 때도 그렇고, 잠깐 스치는 작은 png 한 장을 받을 때도 똑같죠. "이 작은 이미지 한 장에 내 세션이 왜 따라가지?" 한 번쯤 그렇게 의아했던 적 있을 거예요. 알고 보면 그 자동성은 MDN 이 정리한 HTTP cookies 가이드 가 정확히 약속한 그림이거든요. 매번 따라가는 게 아니라, 따라가도록 합의해둔]]></content:encoded>
      <pubDate>Sat, 09 May 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/why-cookies-follow-every-request</guid>
      <category>네트워크</category>
      <category>HTTP</category>
      <category>쿠키</category>
      <category>브라우저</category>
      <category>보안</category>
    </item>
    <item>
      <title><![CDATA[AbortSignal 은 fetch 만 보고 만들지 않았어요]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/abort-signal-beyond-fetch</link>
      <description><![CDATA[fetch 만 쓰다 보면 신호가 어디까지 닿는지 잘 안 보이거든요.]]></description>
      <content:encoded><![CDATA[Promise 의 동시성과 비용은 자주 짚는데, 정작 '취소' 는 거의 안 다루죠. 저도 한참 동안 AbortController 를 'fetch 끊는 거' 로만 알고 살았거든요. 그러다 addEventListener 의 옵션에서 signal 을 보고 멈칫했어요. 알고 보니 이건 fetch 만 위해 만든 게 아니라, 비동기 작업 일반에 외부 신호로 종료를 알리는 표준 통로였거든요. fetch 만 보고 만든 멘탈 모델 MDN 의 AbortController 페이지를 처음 본 사람의 십중팔구는]]></content:encoded>
      <pubDate>Fri, 08 May 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/abort-signal-beyond-fetch</guid>
      <category>JavaScript</category>
      <category>비동기</category>
      <category>AbortController</category>
      <category>React</category>
    </item>
    <item>
      <title><![CDATA[또 모달 만드시려고요?]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/dialog-and-popover-modal-as-standard</link>
      <description><![CDATA[매번 새로 만들던 모달, 브라우저가 어디까지 처리해주는지 다시 점검해봐요.]]></description>
      <content:encoded><![CDATA["또 모달 만들어야 해요?" 팀에 합류하면 비슷한 광경을 자주 봐요. 디자인 시스템에 Modal 컴포넌트가 이미 있는데, 누군가 새 페이지에서 슬쩍 다시 만들고 있어요. "그게 z-index 가 안 먹혀서요." "포커스가 자꾸 뒤로 빠져서요." "ESC 안 듣는다고 QA 가 코멘트 달았어요." 이상하죠. 모달은 웹에서 가장 흔한 컴포넌트인데, 매번 누군가 새로 만들고 있어요. 브라우저는 그동안 뭐 했나 싶고요. 사실 그동안 채워지고 있었어요. 이제는 모든 메이저 브라우저에서 안정적으로 쓸 수]]></content:encoded>
      <pubDate>Fri, 08 May 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/dialog-and-popover-modal-as-standard</guid>
      <category>HTML</category>
      <category>웹 표준</category>
      <category>접근성</category>
      <category>Popover API</category>
    </item>
    <item>
      <title><![CDATA[화면을 닮은 모양으로 코드 두기]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/screen-code-one-to-one-mapping</link>
      <description><![CDATA[화면 한 점을 짚는데 코드는 네 층을 내려가야 할 때, 어디서 어긋난 건지 짚어요]]></description>
      <content:encoded><![CDATA[디자인 검토 자리에서 "이 카드 오른쪽 위에 작은 배지 하나만 추가해주세요" 한 줄을 들었어요. 화면을 보고 있을 땐 답이 너무 명확했죠. "아, 저기." 그런데 에디터를 열면 갑자기 막막해지더라고요. ProductSection 안에 들어가서 ItemList 를 거쳐 ItemRow 를 찾고, 그 안의 ItemMeta 까지 내려가야 그 자리가 나오거든요. 화면에서 한 점을 짚는 데 코드 네 층을 내려가야 한다면, 그 코드는 화면과 다른 모양으로 누워 있는 거예요. 화면을 보고 어디부터 손대지 지난]]></content:encoded>
      <pubDate>Fri, 01 May 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/screen-code-one-to-one-mapping</guid>
      <category>관심사 분리</category>
      <category>응집도</category>
      <category>컴포넌트 설계</category>
      <category>리팩터링</category>
    </item>
    <item>
      <title><![CDATA[SWR과 TanStack Query 가 다른 이유]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/swr-vs-tanstack-query-philosophy</link>
      <description><![CDATA[같은 데이터 페칭처럼 보여도 두 라이브러리의 자기 소개부터 톤이 달라요.]]></description>
      <content:encoded><![CDATA[새 React 프로젝트에서 데이터 페칭 라이브러리를 고르려고 SWR 과 TanStack Query 의 첫 페이지를 동시에 열었어요. 둘 다 stale-while-revalidate 라는 같은 동작을 한다는 말을 들어서 비슷한 자리에서 비슷한 일을 하는 줄 알았거든요. 그런데 자기 소개부터 톤이 달라요. 그 차이가 디폴트 동작과 mutation API 모양에까지 그대로 이어지더라고요. 자기 소개부터 다르다 SWR 의 홈은 자기를 "Modern data fetching, built for]]></content:encoded>
      <pubDate>Fri, 01 May 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/swr-vs-tanstack-query-philosophy</guid>
      <category>React</category>
      <category>데이터 페칭</category>
      <category>SWR</category>
      <category>TanStack Query</category>
    </item>
    <item>
      <title><![CDATA[CDN이 가까이 있다는 건 무슨 뜻일까]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/cdn-speed-and-caching</link>
      <description><![CDATA[CDN을 붙였더니 빨라졌는데 왜 빨라졌는지는 막연한 순간이 있어요.]]></description>
      <content:encoded><![CDATA[처음 서비스에 CDN을 붙여봤을 때 솔직히 뭐가 빨라진 건지 와닿지 않았어요. dist/ 폴더에 들어 있던 파일이 다른 호스트에서 내려올 뿐인데, LCP가 700ms 줄어들고 origin 서버 CPU 그래프가 절반으로 떨어졌거든요. 그 빠름은 사실 두 가지가 동시에 작동해서 만들어집니다. 거리가 짧아지는 게 절반, 두 번째 요청부턴 멀리 다녀오지 않는 게 나머지 절반이에요. 요청은 어디까지 다녀오는가 한국에서 미국 동부 origin까지 한 번 갔다 오는 동안 패킷은 해저 케이블을 두 번 건너요.]]></content:encoded>
      <pubDate>Thu, 30 Apr 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/cdn-speed-and-caching</guid>
      <category>성능</category>
      <category>CDN</category>
      <category>HTTP</category>
      <category>캐싱</category>
    </item>
    <item>
      <title><![CDATA[두 stale-while-revalidate 의 자리]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/stale-while-revalidate-two-layers</link>
      <description><![CDATA[응답 헤더의 SWR 과 Service Worker 의 SWR 패턴이 헷갈렸다면 두 자리부터 나눠보세요]]></description>
      <content:encoded><![CDATA[stale-while-revalidate 라는 이름을 두 군데에서 봤어요. 한 번은 Cache-Control: max-age=600, stale-while-revalidate=30 같은 응답 헤더 자리에서, 또 한 번은 Service Worker 캐싱 가이드의 "SWR 패턴" 이라는 설명에서요. 같은 이름이라서 같은 동작이겠지 싶었는데, 막상 헤더만 박아놓고 SW 패턴을 따로 짜는 글이 있어서 한참을 헤맸거든요. 둘은 일하는 곳이 달라요. 한쪽은 HTTP 응답 헤더로 캐시에 권한을 넘겨주고,]]></content:encoded>
      <pubDate>Thu, 30 Apr 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/stale-while-revalidate-two-layers</guid>
      <category>성능</category>
      <category>브라우저</category>
      <category>캐싱</category>
      <category>Service Worker</category>
      <category>HTTP</category>
    </item>
    <item>
      <title><![CDATA[Promise.all 의 진짜 비용]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/promise-concurrency-and-fan-out</link>
      <description><![CDATA[Promise.all 의 효과는 JS 한 줄 안쪽에서만 보장돼요.]]></description>
      <content:encoded><![CDATA[두 API 응답을 합쳐 화면 하나를 그려야 했던 적이 있어요. 처음엔 await 두 줄로 나눠 받았는데, "두 번째 응답이 첫 번째를 기다릴 이유가 없는데" 싶어서 Promise.all 로 묶었거든요. 체감 속도가 두 배 가까이 빨라졌어요. 거기까지는 좋았는데, 갯수를 30개로 늘렸더니 Network 탭의 막대들이 일곱 번째부터 줄을 서기 시작하더라고요. JS 코드는 동시에 보낸다고 했는데 wire 위에서는 그게 아니었던 거예요. Promise.all 옆에 셋이 더 있어요 MDN 의]]></content:encoded>
      <pubDate>Wed, 29 Apr 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/promise-concurrency-and-fan-out</guid>
      <category>JavaScript</category>
      <category>비동기</category>
      <category>Promise</category>
      <category>HTTP</category>
      <category>네트워크</category>
    </item>
    <item>
      <title><![CDATA[Service Worker 는 네트워크 프록시예요]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/service-worker-network-proxy</link>
      <description><![CDATA[같은 워커 이름인데 둘은 사는 곳이 달랐거든요.]]></description>
      <content:encoded><![CDATA[워커 이름이 들어간 표준이 둘이라서 처음엔 같은 거라고 생각했어요. Web Worker 가 무거운 계산을 옆 스레드로 보낸다는 건 메인 스레드가 멈추면 클릭도 멈춰요 글에서 정리했는데, Service Worker 도 그 친척이려니 했거든요. 근데 코드를 한 줄 들여다보니 둘은 완전히 다른 자리에 사는 워커였어요. 같은 워커인데 왜 이렇게 다를까 W3C 명세는 한 줄로 못박아 둬요. "A service worker is a type of web worker." - W3C Service]]></content:encoded>
      <pubDate>Wed, 29 Apr 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/service-worker-network-proxy</guid>
      <category>Service Worker</category>
      <category>PWA</category>
      <category>브라우저</category>
      <category>네트워크</category>
      <category>캐시</category>
    </item>
    <item>
      <title><![CDATA[Zustand 와 Jotai, 어디서 갈리나]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/zustand-jotai-top-down-bottom-up</link>
      <description><![CDATA[큰 store 를 자르거나, 작은 atom 을 합치거나. 두 라이브러리는 출발점이 정반대예요.]]></description>
      <content:encoded><![CDATA[Redux 를 한참 쓰다가 Zustand 로 갈아탔을 때 저는 "이게 더 가벼운 redux 구나" 정도로 받아들였어요. 그러다 다른 팀에서 Jotai 를 쓴다는 얘기를 듣고 코드를 처음 봤는데, atom 을 import 하고 useAtom 으로 꺼내 쓰는데 store 가 어디 있는지 안 보이더라고요. 머릿속에서 그림이 안 그려지는 거예요. 그때 알았어요. 이 둘은 도구의 무게 차이가 아니라 사상 자체가 다른 친구들이라는 걸요. 같은 문제, 다른 출발점 React 의 Context 만으로는 안]]></content:encoded>
      <pubDate>Wed, 29 Apr 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/zustand-jotai-top-down-bottom-up</guid>
      <category>React</category>
      <category>상태관리</category>
      <category>Zustand</category>
      <category>Jotai</category>
    </item>
    <item>
      <title><![CDATA[선언적인 코드라는 말이 어렵게 느껴질 때]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/declarative-readable-code</link>
      <description><![CDATA['선언적이라 가독성 좋다' 와 '더 어렵다' 가 갈리는 진짜 이유를 짚어요]]></description>
      <content:encoded><![CDATA[React 코드를 두고 누구는 “선언적이라 읽기 쉽다”고 하고, 누구는 “추상화만 늘어서 더 어렵다”고 말해요. 둘 다 틀린 말은 아니에요. 문제는 사람들이 “선언적”이라는 단어를 서로 다른 뜻으로 쓰고 있기 때문이에요. 같은 코드인데 의견이 갈리는 이유 흔한 예시 하나가 조건부 렌더링이에요. 같은 결과를 만드는 두 코드가 이렇게 갈리거든요. // A 방식: && 또는 삼항 {isLoggedIn && <UserMenu />} // B 방식: <If> 같은 선언적 컴포넌트 <If]]></content:encoded>
      <pubDate>Tue, 28 Apr 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/declarative-readable-code</guid>
      <category>가독성</category>
      <category>선언형</category>
      <category>코드 품질</category>
      <category>리팩터링</category>
    </item>
    <item>
      <title><![CDATA[sum 함수에 async를 더해봐요]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/promise-wrapping-microtask-cost</link>
      <description><![CDATA[async 한 줄로 결과가 한 틱 늦게 오는 이유를 짚어요.]]></description>
      <content:encoded><![CDATA[평범한 sum 함수에 await 한 줄을 더하면 콘솔이 의외의 순서로 찍혀요. 같은 5 인데, 찍히는 시점이 한 박자 늦거든요. 처음엔 "기다릴 것도 없는데 뭘 기다린 거지" 싶었어요. 답은 Promise 가 값을 감싸는 방식, 그리고 await 가 그 값을 꺼내는 타이밍에 있어요. Sum 하나에 await 한 줄을 더하면 먼저 동기 sum 과 asyncSum 을 나란히 놓고 볼게요. 본문 로직은 똑같이 두 수를 더해서 돌려주는 거예요. function sum(a, b) { return a +]]></content:encoded>
      <pubDate>Tue, 28 Apr 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/promise-wrapping-microtask-cost</guid>
      <category>JavaScript</category>
      <category>비동기</category>
      <category>Promise</category>
      <category>async/await</category>
      <category>이벤트 루프</category>
    </item>
    <item>
      <title><![CDATA[Suspense 안쪽과 useSuspenseQuery]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/react-suspense-internals-and-tanstack-query</link>
      <description><![CDATA[Suspense는 fallback 스위치가 아니에요. TanStack Query가 그걸 따라간 모양도 봐요.]]></description>
      <content:encoded><![CDATA[컴포넌트 안에서 if (isLoading) return <Spinner /> 를 또 쓰고 있을 때마다 한숨이 나왔어요. 화면 어딘가에는 항상 스피너 하나가 떠 있는데, 그게 누구의 책임인지 매번 정해야 했거든요. 그러다 <Suspense fallback={<Spinner />}> 로 옮긴 뒤에도 한참은 fallback 채우는 스위치 정도로만 썼어요. 그 안쪽이 어떻게 돌아가는지 모르고요. Suspense는 무엇을 멈추는가 React 공식 문서는 Suspense를 한 줄로 정의해요. 자식이 로딩을]]></content:encoded>
      <pubDate>Tue, 28 Apr 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/react-suspense-internals-and-tanstack-query</guid>
      <category>React</category>
      <category>Suspense</category>
      <category>TanStack Query</category>
    </item>
    <item>
      <title><![CDATA[단위마다 다른 기준이 있다]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/css-length-units-map</link>
      <description><![CDATA[100vh가 잘리고 1px이 안 커지는 이유, 단위마다 기준이 달라서예요.]]></description>
      <content:encoded><![CDATA[데스크톱에서 잘 보이던 레이아웃이 모바일에서 100vh를 줬는데도 아래로 잘려요. 폰트만 1px 키웠더니 한 곳에서만 안 커져요. 저도 한참은 "px이면 px이지" 싶었거든요. 알고 보니 CSS의 길이 단위는 다들 자기 기준점을 들고 살아가더라고요. px, em, rem, vh, dvh가 어떤 기준 위에 서 있는지 한 번 그려두면 그 다음부터는 선택이 직관이 돼요. px는 절대 단위가 아니다 CSS 사양은 px를 절대 단위로 분류해요. 1px = 1/96 inch로 못 박아 둔 것까지도 절대]]></content:encoded>
      <pubDate>Mon, 27 Apr 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/css-length-units-map</guid>
      <category>CSS</category>
      <category>단위</category>
      <category>반응형</category>
      <category>접근성</category>
    </item>
    <item>
      <title><![CDATA[이미지 포맷, 무엇을 언제 쓸까]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/image-formats-when-to-use</link>
      <description><![CDATA[사진 한 장에 .avif, .webp, .jpg를 함께 두라는 가이드, 정말 필요할까요?]]></description>
      <content:encoded><![CDATA[같은 사진을 .avif, .webp, .jpg 세 가지로 만들어두라는 가이드를 처음 봤을 때 좀 머리가 아팠어요. 한 이미지를 위해 파일을 세 벌씩 두라고요? 그런데 막상 들여다보면 다 이유가 있어요. 압축률, 알파 채널, 애니메이션, 색역에서 포맷마다 푸는 문제가 다르거든요. 같은 사진에 포맷이 여러 개인 이유 이미지 포맷이 많은 건 압축, 알파 채널, 애니메이션, 색역 같은 영역에서 서로 다른 트레이드오프를 풀고 있기 때문이에요. JPEG는 사진을 작게 만드는 데 강하지만 투명 배경을 못]]></content:encoded>
      <pubDate>Mon, 27 Apr 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/image-formats-when-to-use</guid>
      <category>이미지</category>
      <category>성능</category>
      <category>HTML</category>
      <category>브라우저</category>
    </item>
    <item>
      <title><![CDATA[웹폰트 다섯 종류, 결국 한 줄]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/webfont-formats-and-loading</link>
      <description><![CDATA[다섯 포맷 다 올리지 않아도 돼요, WOFF2 한 줄로 끝나거든요.]]></description>
      <content:encoded><![CDATA[브랜드 폰트를 처음 올릴 때 가장 많이 헷갈리는 게 파일 종류예요. .ttf, .otf, .woff, .woff2까지 다섯 개를 다 받아서 한꺼번에 올리는 분들도 있고, .woff2 하나만 올리는 분들도 있어요. 옛날 글은 다 올리라고 하고 최신 글은 WOFF2 한 줄이면 끝이라고 하니까 누구 말이 맞나 싶어지죠. 저도 그랬거든요. 그래서 다섯 포맷이 어떻게 흘러왔고, 지금 한국어 사이트에 폰트 한 줄을 거는 게 왜 진짜로 한 줄로 끝나는지 한 번에 정리해 봤어요. 다섯 포맷이 다 필요했던 시절]]></content:encoded>
      <pubDate>Mon, 27 Apr 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/webfont-formats-and-loading</guid>
      <category>웹폰트</category>
      <category>CSS</category>
      <category>성능</category>
    </item>
    <item>
      <title><![CDATA[as를 쓰기 전에 한 번 더 보세요]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/typescript-as-assertion</link>
      <description><![CDATA[as는 컴파일러를 설득할 뿐이라 narrowing이나 satisfies가 더 단단해요.]]></description>
      <content:encoded><![CDATA[TypeScript에서 빨간 줄이 가장 보기 싫었던 자리는 어디인가요? 저는 document.getElementById("canvas") 한 줄에서 HTMLElement | null이 자꾸 발목 잡히는 자리였어요. 그래서 as HTMLCanvasElement를 붙이고 빨간 줄을 끄고 다음 줄로 넘어갔거든요. 그게 잘못된 건 아니에요. 다만 as가 정확히 무엇을 멈추고 무엇을 멈추지 않는지 모른 채로 쓰면, 나중에 런타임에서 null.getContext is not a function 같은 에러를]]></content:encoded>
      <pubDate>Sun, 26 Apr 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/typescript-as-assertion</guid>
      <category>TypeScript</category>
      <category>타입 시스템</category>
      <category>satisfies</category>
      <category>ESLint</category>
    </item>
    <item>
      <title><![CDATA[제어 흐름 분석으로 보는 타입 가드]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/typescript-type-guards</link>
      <description><![CDATA[타입 가드들이 사실은 한 메커니즘이라는 걸 알면, 함정도 같이 보여요.]]></description>
      <content:encoded><![CDATA[if (typeof x === 'string') 안쪽에서 빨간 줄이 사라지는 게, 처음엔 신기했어요. typeof 라는 런타임 연산자랑 TypeScript 의 정적 타입이 어떻게 통하는 거지 싶었거든요. 사용자 정의 가드 (x is Foo) 까지 마주치고 나서야 깨달은 게 있어요. 타입 가드는 따로따로 외우는 문법이 아니라, 같은 한 가지 일을 다른 각도에서 묻는 도구라는 점이요. 타입 가드는 도구 모음이 아니에요 function format(x: string | number) { if]]></content:encoded>
      <pubDate>Sun, 26 Apr 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/typescript-type-guards</guid>
      <category>TypeScript</category>
      <category>타입 시스템</category>
      <category>타입 가드</category>
      <category>제어 흐름 분석</category>
    </item>
    <item>
      <title><![CDATA[meta 태그는 모드 4개로 끝나요]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/html-meta-tags-overview</link>
      <description><![CDATA[head에 들어갈 meta 태그가 매번 헷갈렸어요. 모드 4개로 보면 정리돼요.]]></description>
      <content:encoded><![CDATA[새 페이지를 만들 때마다 <head> 안에 어떤 meta 태그를 넣어야 하는지 매번 검색하게 돼요. 저도 마찬가지였어요. SEO 글에서 본 한 줄, 모바일 가이드에서 본 한 줄을 복붙하다 보면 비슷한 태그가 두 번 들어가거나, 정작 필요한 게 빠져 있거나 하죠. 분류만 잡으면 의외로 간단해요. WHATWG가 정한 규칙 한 줄만 보면 모든 meta 태그가 4개 카테고리로 떨어져요. <meta>엔 모드가 4개 있다 HTML 명세는 <meta> 요소에 대해 단호하게 말해요. "Exactly one]]></content:encoded>
      <pubDate>Sat, 25 Apr 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/html-meta-tags-overview</guid>
      <category>HTML</category>
      <category>브라우저</category>
      <category>SEO</category>
      <category>Open Graph</category>
    </item>
    <item>
      <title><![CDATA[link 태그 어디까지 활용하시나요?]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/link-resource-hints</link>
      <description><![CDATA[preload 만 거는데 LCP 가 그대로면, 5종이 자리를 잘못 잡은 거예요.]]></description>
      <content:encoded><![CDATA[처음 <link rel="preload"> 를 발견했을 때, "이걸로 다 해결되겠다" 싶어서 head 에 우다닥 박았어요. 그런데 LCP 가 줄어든 글도 있고, 콘솔에 unused preload 경고만 잔뜩 뜨는 글도 있더라고요. 1편 에서 자원 hint 셋이 정확히 뭐하는 애인지 풀었다면, 이번 편은 5종을 한 표 위에 올려놓고 자리부터 잡아볼게요. 자리를 잘못 잡으면 critical 자원의 대역폭만 잠식하기도 하고요. preload scanner 가 못 찾는 자원 브라우저는 HTML 을]]></content:encoded>
      <pubDate>Sat, 25 Apr 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/link-resource-hints</guid>
      <category>성능</category>
      <category>브라우저</category>
      <category>HTML</category>
    </item>
    <item>
      <title><![CDATA[preload 와 그 형제들]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/preload-and-friends</link>
      <description><![CDATA[preload, prefetch, modulepreload 가 비슷해 보이지만 처리 시점이 정말 달라요.]]></description>
      <content:encoded><![CDATA[성능을 손볼 때 head 에 박는 link 태그가 다섯 가지 있어요. <link rel="dns-prefetch">, <link rel="preconnect">, <link rel="preload">, <link rel="prefetch">, <link rel="modulepreload">. 앞 둘은 연결을 미리 해두고, 뒤 셋은 자원을 미리 받아두는 쪽이에요. 이름이 다 비슷해서 한참 헷갈렸거든요. 다섯 다 "미리 한다" 는 결인데, 막상 박아보면 어떤 글에서는 LCP 가 줄어들고 어떤]]></content:encoded>
      <pubDate>Sat, 25 Apr 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/preload-and-friends</guid>
      <category>성능</category>
      <category>브라우저</category>
      <category>HTML</category>
    </item>
    <item>
      <title><![CDATA[npm 말고도 많은 이유]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/javascript-package-managers-history</link>
      <description><![CDATA[npm만 쓰다가 pnpm이나 bun은 왜 있는지 궁금하셨다면요.]]></description>
      <content:encoded><![CDATA[팀 저장소를 받아서 pnpm install 을 쳤는데 "package manager mismatch" 에러가 떠요. 분명 어제까지 npm install 로 잘 되던 저장소였거든요. 이런 경험 한 번쯤은 있으실 거예요. 패키지 매니저가 왜 이렇게 여러 개인지, 어느 걸 고르는 게 맞는지 감이 잘 안 잡히죠. node_modules 는 왜 이렇게 생겼나 Node.js 에서 require("express") 를 쓰면 런타임이 파일을 찾는 방식이 있어요. 현재 디렉토리의]]></content:encoded>
      <pubDate>Thu, 23 Apr 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/javascript-package-managers-history</guid>
      <category>npm</category>
      <category>pnpm</category>
      <category>Yarn</category>
      <category>Bun</category>
      <category>Node.js</category>
    </item>
    <item>
      <title><![CDATA[React key, 리스트 밖에서도 써봤나요]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/react-key-beyond-lists</link>
      <description><![CDATA[리스트 렌더링용이라 여겼던 key가 state 리셋 도구라는 사실, 놓치셨을 수 있어요.]]></description>
      <content:encoded><![CDATA[리스트를 그릴 때 React가 "이 자식에 고유한 key를 붙여라" 하고 경고하잖아요. 저도 처음엔 그게 React 콘솔을 조용히 만드는 주문 같은 거라고 여겼어요. 근데 key를 잘못 고르면 체크박스가 엉뚱한 행을 따라다니고, 입력하던 텍스트가 다른 자리로 옮겨가기도 해요. 범인은 대개 index였거든요. 그리고 같은 개념이 리스트 밖에서, state를 통째로 리셋하는 스위치로도 쓰여요. 체크박스가 엉뚱한 행을 따라가요 세 줄짜리 할 일 목록을 그려볼게요. 각 항목 옆에 체크박스가 있고,]]></content:encoded>
      <pubDate>Thu, 23 Apr 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/react-key-beyond-lists</guid>
      <category>React</category>
      <category>렌더링</category>
      <category>상태관리</category>
    </item>
    <item>
      <title><![CDATA[Zustand 밑에 깔린 훅의 정체]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/react-use-sync-external-store</link>
      <description><![CDATA[React가 렌더를 중단하는 사이, 외부 store 값이 바뀌면 화면이 갈라져요.]]></description>
      <content:encoded><![CDATA[Zustand의 v4 changelog를 넘기다가 use-sync-external-store 라는 긴 이름을 마주친 적이 있어요. 버전이 올라간 이유 한 줄에 이 훅이 등장하더라고요. 처음엔 "그냥 subscribe 감싸는 유틸이겠거니" 했는데, React 공식 문서가 이 훅을 유난히 신중하게 설명하는 게 궁금해서 읽어봤어요. 동시성 렌더링이 만드는 조용한 함정 하나가 있더라고요. 렌더 중단 사이에 store가 바뀌면 React 17까지는 렌더가 한 번 시작되면 끝까지 달렸어요. 어디선가 상태가]]></content:encoded>
      <pubDate>Thu, 23 Apr 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/react-use-sync-external-store</guid>
      <category>React</category>
      <category>Hooks</category>
      <category>concurrent</category>
      <category>상태관리</category>
    </item>
    <item>
      <title><![CDATA[subscribe 한 줄에 숨은 옵저버 패턴]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/observer-pattern-redux-zustand</link>
      <description><![CDATA[Redux의 store.subscribe와 Zustand의 selector, 이름만 바꾼 옵저버 패턴이에요.]]></description>
      <content:encoded><![CDATA[Redux를 처음 배울 때 store.subscribe(listener)를 읽고 저는 "dispatch 때문에 리렌더가 돌아가는구나" 정도로 대충 넘겼어요. 나중에 Zustand를 쓰면서 useStore(s => s.count)라는 selector를 보고는 또 "신기하네" 하고 넘겼죠. 어느 날 두 코드를 나란히 놓고 보니까 같은 뼈대가 보이더라고요. 이름표만 다를 뿐 GoF의 옵저버 패턴이 그대로 들어있었어요. 옵저버와 Pub-Sub은 자주 섞여요 디자인 패턴 책을 펼쳐보면 "옵저버 패턴"과]]></content:encoded>
      <pubDate>Wed, 22 Apr 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/observer-pattern-redux-zustand</guid>
      <category>React</category>
      <category>상태관리</category>
      <category>디자인패턴</category>
      <category>Redux</category>
      <category>Zustand</category>
    </item>
    <item>
      <title><![CDATA[React 입력의 두 갈래 길]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/react-controlled-vs-uncontrolled</link>
      <description><![CDATA[onChange 마다 리렌더되는 게 부담일 때, 비제어가 답이 되기도 해요.]]></description>
      <content:encoded><![CDATA[폼 입력을 받는 컴포넌트를 만들다 보면 어느 순간 고민이 시작돼요. 이 값을 useState 로 들고 있어야 할지, 그냥 ref 로 꺼내 써도 되는지 말이에요. 저도 처음엔 무조건 state 로 관리했거든요. 근데 입력창이 열 개쯤 되니까 한 글자 쳤을 뿐인데 페이지 전체가 다시 그려지는 게 눈에 보이더라고요. 그때부터 "제어" 와 "비제어" 라는 이름이 귀에 들어오기 시작했어요. 같은 입력, 다른 주인 같은 <input> 을 두고도 React 에서는 두 방식으로 다룰 수 있어요. 한쪽은 값이]]></content:encoded>
      <pubDate>Wed, 22 Apr 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/react-controlled-vs-uncontrolled</guid>
      <category>React</category>
      <category>폼</category>
      <category>성능</category>
      <category>Hooks</category>
    </item>
    <item>
      <title><![CDATA[자식의 ref를 부모가 쓰려면]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/react-use-imperative-handle</link>
      <description><![CDATA[부모에서 자식의 focus나 scroll을 호출해야 할 때 쓰는 훅이에요.]]></description>
      <content:encoded><![CDATA[로그인 폼을 제출했는데 비밀번호가 틀렸을 때, 커서가 다시 비밀번호 칸으로 돌아가면 편하죠. 저도 자식 컴포넌트를 만들어놓고는 부모 쪽에서 "이 input에 focus 좀 넣고 싶은데" 하고 막힌 적이 있거든요. React는 선언적 모델이 기본이라서, 부모가 자식 내부 DOM을 직접 건드리는 건 예외적인 일이에요. 그래서 아예 전용 출구가 따로 마련돼 있어요. 부모가 자식의 무언가를 건드려야 할 때 <input>을 자식 컴포넌트 안에 숨긴 상태에서 focus를 부모가 어떻게 호출할지]]></content:encoded>
      <pubDate>Wed, 22 Apr 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/react-use-imperative-handle</guid>
      <category>React</category>
      <category>Hooks</category>
      <category>ref</category>
    </item>
    <item>
      <title><![CDATA[Flux가 남긴 단방향 흐름]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/flux-pattern</link>
      <description><![CDATA[Flux 저장소는 archive 됐어요. 근데 상태 관리 얘기할 때 왜 여전히 Flux부터 나오죠?]]></description>
      <content:encoded><![CDATA[Redux 공식 문서를 읽다 보면 "prior art: Flux" 같은 섹션이 꼭 나와요. Zustand, Jotai, Recoil 소개글도 마찬가지예요. 2026년 지금, Flux 저장소는 이미 archive 됐는데 왜 매번 Flux 얘기부터 시작할까요. 저는 이게 오래 궁금했거든요. 답을 찾다 보니, 결국 Flux는 "도구" 가 아니라 사상의 원류 로 남아 있는 거였어요. 양방향 바인딩이 무너진 자리 Flux 얘기를 하려면 Flux가 왜 태어났는지부터 봐야 해요. 2010년대 초 프론트엔드는]]></content:encoded>
      <pubDate>Tue, 21 Apr 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/flux-pattern</guid>
      <category>React</category>
      <category>상태관리</category>
      <category>아키텍처</category>
      <category>Flux</category>
      <category>Redux</category>
    </item>
    <item>
      <title><![CDATA[Context를 여러 개로 쪼개야 하는 이유]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/react-context-split-value-dispatch</link>
      <description><![CDATA[한 객체로 묶어 넘기면 dispatch만 쓰는 컴포넌트까지 리렌더돼요.]]></description>
      <content:encoded><![CDATA[React Context로 전역 상태를 내려주는 코드를 보면 <Provider value={{ user, setUser }}> 처럼 상태와 업데이트 함수를 한 객체로 묶어 넘기는 패턴을 자주 만나요. 처음엔 저도 이게 깔끔해 보였거든요. 그런데 앱이 조금만 커지면 "나는 setter만 쓰는데 왜 이 컴포넌트가 계속 다시 그려지지?" 하는 질문이 따라붙어요. 답은 Context의 비교 방식에 있어요. 그리고 이걸 피하려고 React 공식 문서가 직접 보여주는 해법이 "값과 dispatch를 아예]]></content:encoded>
      <pubDate>Tue, 21 Apr 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/react-context-split-value-dispatch</guid>
      <category>React</category>
      <category>Context</category>
      <category>성능</category>
      <category>상태관리</category>
    </item>
    <item>
      <title><![CDATA[리액트는 언제 다시 그리나요]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/react-rerender-conditions</link>
      <description><![CDATA[state, props, 부모 중 무엇이 트리거였는지 헷갈릴 때 보세요.]]></description>
      <content:encoded><![CDATA[리액트로 앱을 만들다 보면 "이 컴포넌트가 왜 또 그려지지?" 하는 순간이 찾아와요. 저도 프로파일러를 켜놓고 한참을 들여다본 적이 있거든요. props가 안 바뀐 것 같은데 자식이 계속 반짝거리고, state를 갱신했는데 화면은 그대로인 경우도 있었어요. 리렌더가 어디서 시작되고 어디까지 번지는지 감이 잡히면 이런 상황이 훨씬 덜 당황스러워져요. 리렌더가 시작되는 순간 리액트 공식 문서는 렌더링을 Trigger, Render, Commit 세 단계로 설명해요. 여기서 핵심은 Trigger예요.]]></content:encoded>
      <pubDate>Tue, 21 Apr 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/react-rerender-conditions</guid>
      <category>React</category>
      <category>렌더링</category>
      <category>성능</category>
    </item>
    <item>
      <title><![CDATA[data URL은 언제 이득이고 언제 손해일까]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/data-url-when-to-use</link>
      <description><![CDATA[아이콘을 매번 요청하는 게 아까워서 인라인했는데, CSS 파일이 더 커졌어요.]]></description>
      <content:encoded><![CDATA[작은 아이콘이 많은 페이지에서 Network 탭이 너덜너덜한 게 싫었어요. 요청 수를 줄이자 싶어서 <img> 대신 data:image/svg+xml;base64,... 형태로 전부 CSS에 박아버렸죠. 그다음 프로젝트에선 CSS 파일이 갑자기 50KB 넘게 뚱뚱해지는 걸 보면서, 이게 맞는 건가 싶더라고요. data URL 자체는 오래된 도구인데, 잘 쓰는 감을 잡지 못하면 오히려 독이 돼요. 요청을 줄이려다 캐시를 잃었습니다 함정은 캐시에서 시작돼요. 원래 icon-ok.svg가 별도]]></content:encoded>
      <pubDate>Sat, 18 Apr 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/data-url-when-to-use</guid>
      <category>브라우저</category>
      <category>성능</category>
      <category>CSS</category>
      <category>보안</category>
    </item>
    <item>
      <title><![CDATA[offsetWidth 한 줄이 프레임을 훔칠 때]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/pretext-dom-measurement-reflow</link>
      <description><![CDATA[offsetWidth를 읽는 순간 브라우저가 멈춰요. 이 문제를 확인해봐요.]]></description>
      <content:encoded><![CDATA[가상 리스트가 이유 없이 뚝뚝 끊길 때가 있어요. 저도 처음엔 "아이템이 많아서 그런가" 싶어서 overscan 숫자만 만지작거렸거든요. 그러다 Performance 탭을 열었더니 빨간 막대가 빼곡하더라고요. "Forced reflow"라는 경고 위로 화살표를 따라가 보니, 아이템 높이를 재려고 박아둔 offsetHeight 한 줄이 원인이었어요. 한 줄이 프레임 하나를 통째로 삼키고 있었던 거죠. offsetWidth 한 줄이 하는 일 평소 브라우저는 style 계산, layout,]]></content:encoded>
      <pubDate>Sat, 18 Apr 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/pretext-dom-measurement-reflow</guid>
      <category>성능</category>
      <category>DOM</category>
      <category>렌더링</category>
      <category>브라우저</category>
    </item>
    <item>
      <title><![CDATA[이모지 length가 1이 아닌 이유]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/data-representation</link>
      <description><![CDATA[이모지 length 와 0.1+0.2, 30 년 된 표준 두 개의 함정이에요.]]></description>
      <content:encoded><![CDATA['A'.length 는 1 이에요. 그 다음 줄에 '😀'.length 를 찍으면 2 가 나와요. '👨‍👩‍👧'.length 는 8 까지 나오기도 하고요. 한 글자인데 length 가 1 이 아닌 자리, 처음 봤을 때 진짜로 당황했거든요. 이게 그냥 자바스크립트 버그가 아니라 30 년 전 유니코드 인코딩 표준을 그대로 들고 온 결과예요. 같은 결로 number 도 IEEE 754 라는 30 년 표준 위에 서 있어서, 0.1 + 0.2 가 0.3 이 아닌 자리가 나오고요. 두 함정의 뿌리가]]></content:encoded>
      <pubDate>Sat, 18 Apr 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/data-representation</guid>
      <category>CS</category>
      <category>프론트엔드</category>
      <category>JavaScript</category>
      <category>유니코드</category>
      <category>부동소수점</category>
    </item>
    <item>
      <title><![CDATA[Promise가 setTimeout보다 먼저인 이유]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/event-loop-microtasks</link>
      <description><![CDATA[Promise 가 setTimeout 보다 먼저인 데는 큐가 두 개라서요.]]></description>
      <content:encoded><![CDATA[콘솔에 줄 네 개 찍어 보면 이상한 순서가 나와요. setTimeout(fn, 0) 보다 Promise.then 이 먼저 실행되거든요. "0ms 인데 왜 뒤로 밀리지?" 처음엔 이게 의문이었어요. 그리고 이 순서는 매번 같아요. 우연이 아니라는 뜻이죠. 답은 HTML 명세 가 정의한 두 가지 큐, task 큐와 microtask 큐의 우선순위에 있어요. 한 task 가 끝나야 다음 task 로 자바스크립트 런타임은 한 번에 하나씩 일을 처리해요. call stack 위에서 함수 호출이 차곡차곡]]></content:encoded>
      <pubDate>Sat, 18 Apr 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/event-loop-microtasks</guid>
      <category>CS</category>
      <category>프론트엔드</category>
      <category>JavaScript</category>
      <category>이벤트 루프</category>
      <category>브라우저</category>
    </item>
    <item>
      <title><![CDATA[한 줄로 모두 보내는 HTTP/2]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/http-multiplexing</link>
      <description><![CDATA[여섯 개에서 멈추는 요청부터, 매일 짜는 코드 한 줄 뒤를 봐요.]]></description>
      <content:encoded><![CDATA[Network 탭을 열어두고 페이지를 새로고침하면 가로로 길게 늘어선 막대들이 줄을 서요. 처음 여섯 개는 동시에 출발하는 것 같은데, 일곱 번째 막대부터는 한 박자 늦게 시작하거든요. "왜 하필 6 개부터 줄을 서지?" 처음엔 그게 의문이었어요. 알고 보면 그 6 이라는 숫자는 MDN 이 정리한 브라우저 동작 이 직접 알려주는 한도예요. 그리고 이 한도가 사라진 자리에서 우리가 매일 짜는 번들과 sprite 전략까지 줄줄이 바뀌었거든요. 한 번에 하나씩 처리하던 HTTP/1.1 HTTP/1.1]]></content:encoded>
      <pubDate>Sat, 18 Apr 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/http-multiplexing</guid>
      <category>CS</category>
      <category>프론트엔드</category>
      <category>네트워크</category>
      <category>HTTP</category>
      <category>브라우저</category>
    </item>
    <item>
      <title><![CDATA[메인 스레드가 멈추면 클릭도 멈춰요]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/web-worker-concurrency</link>
      <description><![CDATA[정렬 한 줄에 클릭이 멈춰요. 메인 스레드가 한 가닥이거든요.]]></description>
      <content:encoded><![CDATA[큰 데이터를 정렬하는 동안 화면이 잠깐 얼어붙은 적이 있어요. 마우스로 클릭해도 반응이 없고, 스크롤도 안 되고, 한 2 초 뒤에 일제히 풀리는 그 답답한 순간이요. 처음엔 "비동기인데 왜 다른 게 같이 멈춰?" 싶었거든요. 사실 자바스크립트는 비동기여도 한 스레드 위에서 돌아요. 그 한 스레드가 정렬 함수에 묶여 있으면 클릭, 스크롤, 페인트 모두 같이 묶이는 거예요. 답은 Web Worker 라는, 별도 스레드를 띄우는 표준이에요. 다만 워커가 모든 문제를 푸는 건 아니에요. 렌더러 프로세스]]></content:encoded>
      <pubDate>Sat, 18 Apr 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/web-worker-concurrency</guid>
      <category>CS</category>
      <category>프론트엔드</category>
      <category>Web Worker</category>
      <category>동시성</category>
      <category>성능</category>
      <category>브라우저</category>
    </item>
    <item>
      <title><![CDATA[브라우저는 어떻게 화면을 그릴까]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/how-browsers-render-pages</link>
      <description><![CDATA[HTML을 받은 브라우저는 바이트 덩어리부터 픽셀까지 여섯 단계를 거쳐요.]]></description>
      <content:encoded><![CDATA[DevTools Performance 탭을 열어보면 알록달록한 막대가 한가득 쌓여 있어요. 파란 막대는 Loading, 노란 막대는 Scripting, 보라색은 Rendering, 초록색은 Painting. 처음엔 이게 뭘 뜻하는지 감이 안 잡혔거든요. "왜 CSS 하나 바꿨는데 노란 막대 밑에 보라색 막대까지 깔리지?" 싶었죠. 알고 보면 각 색깔이 브라우저가 한 프레임을 그리는 단계에 대응해요. 그래서 단계를 알면 막대가 읽히기 시작합니다. 바이트 한 덩어리가 화면이 되기까지 서버가 보낸]]></content:encoded>
      <pubDate>Thu, 16 Apr 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/how-browsers-render-pages</guid>
      <category>브라우저</category>
      <category>렌더링</category>
      <category>성능</category>
    </item>
    <item>
      <title><![CDATA[렌더링은 누가 언제 할 것인가]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/rendering-strategies-map</link>
      <description><![CDATA[SSR, SSG, ISR 이름은 익숙한데 막상 프로젝트에서 뭘 고를지 막힐 때가 많아요.]]></description>
      <content:encoded><![CDATA[SSR, CSR, SSG, ISR. 이름은 어디서든 본 것 같은데 막상 새 프로젝트에서 무엇을 고를지는 매번 멈칫하게 돼요. 저도 그랬거든요. 용어가 많아 보여도 기준은 둘뿐이에요. HTML을 언제 만들고 누가 만드느냐. 이 두 축만 잡으면 지도가 한 장에 들어와요. 렌더링이라는 말이 가리키는 것 서버에서 HTML을 조립하는 것도 렌더링, 브라우저가 상태 변화에 따라 DOM을 갱신하는 것도 렌더링이에요. 한 단어가 여러 층위에서 쓰이다 보니 SSR과 CSR의 경계가 흐릿해지거든요. 근데 차이는]]></content:encoded>
      <pubDate>Thu, 16 Apr 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/rendering-strategies-map</guid>
      <category>성능</category>
      <category>SSR</category>
      <category>SSG</category>
      <category>ISR</category>
      <category>Next.js</category>
    </item>
    <item>
      <title><![CDATA[Virtual DOM이 왜 필요할까요]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/why-react-uses-virtual-dom</link>
      <description><![CDATA[Virtual DOM이 왜 있고 무엇으로 이루어져 있는지 짚어봐요.]]></description>
      <content:encoded><![CDATA[처음 React를 배울 때 Virtual DOM이 빠르다고 들었어요. 브라우저 DOM 조작이 느리니까, 가벼운 JS 객체 트리로 먼저 계산한 다음 바뀐 곳만 DOM에 반영한다고요. 그럴듯했습니다. 근데 막상 react.dev를 한참 뒤져도 "Virtual DOM"이라는 단어 자체가 거의 안 나와요. Reconciliation이라는 용어가 나오고, render tree나 element 같은 이름이 그 자리를 대신하고 있어요. 이유는 Virtual DOM이 빠름을 위한 장치가 아니라는 데 있어요.]]></content:encoded>
      <pubDate>Thu, 16 Apr 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/why-react-uses-virtual-dom</guid>
      <category>React</category>
      <category>DOM</category>
      <category>렌더링</category>
    </item>
    <item>
      <title><![CDATA[Bridge를 얇게, 스토어를 우회하기]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/bridgeless-and-codepush</link>
      <description><![CDATA[Bridge가 병목이 되고 심사가 느려지자, 개발자들이 찾은 두 가지 우회로예요.]]></description>
      <content:encoded><![CDATA[지난 편에서 WebView와 Bridge가 같은 아이디어의 두 얼굴이라는 이야기를 했어요. JS 코드가 네이티브 능력을 빌려오는 구조죠. 다만 이 Bridge가 스크롤과 애니메이션에서 병목이 되면서 "어떻게 하면 건너갈 일을 줄일까" 하는 흐름이 시작돼요. 그 흐름이 Bridge를 얇게 만들려는 New Architecture고, 그 옆에서 CodePush가 심사 병목을 우회하는 길을 냈어요. React Native의 New Architecture React Native의 초기 Bridge가]]></content:encoded>
      <pubDate>Thu, 16 Apr 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/bridgeless-and-codepush</guid>
      <category>하이브리드 앱</category>
      <category>WebView</category>
      <category>React Native</category>
      <category>Flutter</category>
    </item>
    <item>
      <title><![CDATA[앱 안에 심은 WebView와 Bridge]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/webview-and-bridge-origin</link>
      <description><![CDATA[웹팀은 당일 배포하는데 앱은 심사로 며칠을 기다리던 시절, 하이브리드 앱이 나온 이유예요.]]></description>
      <content:encoded><![CDATA[같은 기능을 iOS는 Swift로, Android는 Kotlin으로 두 번 만들어본 적 있으세요? 저는 작은 팀에서 이 구조를 돌리다가 버전 싱크가 어긋나는 걸 여러 번 봤어요. 웹 팀은 수정본을 당일에 배포하는데, 앱은 스토어 심사를 기다리면서 며칠이 흘러가죠. 이 답답함이 하이브리드 앱이라는 아이디어의 출발점이에요. 왜 하이브리드였나 네이티브로 두 플랫폼을 따로 개발하는 비용은 생각보다 큽니다. iOS 팀과 Android 팀을 따로 꾸려야 하고, 같은 기능을 두 번 설계해야 하거든요.]]></content:encoded>
      <pubDate>Thu, 16 Apr 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/webview-and-bridge-origin</guid>
      <category>하이브리드 앱</category>
      <category>WebView</category>
      <category>React Native</category>
      <category>Flutter</category>
    </item>
    <item>
      <title><![CDATA[이미지와 폰트만 잡아도 점수가 돌아온다]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/core-web-vitals-images-fonts</link>
      <description><![CDATA[이미지에 치수만 적어도 레이아웃이 안 밀려요. 웹폰트 교체는 조금 더 까다롭고요.]]></description>
      <content:encoded><![CDATA[로컬에선 Lighthouse 점수가 다 초록색이었어요. 배포한 다음 실제 모바일에서 같은 페이지를 열어봤더니 LCP 3.8초에 CLS 0.22, 초록색이 주황색으로 바뀌는 순간이 옵니다. Network 탭을 뒤지다 보면 범인은 거의 둘 중 하나예요. 이미지 아니면 폰트. LCP와 CLS가 같은 자리에서 무너진다 정의부터 짧게요. LCP(Largest Contentful Paint)는 뷰포트에 나타난 가장 큰 콘텐츠가 렌더된 시점이에요. Chrome은 2.5초를 기준으로 두고, 4초가 넘어가면]]></content:encoded>
      <pubDate>Wed, 15 Apr 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/core-web-vitals-images-fonts</guid>
      <category>성능</category>
      <category>CSS</category>
      <category>웹폰트</category>
    </item>
    <item>
      <title><![CDATA[바꾸기 전에 얼마나 바뀔지 재보기]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/measuring-migration-impact</link>
      <description><![CDATA[라이브러리 메이저 업그레이드, 얼마나 깨질지 감이 안 잡힐 때 AST가 도와줘요.]]></description>
      <content:encoded><![CDATA["다음 스프린트에 디자인 시스템 v2 올릴 건데, 얼마 걸려요?" 라는 질문을 받았을 때 저는 한참을 못 답했어요. 색상 토큰 이름이 바뀌고, 아이콘 패키지가 쪼개지고, 일부 컴포넌트 prop이 deprecated 됩니다. 파일이 몇 개나 영향을 받는지 감이 안 잡히거든요. 그냥 GitHub 검색으로 키워드를 몇 개 쳐보다가, 이게 과소집계라는 걸 깨달았어요. 범위 파악이 먼저다 마이그레이션에서 제일 먼저 해야 하는 건 변환 스크립트 작성이 아니에요. "얼마나 깨지는가" 를 재는 일이에요.]]></content:encoded>
      <pubDate>Wed, 15 Apr 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/measuring-migration-impact</guid>
      <category>JavaScript</category>
      <category>TypeScript</category>
      <category>AST</category>
      <category>codemod</category>
      <category>리팩토링</category>
    </item>
    <item>
      <title><![CDATA[코드를 텍스트가 아닌 트리로 읽기]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/reading-code-as-tree</link>
      <description><![CDATA[같은 이름의 함수가 300군데에 쏟아져 있을 때, grep으로는 뭘 놓칠까요?]]></description>
      <content:encoded><![CDATA[회사 레포에서 getUser 호출부를 찾아야 했어요. grep -rn "getUser" 한 줄로 끝낼 생각이었거든요. 매칭 결과가 312줄. 막상 열어보니 주석 안에도 있고, 문자열 리터럴로도 있고, getUserName 같은 엉뚱한 함수까지 딸려 나옵니다. "이게 함수 호출인지 변수 선언인지 import 경로인지" 텍스트 매칭은 구분을 못 해요. grep이 놓치는 장면 첫째, 이름이 같은 서로 다른 함수. 서비스 A의 getUser와 서비스 B의 getUser가 공존하는 모노레포에서는 단순]]></content:encoded>
      <pubDate>Wed, 15 Apr 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/reading-code-as-tree</guid>
      <category>JavaScript</category>
      <category>TypeScript</category>
      <category>AST</category>
      <category>codemod</category>
      <category>리팩토링</category>
    </item>
    <item>
      <title><![CDATA[스켈레톤은 왜 껌뻑거리는가]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/loading-ux-web-vitals</link>
      <description><![CDATA[fallback 하나 추가했더니 LCP가 뒤로 밀렸어요. 경계 위치를 다시 재봐야 해요.]]></description>
      <content:encoded><![CDATA[2편까지 경계 설계와 스트리밍 메커니즘을 정리했어요. 실제 프로덕션에 올려놓고 Lighthouse 돌려보면 의외의 숫자가 나와요. TTFB 는 확실히 좋아졌는데 LCP 는 오히려 뒤로 밀리고, 스켈레톤이 본 콘텐츠로 바뀌는 순간 CLS 가 튀는 거예요. 경계를 잘 그었다고 Web Vitals 가 자동으로 좋아지진 않거든요. 오히려 악화되는 축이 있어요. 스트리밍이 Web Vitals 를 바꾸는 방식 Next.js Streaming 가이드의 "Streaming and Web Vitals" 섹션이]]></content:encoded>
      <pubDate>Wed, 15 Apr 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/loading-ux-web-vitals</guid>
      <category>React</category>
      <category>Next.js</category>
      <category>RSC</category>
      <category>성능</category>
      <category>Suspense</category>
    </item>
    <item>
      <title><![CDATA[스트리밍 HTML은 어디서 끊기는가]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/streaming-html-chunks</link>
      <description><![CDATA[스트리밍이 켜져 있어도 첫 바이트는 한참 안 와요. 범인은 상위 await 한 줄이에요.]]></description>
      <content:encoded><![CDATA[1편에서 Suspense 경계를 어디 둘지 봤어요. 막상 설계대로 경계를 쪼개놨는데, DevTools Network 탭을 열어보면 첫 바이트가 한참 안 와요. "분명 스트리밍인데 왜 전통 SSR 처럼 기다리지?" 하고 코드를 뒤집어보면 범인은 거의 매번 상위 layout.tsx 안의 await 한 줄이에요. 경계는 제대로 그었지만, 상위에서 이미 페이지가 발목을 잡혀 있었거든요. 전통 SSR이 멈춰 있던 이유 전통적인 서버 렌더는 전체 HTML 이 완성된 후에야 응답을 시작해요. 느린 쿼리]]></content:encoded>
      <pubDate>Wed, 15 Apr 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/streaming-html-chunks</guid>
      <category>React</category>
      <category>Next.js</category>
      <category>RSC</category>
      <category>성능</category>
      <category>Suspense</category>
    </item>
    <item>
      <title><![CDATA[Suspense 경계는 어디에 그어야 하나]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/suspense-boundary-where</link>
      <description><![CDATA[Suspense를 fallback 넣는 스위치로 썼다면, 경계 한 번쯤 다시 봐야 해요.]]></description>
      <content:encoded><![CDATA[Next.js App Router 로 작업하다가 로딩이 길어지는 구간에 <Suspense> 를 감싸둔 적이 있어요. 스피너는 뜨긴 뜨는데, 정작 원하는 부분이 단독으로 먼저 보이지 않고 페이지 전체가 한꺼번에 나타나더라고요. fallback 을 바꿔봐도 소용이 없었어요. 범인은 컴포넌트가 아니라 경계 위치 였거든요. 비슷한 고민을 use client 한 줄이 끌고 오는 것들에서 번들 경계로 풀었던 기억이 나는데, 그거랑 다른 층위의 경계가 하나 더 있어요. Suspense가 감지하는 것,]]></content:encoded>
      <pubDate>Wed, 15 Apr 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/suspense-boundary-where</guid>
      <category>React</category>
      <category>Next.js</category>
      <category>RSC</category>
      <category>성능</category>
      <category>Suspense</category>
    </item>
    <item>
      <title><![CDATA[import가 안 되는 진짜 이유]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/typescript-module-resolution</link>
      <description><![CDATA[moduleResolution을 바꾸면 되긴 하는데, 왜 바꿔야 하는 건지 모르겠다면요.]]></description>
      <content:encoded><![CDATA[import something from './utils'를 썼는데 빨간 줄이 뜹니다. "Cannot find module"이라는 메시지가 반겨주죠. 분명 파일은 있는데요. 저도 처음엔 tsconfig를 이리저리 만지다가 moduleResolution을 바꾸니까 해결됐는데, 왜 그래야 하는지는 한참 뒤에야 이해했어요. 빨간 줄이 뜨는 그 순간 에디터에서 import 경로에 빨간 밑줄이 생기는 건 TypeScript 컴파일러가 "이 경로에 해당하는 파일을 못 찾겠어"라고 말하는 거예요. 여기서 한]]></content:encoded>
      <pubDate>Tue, 14 Apr 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/typescript-module-resolution</guid>
      <category>TypeScript</category>
      <category>Node.js</category>
      <category>모듈 시스템</category>
      <category>ESM</category>
    </item>
    <item>
      <title><![CDATA[`use client` 한 줄이 끌고 오는 것들]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/use-client-boundary-and-overuse</link>
      <description><![CDATA[파일 맨 위에 `use client`를 쓰면 경계가 그어지고, 그 아래 모듈까지 전부 클라이언트로 딸려가요.]]></description>
      <content:encoded><![CDATA[App Router 프로젝트에서 에러가 뜨면 반사적으로 use client를 한 줄 붙이게 돼요. 저도 한동안 그랬어요. useState가 안 돈다고 하면 붙이고, CSS-in-JS 라이브러리가 서버에서 막힌다고 하면 또 붙이고. 근데 어느 순간 번들 사이즈를 열어보니 생각보다 훨씬 뚱뚱해져 있었거든요. 범인을 따라가 보니 use client 한 줄이 혼자서 끌고 온 것들이 많았어요. use client가 실제로 하는 일 공식 문서 표현을 그대로 읽어보면 오해가 덜해져요. "Add 'use]]></content:encoded>
      <pubDate>Tue, 14 Apr 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/use-client-boundary-and-overuse</guid>
      <category>Next.js</category>
      <category>React</category>
      <category>RSC</category>
      <category>App Router</category>
    </item>
    <item>
      <title><![CDATA[가상 리스트가 DOM에서 덜어내는 것들]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/virtual-list-dom-cost</link>
      <description><![CDATA[수천 행짜리 리스트가 뻑뻑해질 때, 가상화는 뭘 빼고 뭘 다시 채워야 하는 걸까요]]></description>
      <content:encoded><![CDATA[수천 행짜리 테이블을 한 번 그려본 적 있다면, 스크롤이 이상하게 뻑뻑해지는 순간을 기억하실 거예요. 저도 처음엔 "렌더링이 왜 이렇게 무겁지" 싶어서 React DevTools Profiler만 뒤적였거든요. 근데 범인은 React가 아니라 그 아래 깔려 있는 DOM이었어요. 가상 리스트는 이 DOM을 덜어내는 기술이에요. 다만 덜어낸 만큼, 브라우저가 원래 해주던 일을 개발자가 대신 떠맡아야 합니다. 리스트가 무거워지는 순간 행이 몇천 개를 넘어가면, 각 행이 아무리 단순해도 DOM 노드]]></content:encoded>
      <pubDate>Tue, 14 Apr 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/virtual-list-dom-cost</guid>
      <category>React</category>
      <category>성능</category>
      <category>접근성</category>
      <category>DOM</category>
    </item>
    <item>
      <title><![CDATA[Object.assign compound가 RSC에서 안 보이는 이유]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/compound-pattern-module-graph</link>
      <description><![CDATA[Card.Body를 평범하게 썼을 뿐인데 App Router에서 에러가 났어요. 범인은 번들러가 못 보는 연결이었습니다.]]></description>
      <content:encoded><![CDATA[팀 디자인 시스템의 Card 컴포넌트를 Next.js App Router 프로젝트에 붙였는데, 엉뚱한 곳에서 에러가 났어요. <Card.Body>를 평범하게 쓴 것뿐이었거든요. 처음엔 버전 문제인가 싶었는데, 파고들수록 이야기가 달라졌어요. 디자인 시스템의 세 축을 맞추는 것과는 또 다른 층위의 문제였죠. 모든 부품을 Card. 뒤에 달던 시절 우리 Card는 compound component 패턴으로 구현돼 있었어요. API만 보면 깔끔하죠. <Card> <Card.Header />]]></content:encoded>
      <pubDate>Tue, 14 Apr 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/compound-pattern-module-graph</guid>
      <category>React</category>
      <category>디자인 시스템</category>
      <category>RSC</category>
      <category>Compound Component</category>
      <category>Next.js</category>
    </item>
    <item>
      <title><![CDATA[namespace 전환에 숨은 세 가지 비용]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/compound-pattern-three-costs</link>
      <description><![CDATA[Object.assign을 namespace로 고치면 RSC는 풀려요. 근데 API와 Context, 번들 쪽이 동시에 흔들려요.]]></description>
      <content:encoded><![CDATA[1편에서 Object.assign이 번들러 모듈 그래프에 잡히지 않는다는 걸 봤어요. namespace 패턴으로 바꾸면 그 문제는 풀린다는 것도 정리했고요. PR을 올리고 리뷰를 기다리던 시점엔 "이제 끝"이라고 생각했는데, 리뷰어들의 질문이 쌓이면서 비용 세 개가 하나씩 드러났어요. API가 바뀐다는 첫 번째 비용 Object.assign 방식에서는 Card 자체가 루트 컴포넌트였어요. <Card>...</Card>로 썼죠. 그런데 namespace 방식에서 Card는 함수가 아니라 객체예요.]]></content:encoded>
      <pubDate>Tue, 14 Apr 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/compound-pattern-three-costs</guid>
      <category>React</category>
      <category>디자인 시스템</category>
      <category>RSC</category>
      <category>Compound Component</category>
      <category>Next.js</category>
    </item>
    <item>
      <title><![CDATA[dot-notation과 named export를 함께 내보내기]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/compound-pattern-tradeoff-choice</link>
      <description><![CDATA[세 비용을 한 번에 풀 순 없어요. 대신 기본 API와 대안 경로를 같이 둘 수는 있었죠.]]></description>
      <content:encoded><![CDATA[2편에서 namespace 전환의 비용 세 개가 서로 얽혀 있다는 걸 봤어요. 그렇다면 이미 만들어진 디자인 시스템들은 이 얽힘을 어떻게 나눠 지고 있을까요. 팀 내부 논의에 영향을 준 관찰부터 정리해볼게요. 다른 라이브러리들은 어떻게 감수했나 우리가 참고한 라이브러리는 Chakra UI v3, Radix Primitives, Base UI, Ark UI, 그리고 seed-design이었어요. 패턴이 두 갈래로 갈리더라고요. 첫 번째 갈래는 대부분의 부품을 Client로 두고 namespace]]></content:encoded>
      <pubDate>Tue, 14 Apr 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/compound-pattern-tradeoff-choice</guid>
      <category>React</category>
      <category>디자인 시스템</category>
      <category>RSC</category>
      <category>Compound Component</category>
      <category>Next.js</category>
    </item>
    <item>
      <title><![CDATA[Emotion은 스타일을 어떻게 주입할까]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/emotion-style-injection</link>
      <description><![CDATA[css prop을 쓰면 스타일이 알아서 적용되죠. 그 사이에서 Emotion이 하는 일을 따라가 봅니다.]]></description>
      <content:encoded><![CDATA[css={{ color: 'red' }}을 쓰면 빨간 글씨가 나옵니다. 근데 그 사이에 뭐가 벌어지는지 생각해본 적은 별로 없었어요. 저도 한동안 "그냥 되니까" 쓰다가, 스타일 우선순위가 꼬이는 버그를 만나고 나서야 내부를 뜯어보게 됐거든요. Emotion이 자바스크립트 객체를 실제 CSS로 바꿔 브라우저에 꽂기까지, 네 단계를 하나씩 따라가 볼게요. css 함수가 반환하는 것 Emotion을 처음 쓰면 css 함수가 클래스명 문자열을 돌려줄 거라 짐작하기 쉬워요. @emotion/css]]></content:encoded>
      <pubDate>Tue, 14 Apr 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/emotion-style-injection</guid>
      <category>CSS-in-JS</category>
      <category>Emotion</category>
      <category>런타임 스타일링</category>
      <category>React</category>
    </item>
    <item>
      <title><![CDATA[런타임 없이 같은 DX를 유지하는 법]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/zero-runtime-css-variables</link>
      <description><![CDATA[Emotion의 런타임 주입을 CSS 변수로 대체하면 어떤 일이 생기는지, 세 라이브러리로 확인해 봅니다.]]></description>
      <content:encoded><![CDATA[지난 글에서 Emotion이 스타일을 주입하는 네 단계를 따라갔어요. 직렬화, Stylis 컴파일, 캐시, DOM 삽입. 이 모든 게 브라우저에서 벌어지는 런타임 작업이었죠. 근데 React Server Components가 등장하면서 이 런타임 파이프라인이 문제가 됐어요. 저도 Next.js App Router로 옮기면서 styled-components가 서버 컴포넌트에서 안 돌아가는 걸 보고 당황했거든요. 런타임 CSS-in-JS의 한계 Emotion과 styled-components 같은]]></content:encoded>
      <pubDate>Tue, 14 Apr 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/zero-runtime-css-variables</guid>
      <category>CSS-in-JS</category>
      <category>Vanilla Extract</category>
      <category>Panda CSS</category>
      <category>Linaria</category>
    </item>
    <item>
      <title><![CDATA[padding 비율 트릭은 이제 그만]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/css-aspect-ratio</link>
      <description><![CDATA[16:9 비율 만들겠다고 padding-top에 56.25%를 넣고 있나요? 더 나은 방법이 있어요.]]></description>
      <content:encoded><![CDATA[16:9 영상 비율을 만들겠다고 padding-top: 56.25%를 쓰고, 자식 요소를 position: absolute로 띄워본 적 있나요? 동작은 하는데 "이게 왜 비율이지?" 싶은 느낌을 떨칠 수가 없었어요. aspect-ratio 한 줄이면 그 해킹이 통째로 사라집니다. padding-top이 비율이 되던 시절 padding의 퍼센트 값은 부모 너비 기준으로 계산돼요. 높이가 아니라 너비요. 이 특성을 이용해서 padding-top: 56.25%(= 9/16 × 100)를 주면 부모]]></content:encoded>
      <pubDate>Mon, 13 Apr 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/css-aspect-ratio</guid>
      <category>CSS</category>
      <category>aspect-ratio</category>
      <category>레이아웃</category>
    </item>
    <item>
      <title><![CDATA[:has()로 부모를 고르는 법]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/has-selector-patterns</link>
      <description><![CDATA[자식을 보고 부모를 골라야 하는 순간, JavaScript 없이 CSS만으로 해결할 수 있어요.]]></description>
      <content:encoded><![CDATA[폼 입력 필드에 에러가 생기면 감싸는 컨테이너 배경을 빨갛게 바꿔야 했어요. 자식의 상태를 보고 부모를 골라야 하는 거죠. 예전엔 JavaScript로 부모 요소에 클래스를 붙였다 떼는 수밖에 없었어요. CSS 선택자는 항상 위에서 아래로만 흘렀거든요. :has()가 그 방향을 뒤집었습니다. 자식을 보고 부모를 고르고 싶다 카드 컴포넌트 안에 이미지가 있을 때만 레이아웃을 바꾸고 싶다고 해볼게요. 이미지가 없는 카드는 텍스트만 꽉 채우고, 이미지가 있는 카드는 좌우 분할로 바꾸는 거예요.]]></content:encoded>
      <pubDate>Mon, 13 Apr 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/has-selector-patterns</guid>
      <category>CSS</category>
      <category>선택자</category>
      <category>has</category>
    </item>
    <item>
      <title><![CDATA[npm install 말고 npx]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/npm-vs-npx</link>
      <description><![CDATA[create-react-app을 npx로 실행하면서도 npm과 뭐가 다른지 설명 못 하겠다면요.]]></description>
      <content:encoded><![CDATA[npx create-react-app my-app 한 번쯤은 쳐봤을 거예요. 근데 "npm이랑 npx가 뭐가 다른데?"라고 물으면 선뜻 대답하기 애매하죠. 저도 한동안 그냥 "npx 쓰면 설치 안 해도 된다" 정도로만 알고 넘어갔거든요. npm은 패키지를 관리하는 도구 npm은 Node.js의 패키지 매니저예요. npm install로 패키지를 설치하고, package.json으로 의존성을 추적하고, npm run으로 스크립트를 실행하죠. 여기서 핵심은 npm이 "설치"와 "관리" 에 초점을]]></content:encoded>
      <pubDate>Mon, 13 Apr 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/npm-vs-npx</guid>
      <category>Node.js</category>
      <category>npm</category>
      <category>npx</category>
      <category>CLI</category>
    </item>
    <item>
      <title><![CDATA[useMemo가 필요한 순간]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/react-usememo-patterns</link>
      <description><![CDATA[느린 것 같으니까 useMemo 감싸자, 로 시작하면 대부분 빗나갑니다.]]></description>
      <content:encoded><![CDATA[컴포넌트가 느린 것 같으면 제일 먼저 손이 가는 게 useMemo예요. 저도 처음엔 "비싸 보이는 계산은 일단 감싸두자"는 식으로 썼거든요. 근데 나중에 프로파일러를 켜보니, 감싼 부분은 멀쩡하고 정작 병목은 엉뚱한 데 있었습니다. useMemo는 측정 후 꺼내는 도구이지, 기본값으로 쓰는 보험이 아니에요. 매번 다시 계산되는 게 정말 문제인가 React 컴포넌트가 리렌더될 때 본문의 코드가 다시 실행되는 건 맞아요. 그래서 "리렌더 = 느리다"고 생각하기 쉽죠. 다만 대부분의 계산은 정말]]></content:encoded>
      <pubDate>Mon, 13 Apr 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/react-usememo-patterns</guid>
      <category>React</category>
      <category>성능</category>
      <category>Hooks</category>
    </item>
    <item>
      <title><![CDATA[찾아 바꾸기로는 부족할 때, codemod]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/what-is-codemod</link>
      <description><![CDATA[파일 수백 개에 흩어진 API를 한꺼번에 바꿔야 할 때, 찾아 바꾸기로는 안 돼요.]]></description>
      <content:encoded><![CDATA[라이브러리를 메이저 업데이트했더니 deprecated API가 300군데에서 터집니다. 에디터에서 Ctrl+Shift+H를 누르고 한숨부터 나오죠. 저도 몇 번을 겪었어요. 근데 찾아 바꾸기만으로는 이 문제가 깔끔하게 풀리지 않거든요. 찾아 바꾸기가 부족한 이유 에디터의 find-and-replace는 텍스트를 다뤄요. 코드가 아니라요. 예를 들어 getUser라는 함수 이름을 fetchUser로 바꾸고 싶다고 해볼게요. 단순히 텍스트를 치환하면 주석 안에 있는 getUser도 바뀌고, 문자열]]></content:encoded>
      <pubDate>Mon, 13 Apr 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/what-is-codemod</guid>
      <category>JavaScript</category>
      <category>TypeScript</category>
      <category>codemod</category>
      <category>AST</category>
      <category>리팩토링</category>
    </item>
    <item>
      <title><![CDATA[React에서 View Transitions 쓰기]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/react-view-transitions</link>
      <description><![CDATA[startViewTransition을 React에서 그냥 쓰면 타이밍이 어긋나요. flushSync로 맞추는 법부터 정리했어요.]]></description>
      <content:encoded><![CDATA[지난 글에서 document.startViewTransition()으로 페이지 전환을 매끄럽게 만드는 법을 봤어요. 브라우저 API 하나로 cross-fade가 되니까, React에서도 그냥 쓰면 될 것 같죠? 근데 막상 해보면 상태는 바뀌었는데 애니메이션이 안 나와요. React의 비동기 렌더링이 타이밍을 어긋나게 만드는 거예요. 왜 React에서 바로 안 되나 startViewTransition(callback)은 콜백이 끝나는 순간 새 화면의 스냅샷을 찍어요. 근데 React의]]></content:encoded>
      <pubDate>Mon, 13 Apr 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/react-view-transitions</guid>
      <category>React</category>
      <category>JavaScript</category>
      <category>View Transitions</category>
    </item>
    <item>
      <title><![CDATA[깜빡임 없는 페이지 전환]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/view-transitions-api</link>
      <description><![CDATA[페이지 이동할 때마다 화면이 뚝 끊기죠. 브라우저 API 하나면 매끄럽게 이어줄 수 있어요.]]></description>
      <content:encoded><![CDATA[SPA에서 라우트를 바꿀 때마다 화면이 뚝 끊기는 거, 다들 한 번쯤 겪어봤을 거예요. DOM을 통째로 갈아치우니까 이전 화면이 사라지고 다음 화면이 뜨는 사이에 빈 프레임이 끼어들죠. 이걸 매끄럽게 만들려고 old 요소를 페이드아웃 시키고, new 요소를 페이드인 시키고, 그 사이에 사용자 클릭도 막아야 하고... 직접 해보면 생각보다 손이 많이 갑니다. View Transitions API는 이 과정을 브라우저에 맡기자는 아이디어에서 시작됐어요. 화면이 "뚝" 끊기는 순간 SPA 프레임워크는]]></content:encoded>
      <pubDate>Mon, 13 Apr 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/view-transitions-api</guid>
      <category>CSS</category>
      <category>JavaScript</category>
      <category>View Transitions</category>
    </item>
    <item>
      <title><![CDATA[BEM, 이름이 곧 설계다]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/css-bem-methodology</link>
      <description><![CDATA[클래스명 충돌에 지쳤다면, BEM이 제안하는 '이름으로 구조를 말하는 법'.]]></description>
      <content:encoded><![CDATA[프로젝트가 커지면 CSS에서 가장 먼저 부서지는 건 레이아웃이 아니라 이름이에요. .title { font-size: 24px; color: #111; } .card .title { font-size: 16px; } .modal .title { font-size: 20px; color: #fff; } .title이 세 곳에서 다른 의미로 쓰이고 있어요. 누군가 .title의 font-size를 고치면, 의도하지 않은 곳이 깨져요. 이름이 같으니까. 캐스케이드가 어떻게 동작하는지 정확히 알아도,]]></content:encoded>
      <pubDate>Sun, 12 Apr 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/css-bem-methodology</guid>
      <category>CSS</category>
      <category>BEM</category>
      <category>CSS 방법론</category>
      <category>네이밍</category>
    </item>
    <item>
      <title><![CDATA[box-sizing: border-box인 이유]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/css-box-sizing</link>
      <description><![CDATA[width: 200px인데 220px로 렌더링되는 이유, border-box 한 줄이 해결합니다.]]></description>
      <content:encoded><![CDATA[width: 200px인데 왜 220px이지? 카드 컴포넌트에 width: 200px을 줬는데, DevTools를 열어보면 실제 렌더링 크기가 220px입니다. 분명 200이라고 썼는데요. 범인은 padding입니다. CSS의 기본 box-sizing 값인 content-box에서는 width가 콘텐츠 영역만 의미하거든요. padding과 border는 그 바깥에 추가됩니다. <CodePlayground code={function ContentBoxTrap() { return ( <div]]></content:encoded>
      <pubDate>Sun, 12 Apr 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/css-box-sizing</guid>
      <category>CSS</category>
      <category>박스 모델</category>
      <category>레이아웃</category>
    </item>
    <item>
      <title><![CDATA[* + * 한 줄의 비밀]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/css-cascade-and-combinators</link>
      <description><![CDATA[선택자 한 줄이 마법처럼 동작하는 이유는 캐스케이드와 결합자에 있습니다.]]></description>
      <content:encoded><![CDATA[며칠 전 코드 리뷰에서 이런 한 줄을 봤어요. .stack > * + * { margin-top: 16px; } 별표 두 개에 더하기 한 개. 선언이라기엔 뭘 고르는 건지 한참을 봐야 합니다. 그런데 실행해 보면 부모 안의 자식들 사이에 16px 간격이 정확히 들어가요. 첫 번째 자식 위에는 안 들어갑니다. 마법 같죠. 사실 마법이 아니라 CSS의 기본기 두 가지가 정직하게 만나는 자리예요. 캐스케이드와 결합자입니다. 우선순위는 한 줄이 아니라 계단 "이 스타일이 왜 안 먹히지?"의 답은 거의]]></content:encoded>
      <pubDate>Sun, 12 Apr 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/css-cascade-and-combinators</guid>
      <category>CSS</category>
      <category>Cascade</category>
      <category>선택자</category>
    </item>
    <item>
      <title><![CDATA[디자인 시스템의 세 축, 하나라도 빠지면]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/team-design-system-guide</link>
      <description><![CDATA[버튼 컴포넌트부터 만들었다가 CSS 모음집이 된 적 있다면, 세 축부터 다시 세워보세요.]]></description>
      <content:encoded><![CDATA["일단 버튼부터 만들죠." 팀에서 디자인 시스템 얘기가 나오면 거의 이렇게 시작해요. 저도 그랬거든요. 버튼 하나 만들고, 인풋 하나 만들고, 얼추 공통 컴포넌트 폴더에 밀어 넣으면 시스템이 되는 줄 알았어요. 6개월 뒤 광경은 매번 비슷합니다. Button, PrimaryButton, ButtonV2, NewButton이 공존하고, 팔레트에는 파란색만 네 가지가 있고, 아무도 어떤 회색을 써야 하는지 몰라요. 코드는 쌓였는데 화면의 일관성은 오히려 나빠졌죠. 원인은 컴포넌트가 부족해서가]]></content:encoded>
      <pubDate>Sun, 12 Apr 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/team-design-system-guide</guid>
      <category>디자인 시스템</category>
      <category>디자인 토큰</category>
      <category>Atomic Design</category>
      <category>프론트엔드 아키텍처</category>
    </item>
    <item>
      <title><![CDATA[접근성은 특별한 기능이 아니에요]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/web-accessibility-why-and-how</link>
      <description><![CDATA[스크린 리더 사용자만의 문제가 아닙니다. 모두를 위한 기본기, 그 시작점을 정리합니다.]]></description>
      <content:encoded><![CDATA["접근성"이라는 단어를 들으면 어떤 이미지가 떠오르나요. 스크린 리더, 시각 장애, 법적 의무 같은 키워드가 먼저 떠오르고, 자연스럽게 "나중에 시간 나면"이라는 결론에 닿곤 합니다. 저도 그랬어요. 그런데 어느 날 손목을 다쳐서 마우스를 못 잡게 됐습니다. 키보드만으로 내가 만든 페이지를 써보니, Tab 키를 눌러도 포커스가 어디 있는지 안 보이고, <div>로 만든 버튼은 Enter를 아무리 눌러도 반응이 없었어요. 접근성은 '누군가를 위한 것'이 아니라 '지금 내가 필요한 것'이었습니다.]]></content:encoded>
      <pubDate>Sun, 12 Apr 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/web-accessibility-why-and-how</guid>
      <category>접근성</category>
      <category>웹 표준</category>
      <category>시맨틱 HTML</category>
    </item>
    <item>
      <title><![CDATA[z-index 9999의 배신]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/css-stacking-context</link>
      <description><![CDATA[z-index를 아무리 올려도 안 먹히는 순간이 있습니다. 범인은 숫자가 아니에요.]]></description>
      <content:encoded><![CDATA["z-index: 9999인데 왜 모달이 헤더 뒤에 깔려 있죠?" 이 한 줄을 속으로 외쳐본 적 있다면, 오늘 글은 당신 얘깁니다. 저도 몇 번을 겪었어요. 분명 숫자는 최대치로 박았는데 원하는 대로 안 올라오고, DevTools 한참 뒤져도 이유가 안 잡히고. 범인은 z-index 숫자가 아니라 stacking context라는 개념입니다. HTML은 평면이 아닙니다 브라우저 화면을 우리는 보통 2D로 상상하죠. 가로 X, 세로 Y. 하지만 드롭다운이 버튼 위에 얹히고, 모달이 본문을 덮고,]]></content:encoded>
      <pubDate>Sat, 11 Apr 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/css-stacking-context</guid>
      <category>CSS</category>
      <category>stacking-context</category>
      <category>z-index</category>
    </item>
    <item>
      <title><![CDATA[스타일링, 취향이 아니라 기준]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/css-styling-tradeoffs</link>
      <description><![CDATA[Tailwind냐 CSS-in-JS냐는 취향 싸움이 아닙니다. 각자 다른 문제를 푸는 도구예요.]]></description>
      <content:encoded><![CDATA[새 프로젝트를 세팅하는데 팀 채팅에 "스타일링 뭐로 할까요"를 던지면 세 가지 답이 돌아옵니다. "그냥 Tailwind 쓰죠." "우리 styled-components 잘 쓰고 있잖아요." "CSS Modules로도 충분해요." 셋 다 틀린 말이 아닙니다. 그래서 더 혼란스럽죠. 이 글은 "뭐가 제일 좋은가"를 정하려는 게 아니에요. 세 방식이 각각 어떤 문제를 풀려고 태어났는지 짚고, 프로젝트 조건에 따라 어느 쪽이 덜 아플지 고르는 기준을 세워봅니다. 똑같은 버튼, 세 가지 방법 같은 버튼을]]></content:encoded>
      <pubDate>Sat, 11 Apr 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/css-styling-tradeoffs</guid>
      <category>CSS</category>
      <category>Tailwind</category>
      <category>CSS-in-JS</category>
      <category>CSS Modules</category>
    </item>
    <item>
      <title><![CDATA[will-change는 공짜가 아니다]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/css-will-change</link>
      <description><![CDATA[한 줄이면 끝나는 최적화 같지만, 잘못 쓰면 오히려 더 느려집니다.]]></description>
      <content:encoded><![CDATA[버튼에 hover하면 살짝 떠오르는 카드 애니메이션을 만들었습니다. 로컬에선 부드럽게 잘 돌아갔어요. 그런데 저사양 기기에서 처음 한 번 hover할 때, 그 첫 프레임이 묘하게 탁 걸립니다. 두 번째부터는 멀쩡해요. "왜 첫 프레임만 버벅이지?" 싶어서 찾아보다 will-change라는 속성을 만나게 되죠. 이 글은 그 "왜"에 대한 이야기입니다. will-change가 실제로 뭘 하는지, 언제 써야 하고 언제는 오히려 해로운지. 첫 프레임이 버벅이는 이유 브라우저가 화면을 그리는 과정은 크게]]></content:encoded>
      <pubDate>Sat, 11 Apr 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/css-will-change</guid>
      <category>CSS</category>
      <category>성능</category>
      <category>애니메이션</category>
    </item>
    <item>
      <title><![CDATA[Flexbox만으로는 부족하다]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/grid-vs-flex</link>
      <description><![CDATA[flex-basis로 2차원을 흉내 내는 순간, 이미 Grid가 풀 문제입니다.]]></description>
      <content:encoded><![CDATA[새 페이지를 짜면서 display: flex부터 칩니다. 카드 세 장이 예쁘게 줄 세워져요. 그런데 한 줄에 네 장이 넘치면서 두 번째 줄로 넘어가자 갑자기 어긋나기 시작합니다. 두 번째 줄의 카드가 위 줄과 딱 맞게 정렬되지 않아요. 어찌어찌 flex-basis: calc(25% - 16px)로 욱여넣고 나서 생각하죠. "이게 맞나?" 이 글은 그 "이게 맞나?"에 대한 이야기입니다. Flex와 Grid는 어느 쪽이 더 좋은 게 아니라, 다루는 차원이 다릅니다. 그 차이만 잡으면 고민이]]></content:encoded>
      <pubDate>Sat, 11 Apr 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/grid-vs-flex</guid>
      <category>CSS</category>
      <category>레이아웃</category>
      <category>Flexbox</category>
      <category>Grid</category>
    </item>
    <item>
      <title><![CDATA[애니메이션이 끊기는 진짜 이유]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/transform-vs-top-left-rendering-pipeline</link>
      <description><![CDATA[top/left는 매 프레임 레이아웃을 다시 계산합니다. transform은 그 과정을 건너뛰죠.]]></description>
      <content:encoded><![CDATA[드롭다운 메뉴가 아래로 쓱 내려오는 애니메이션을 만들었어요. 로컬에선 부드러운데 모바일에서 보니까 뚝뚝 끊깁니다. DevTools Performance 탭을 켜보니 초록색 막대(Rendering)가 한가득. "position을 top에서 transform으로 바꾸기만 해도 된다"는 조언은 많이 들어봤지만, 왜 그런지 제대로 짚고 넘어간 적은 없었거든요. 이유는 브라우저가 화면을 그리는 순서에 있습니다. 브라우저가 한 프레임을 그리는 순서 사용자가 버튼을 누르든, 스크롤을 내리든, CSS]]></content:encoded>
      <pubDate>Sat, 11 Apr 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/transform-vs-top-left-rendering-pipeline</guid>
      <category>CSS</category>
      <category>성능</category>
      <category>애니메이션</category>
    </item>
    <item>
      <title><![CDATA[CSS 텍스트 줄바꿈의 기본 원리]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/css-text-control-basics</link>
      <description><![CDATA[브라우저가 텍스트를 어떻게 처리하고 줄바꿈을 결정하는지 기본 원리를 이해합니다.]]></description>
      <content:encoded><![CDATA[웹 개발을 하다 보면 텍스트가 예상과 다르게 줄바꿈되어 당황한 경험이 있을 겁니다. 긴 URL이 컨테이너를 뚫고 나가거나, 한글과 영어가 섞인 텍스트가 어색하게 잘리는 현상 말이죠. 이런 문제들을 해결하려면 먼저 브라우저가 텍스트를 어떻게 처리하는지 이해해야 합니다. 브라우저의 텍스트 렌더링 과정 브라우저가 텍스트를 화면에 그리는 과정을 단계별로 살펴보겠습니다. <AnimatedStep steps={[ { title: "텍스트 파싱", content: "HTML에서 텍스트 노드를 읽어들이고,]]></content:encoded>
      <pubDate>Sat, 11 Apr 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/css-text-control-basics</guid>
      <category>CSS</category>
      <category>텍스트</category>
      <category>제어</category>
    </item>
    <item>
      <title><![CDATA[실무에서 활용하는 텍스트 제어 패턴]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/text-control-real-world-examples</link>
      <description><![CDATA[반응형 디자인, 다국어 지원, 접근성을 고려한 실제 프로젝트 사례와 모범 사례를 소개합니다.]]></description>
      <content:encoded><![CDATA[시리즈의 마지막 편에서는 지금까지 배운 word-break와 white-space 속성을 실제 프로젝트에서 어떻게 활용하는지 살펴보겠습니다. 단순히 속성을 알고 있는 것과 실무에서 제대로 활용하는 것은 완전히 다른 문제죠. 반응형 디자인에서의 텍스트 제어 모바일 퍼스트 시대에서 텍스트는 다양한 화면 크기에 맞춰 적응해야 합니다. 단순히 word-break: break-all만 적용하면 될까요? <CodePlayground code={`/* 기본적이지만 문제가 있는 접근 */]]></content:encoded>
      <pubDate>Sat, 11 Apr 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/text-control-real-world-examples</guid>
      <category>CSS</category>
      <category>텍스트</category>
      <category>제어</category>
    </item>
    <item>
      <title><![CDATA[white-space 속성으로 공백과 줄바꿈 제어하기]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/white-space-property-mastery</link>
      <description><![CDATA[white-space 속성의 다양한 값들을 활용해 공백, 탭, 줄바꿈을 자유자재로 제어하는 방법을 학습합니다.]]></description>
      <content:encoded><![CDATA[웹 개발을 하다 보면 텍스트의 공백과 줄바꿈을 원하는 대로 제어하고 싶을 때가 있습니다. 코드 블록을 예쁘게 보여주거나, 시나 주소처럼 줄바꿈이 중요한 콘텐츠를 표시할 때 말이죠. 바로 이때 white-space 속성이 빛을 발합니다! white-space가 제어하는 것들 white-space 속성이 제어하는 건 크게 세 가지예요. 스페이스와 탭 같은 공백 문자의 처리 방식, 줄바꿈 문자(\n)의 처리 방식, 그리고 자동 줄바꿈 여부입니다. <AnimatedStep steps={[ {]]></content:encoded>
      <pubDate>Sat, 11 Apr 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/white-space-property-mastery</guid>
      <category>CSS</category>
      <category>텍스트</category>
      <category>제어</category>
    </item>
    <item>
      <title><![CDATA[word-break 속성 완벽 가이드]]></title>
      <link>https://tech-blog-six-phi.vercel.app/posts/word-break-property-guide</link>
      <description><![CDATA[word-break의 모든 값들과 실제 사용 사례를 통해 단어 단위 줄바꿈을 마스터합니다.]]></description>
      <content:encoded><![CDATA[이전 편에서 CSS 텍스트 줄바꿈의 기본 원리를 살펴봤다면, 이제 word-break 속성으로 더 정교한 제어를 해볼 차례입니다. 웹 개발을 하다 보면 긴 URL이 레이아웃을 깨뜨리거나, 한글 텍스트가 예상과 다르게 줄바꿈되는 상황을 마주하곤 합니다. 이런 문제들을 word-break 속성으로 해결할 수 있습니다. word-break의 세 가지 핵심 값 word-break 속성은 단어의 경계에서 줄바꿈이 어떻게 일어날지 결정합니다. 각 값의 차이점을 단계별로 살펴보겠습니다.]]></content:encoded>
      <pubDate>Sat, 11 Apr 2026 00:00:00 GMT</pubDate>
      <guid isPermaLink="true">https://tech-blog-six-phi.vercel.app/posts/word-break-property-guide</guid>
      <category>CSS</category>
      <category>텍스트</category>
      <category>제어</category>
    </item>
  </channel>
</rss>